标准输入输出
格式化输入输出
int a,b;
int arr[10]={1},*p=&b;
double d;
char ch,str[30];
scanf("%d%d",&a,p);
scanf("%c%s",&ch,str);
scanf("%lf",&d);
scanf("%d%d",arr+1,&arr[2]);
printf("%-10d %10d\n",a,*p);//'-'表示左对齐(默认右对齐),"10"表示位宽 printf("%c--%s\n",ch,str);
字符数据的输入输出
char c,s[20];
c=getchar();
putchar(c);
char ch; scanf("%c",&ch); if(ch>='a'&&ch<='z')ch=ch-'a'+'A'; else if(ch>='A'&&ch<='Z')ch=ch-'A'+'a'; printf("%c\n",ch);
s=gets();
puts(s);
预处理命令
变量:
变量必须定义在函数开头(尤其注意for循环。。。)
变量的存在性(可见性)
遵循内部优先原则(如果程序块内外都定义了,则外部被屏蔽),从被定义的地方到程序块结束都可见,不会因为存储类别而有异议(static只改变生命周期,不改变可见性)。
变量的生存周期
按照生存周期,变量可以分为
静态存储
-静态局部变量(程序块内有效)(static)
-静态外部变量(本文件内有效)(定义在全局)
-外部变量(用 extern 声明后,其他文件也可以引用)(extern)
动态存储
-自动变量(程序块内有效)(缺省或者auto)
-寄存器变量(程序块内有效)(register(不过编译器会优化,现在很少自己定义))
-形式参数(程序块内有效)
数组名&指针:
我们都知道一维数组的时候,数组名字指代数组第一个元素的地址,类似于一个指针。
比如,定义int a[10]; 我们就知道 a==&a[0]; *(a+i)==a[i]; a类似于一个指向int的指针变量,a+1在地址上实际是a+1*sizeof(int)。
同理,定义完int a[10][10]; 我们就知道 a==&a[0]; a[0]==&a[0][0].
a[0]类似于一个指向int的指针变量,a[0]+1在地址上实际是a[0]+1*sizeof(int)。
a类似于一个指向"整个"int[10]的指针变量,a+1在地址上实际是a+1*10*sizeof(int)。
这里不容易理解的地方在于,a[i]在数学上是存在且有数字的,他的值就是:&a[i][0],同时也是:*(a+i);但是在物理上却是不存在的。
*(a+i)的实际意义,其实不是“指针变量a[i]”,而是以a[i]起头的一维数组“a[i][0]...a[i][9]”的首地址。
这是由“数组的定义”定义的,所以说a类似于一个指向指针的指针,但是实际上并不是,它是一个数组的一部分,就是数组的首地址。不要完全按照指针去理解。
当然,指向指针的指针也可以定义动态二维数组。
int** a;
a=new int*[n];
for(int i=0;i<n;i++)
a[i]=new int[m];
这里的每个*(a+i)就是有物理存储的了。和静态数组的区别在于:
1,多了n个变量来存放二级指针。(缺点)
2,可以不连续存储。(优点)
分支结构:
?表达式(条件运算符)
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",a>b?1:0);
//这个程序是输入两个整数,如果第一个数字大于第二个则输出1,否则输出0
//C语言里唯一的三目运算符(+-*/都是二目)
//a?b:c //这个表达式的意思是,如果a是真(也就是说如果a非零),这个表达式的值为b,否则为c
c=(a>b?a:b);//这句话等价于max函数
if语句
嵌套的if语句里面的else的匹配要小心,与它上面的同一个程序块的离它最近的if匹配。
switch语句
char num=0; scanf("%c",&num);
switch(num){ case 1:p("wuhao");break; default :p("error"); case 2:p("xujunwu"); case 3:p("xuwangzihao");break; case 4:p("xuzhenghao"); }
//switch后面跟的变量必须是整数,意味着可以是整型或者字符型。
//任意两个case后面的值不能相同,否则编译出错。
//各个case和default只是入口,他们在逻辑上不分先后,一旦哪个入口被确定了代码就从那个冒号开始执行语句,直到switch语句的结束(遇到后大括号或者break)
//这段代码写的很“不标准”,理解用,可以运行一下看看。(标准的做法是,除了最后一个入口,其余入口做完自己该做的事后都应该加上break,这段代码在default和case 2后面缺失break)
循环结构:
逻辑表达式都是有值的,1(真/true)或者0(假/false)。
自增/自减运算符
a++;//意思是a=a+1;表达式返回值是之前的a的值
++a;//意思是a=a+1;表达式返回值是a+1之后的值
a--;//意思是a=a-1;表达式返回值是之前的a的值
--a;//意思是a=a-1;表达式返回值是a-1之后的值
int a=0;
if(a++)printf("a++ %d\n",a);
a=0;
if(++a)printf("++a %d\n",a);
//运行一下观察结果。(if语句括住的表达式值为1的时候才能继续执行,否则执行else(没有else则忽视))
while和do_while语句
int n;
scanf("%d",&n);
while(n--){
printf("*");
}
printf("\n");
//----------------------------------------------------------------
int n;
scanf("%d",&n);
do{
printf("*");
}while(--n);printf("\n");
//这两段代码执行的操作都是输入n,输出一行n个'*'。在n>0的时候完全相同,但是n<1的时候,dowhile会输出一个,while不会输出。
//两者的区别在于先判断再执行还是先执行后判断。
//dowhile语句有个特点就是不论初始什么情况,循环体内部的命令总是有机会被执行一次。
//dowhile语句结束的时候有个分号别忘了。。。。
break&continue语句
int a;
do{
scanf("%d",&a);
printf("%d\n",a);
}while(1);
while(1){
scanf("%d",&a);
printf("%d\n",a);
}
//这两段循环都是无限循环,因为负责判断的表达式是个常量1,永远为真。
while(1){
scanf("%d",&a);
printf("%d\n",a);
if(a==0)break;
}
//break语句跳出当前循环,继续顺序执行。当我们输入0的时候,循环结束。
while(1){
scanf("%d",&a);
if(a%2)continue;
printf("%d\n",a);
if(a==0)break;
}
//continue语句跳出当前这次循环的末尾,继续执行下一次循环。当我们输入奇数的时候,不输出。
for语句
for(a;b;c){
work();
}
//*********************
a;
while(b){
work();
c;
}
//以上两段代码功能完全一样。下面举个例子。
int n=10,i,a[11];
i=1;
while(i<=n){
scanf("%d",&a[i]);
i++;
}
//******************
int n=10,i,a[11];
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
}
//以上两个代码都是输入10个数字存在数组a里。
for(;;){
work();
}
//for语句三个表达式可以缺省。上面是一个无限循环。
指针:
int * p; //指向int的指针
int * p[4]; //指针数组,4个指向int数据的指针
int (*p)[4]; //指向int数组的指针
int * p(); //返回“指向一个int数据的指针”的函数
int (*p)(); //指向函数的指针,返回值为int
int **p; //指向“指向int数据的指针”的指针
void *p; //指向不确定数据类型的数据的指针(类型由计算机自动转换)
一个翻译C语言成人话的网站:https://cdecl.org/
看到一个巨无敌的读变量声明的方法叫做“右左震荡法” (来源:http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html)
就是说:
0,见到如下符号就进行替换
- "[]" == "一个数组,他的每个元素是"
- "()" == "一个函数,它的返回值是"
- "*" == "一个指针,指向"
1,找到变量;
2,往右看,直到表达式结束或者遇到一个右括号。
3,往左看,直到表达式开头或者遇到一个左括号。
重复23,直到全部扫完。
举个例子:
examples:
int *p[];
1) 找到变量. int *p[];
^
"p 是"
2) 往右看,直到表达式结束或者遇到一个右括号。
int *p[];
^^
"p 是一个数组,他的元素是"
3) 不能再往右走了 (表达式结束), 开始左移:
int *p[];
^
"p 是一个数组,他的每个元素是一个指针,指向"
4) 继续左移:
int *p[];
^^^
"p 是一个数组,他的每个元素是一个指针,指向int".
(or "p is an array where each element is of type pointer to int")
Another example:
int *(*func())();
1) Find the identifier. int *(*func())();
^^^^
"func 是"
2) Move right. int *(*func())();
^^
"func 是一个函数,返回值是"
3) 不能再右移了因为遇到了右括号,所以开始左移.
int *(*func())();
^
"func 是一个函数,返回值是一个指针,指向"
4) 不能再左移因为遇到左括号, 继续右移
. int *(*func())();
^^
"func 是一个函数,返回值是一个指针,指向一个函数,返回值是"
5) Can't move right anymore because we're out of symbols, so go left.
int *(*func())();
^
"func 是一个函数,返回值是一个指针,指向一个函数,返回值是一个指针,指向"
6) And finally, keep going left, because there's nothing left on the right.
int *(*func())();
^^^
"func 是一个函数,返回值是一个指针,指向一个函数,返回值是一个指针,指向int".
动态申请回收资源:
void * mallac(unsigned int size);
void * calloc(unsigned n,unsigned size);
void free(void * p); //p cames from the last calloc or mallac function.
void * realloc(void * p,unsigned int size); //resize
指针函数:
double Sum(double *arr,double n){
//函数主体
work();
}
double (*xzh)(double *,double);//声明函数指针。
xzh=Sum; //指针赋值。
sum=(*xzh)(a,10); //用函数指针调用函数。
自定义数据结构
struct结构体
struct pupil{ char name[10]; double yu; double shu; double wai; };//结构体定义
struct pupil sa[20]; //注意在C语言里,使用自定义数据类型的时候,struct前缀不能省略。(和C++区别)
struct pupil sum={" ",0,0,0};//顺序初始化成员(类似数组)
struct pupil sum={.name="aaa"};//允许单独初始化某个成员
for(i=1;i<=10;i++){ scanf("%lf%lf%lf",&sa[i].yu,&sa[i].shu,&sa[i].wai); //'.'是成员运算符 }
结构体指针
struct pupil PupilSum(struct pupil * arr , int n){//arr是一个指向“自定义数据类型pupil”的指针 struct pupil sum={" ",0,0,0}; //struct pupil sum={.name="aaa"};//允许单独初始化某个成员 int i; for(i=1;i<n;i++){
sum.yu+=(arr+i+1)->yu;//"->"是指向运算符,在调用指针指向的结构体内部成员的时候使用
sum.yu+=(*(arr+i+1)).yu;//由于运算符优先级的问题,这里的括号一个都不能省。。。。
//(这种写法和上一行效果一样,使用的时候二选一) } };
union共同体/联合体
union Data{
//联合体中所有数据公用一段存储空间,所以一个联合体元素的大小,等于其中占用空间最大的成员占用的空间,比如这个联合体 i 四个字节,c 一个字节,f 八个字节,所以每个该联合体数据占用空间就是八字节。一个联合体数据一次只能存储一个成员的信息。
int i;
char c;
double f;
}a={16},b={.c='a'};//可以对第一个元素赋值,或者对特定的一个元素赋值
union Data c={16,'h',1.0};//这种写法是错误的。
enum枚举类型
enum Weekday{sun,mon,tue,wed,thu,fri,sat};
//枚举类型如果使用默认的初值,就是{0...n-1}
enum Weekday workday,weekend=sat;
//---------------------------------------------------------------------------------------
enum Weekday{sun=3,mon=2,tue,wed=2,thu,fri,sat};
//枚举类型可以手动赋初值,可以重复,没有赋初值的元素的值,等于他上一个元素的值加一(首元素默认为0)
//比如现在他们的值是{3,2,3,2,3,4,5}
//枚举类型不能再次赋值(比如mon=4就是错的)
//枚举类型的变量可以赋值,可以相互比较(因为实质还是整数)
typedef声明新类型名
typedef int Grade;
Grade a; //这里的Grade等价于int
//--------------------------------------------------------------------------------------------------------------
typedef int DATA[10],*IP; //声明一个新的数据类型DATE,每个DATE类型的元素是长度为10的int数组。
//声明一个新的数据类型IP,每一个IP类型的元素是一个指向int类型数据的指针 DATA a[8]; //a是一个二维数组,这句话等价于int a[8][10];
IP p; //p是一个指向int的指针,这句话等价于int *p;
//--------------------------------------------------------------------------------------------------------------
typedef struct Node{ int v; int lc, rc; }ND,*NP;
NP root=NULL; //root是一个指向"struct Node"的指针 ND e[1000]; //e是一个数组,每个元素都是"struct Node"类型的。
sizeof函数,返回变量或者数据类型的大小(字节为单位)
typedef struct Node{ int v; int lc, rc; }ND,*NP;
NP root=NULL; ND e[1000];
printf("%d %d\n",sizeof(NP),sizeof(root));//这里输出的是4 4
//NP是一种数据类型,指向struct Node的指针,指针的大小由操作系统决定,32位下是4字节。root是NP类型的变量。 printf("%d %d %d\n",sizeof(ND),sizeof(e[0]),sizeof(e));//这里输出的是12 12 12000
//ND是struct Node,每个ND包含三个int,每个int是4字节,所以每个ND类型的变量是12字节。
//e[0]是一个ND类型的变量,e是包含了1000个ND类型变量的数组
字节对齐(百度面试遇到的.....)
接下来的文字,大量转载自:http://blog.csdn.net/hairetz/article/details/4084088
写出一个struct,然后sizeof,你会发现sizeof的结果往往都比你声明的变量总长度要大。这涉及到字节对齐的问题,对齐规则如下。
1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
如果在程序的头部加一句#pragma pack(1),情况会有所不同,它告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则.
ps:Vc,Vs等编译器默认是#pragma pack(8),所以测试我们的规则会正常;注意gcc默认是#pragma pack(4),并且gcc只支持1,2,4对齐。套用三原则里计算的对齐值是不能大于#pragma pack指定的n值。
举个例子:
{char;char;short;int;short;} sizeof 应该等于12
{char;short;char;int;short;} sizeof 应该等于16
//int是4,short是2,char是1
文件操作
文件顺序读写
FILE *fp; //文件指针,FILE类型在头文件stdio.h里
fp=fopen("add.in","r"); //指针赋值,指向目标文件,并说明使用文件方式。
//第一个参数是路径,可以是绝对路径也可以是相对路径。
//第二个是文件使用方式,主要有三种,r读,w写,a追加
//r 时如果文件不存在会返回NULL,w 时文件存在会删除文件再新建(小心数据丢失)
//此外还有 "r+" "w+" "a+" 三种方式,分别是读写,写读,追加读,有的编译器不支持,所以不推荐用
FILE *ofp;
ofp=fopen("add.out","w");
fsanf(fp,"%d",&a);
fprintf(ofp,"%d\n",&a);
c=fgetc(fp);
fputc(c,ofp);
fgets(s,n,fp);
fputs(s,ofp);
fclose(fp);//关闭文件,清除缓冲区,解放指针
fclose(ofp);
以二进制方式读写文件
struct {
...
}a[20];//这是一个匿名结构体
FILE *ifp,*ofp;
ifp=fopen("add.in","r");
ofp=fopen("add.out","w");
fread(a,sizeof(a[0]),20,ifp);//直接把文件存储的二进制数据映射到地址{a...a+20*sizeof(a[0])};
fwrite(a,sizeof(a[0]),20,ofp);//直接把内存中的二进制数据原封不动的存入文件。
随机读写数据文件(常用于二进制文件的读写)
FILE *ifp;
char s[512];
ifp=fopen("add.in","r");
s=fgets(s,512,ifp);
rewind(ifp);//让位置标记重新指向0L
fseek(ifp,100L,0);//模式0,位置标记指向文件开头100字节处。
fseek(ifp,50L,1);//模式1,位置标记指向当前位置+50字节处。
fseek(ifp,-10L,2);//模式2,位置标记指向文件末尾-10字节处。
int i=ftell(fp);//返回当前位置标记所在位置,如果函数出错,返回-1L
|
请发表评论