一.函数
1.1 初见函数
//求两个数之间的素数的和 #include<stdio.h> int isPrime(int i) { int ret=1; int k; for(k=2;k<i-1;k++) { if(i%k==0) { ret=0; break; } } return ret; } int main() { int m,n; int sum=0; int cnt=0; int i; scanf("%d %d",&m,&n); //m=10,n=31; if(m==1) m=2; for(i=m;i<=n;i++) { if(isPrime(i)) { sum +=i; cnt++; } } printf("%d %d\n",cnt,sum); }
//求出1到10.20到30和35到45的三个和 #include<stdio.h> int stn(int A,int B) { int sum; for(int i=A;i<=B;i++) { sum+=i; } return sum; } int main() { int sum; sum=stn(1,10)+stn(20,30)+stn(35,45); printf("sum=%d",sum); }
1.2 函数的定义和调用
1.2.1 函数的定义
函数是一块代码,接收零个或多个参数,做一件事情,并返回零个或一个值
1.2.2 函数的调用
函数名(参数值) ()起到了表示函数调用的重要作用 即使没有参数也需要()
#include<stdio.h> void cheer() { printf("cheer\n"); } int main() { cheer(); return 0; }
如果有参数,则需要给出正确的数量和顺序 这些值会被按照顺序依次用来初始化函数中的参数
//求出1到10.20到30和35到45的三个和 #include<stdio.h> int stn(int A,int B) { int sum; for(int i=A;i<=B;i++) { sum+=i; } return sum; } int main() { int sum; sum=stn(1,10)+stn(20,30)+stn(35,45); printf("sum=%d",sum); }
1.2.3从函数中返回值
int isPrime(int i) { int ret=1; int k; for(k=2;k<i-1;k++) { if(i%k==0) { ret=0; break; } } return ret; }
return停止函数的执行,并送回一个值 return; return表达式;
没有返回值的函数 void 函数名(参数表) 不能使用带值的return 可以没有return 调用的时候不能做返回值的赋值
二.数组
2.1 初识数组
//写一个程序计算用户输入的数字的平均数,并输出所有大于平均数的数? #include<stdio.h> int main() { int x; int num[100]; double sum=0; int cnt=0; scanf("%d",&x); while(x!=-1) { num[cnt]=x; sum+=x; cnt++; scanf("%d",&x); } if(cnt>0) { printf("%f\n",sum/cnt); int i; for(i=0;i<cnt;i++) { if(num[i]>sum/cnt) { printf("%d\n",num[i]); } } } }
//写一个程序计算用户输入的数字的平均数,并输出所有大于平均数的数? #include<stdio.h> int main() { int x; int num[100]; double sum=0; int cnt=0; scanf("%d",&x); while(x!=-1) { num[cnt]=x; int i; printf("%d\t",cnt); for(i=0;i<=cnt;i++) { printf("%d\t",num[i]); } printf("\n"); sum+=x; cnt++; scanf("%d",&x); } if(cnt>0) { printf("%f\n",sum/cnt); int i; for(i=0;i<cnt;i++) { if(num[i]>sum/cnt) { printf("%d\n",num[i]); } } } }
2.2 数组的使用:如何定义和使用数组,数组的下标和下标的范围
<类型>变量名称[元素数量]; int grades[100]; double weight[20]; 元素数量必须是整数 C99之前:元素数量必须是编译时刻确定的字面量
数组 是一种容器(放东西的东西),特点是: 其中所有的元素具有相同的数据类型; 一旦创建,不能改变大小 *(数组中的元素在内存中是连续依次排列的)
int a[10] 一个int的数组 10个单元:a[0],a[1],...a[9] 每个单元就是一个int类型的变量 可以出现在赋值的左边或右边: a[2]=a[1]+6 *在赋值左边的叫做左值
数组的单元 数组的每个单元就是数组类型的一个变量 使用数组时放在[]中的数字叫做下标或索引,下标从0开始计数: grades[0] grades[99] average[5]
有效的下标范围 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃 segmentation fault 但是也可能运气好,没造成严重的后果 所以这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]
可以创建长度为0的数组 int a[0]; 可以存在,但是无用
2.3 统计个数
写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束 #include<stdio.h> int main() { int x; int array_1[10]; int i; for(i=0;i<10;i++) { array_1[i]=0; } scanf("%d",&x); while(x!=-1) { if(x>=0 && x<=9) { array_1[x]++; } scanf("%d",&x); } for(int i=0;i<10;i++) { printf("%d:%d\t",i,array_1[i]); } }
2.4 数组运算
#include<stdio.h> //在一组给定的数据中,如何找出某个数据是否存在 int main() { int a[]={2,5,6,7,23,45,1243,33,4,8,9,0}; int x; int loc; printf("请输入一个数字:"); scanf("%d",&x); loc=search(x,a,sizeof(a)/sizeof(a[0])); if(loc!=-1) { printf("%d\t在第%d个位置上\n",x,loc); } else { printf("%d不存在\n",x); } } int search(int key,int a[],int length) { int ret=-1; int i; for(i=0;i<length;i++) { if(a[i]==key) { ret=i+1; break; } } return ret; }
int a[]={2,4,6,7,1,3,5,9,11,13,23,14,32} 直接用大括号给出数组的所有元素的初始值 不需要给出数组的大小,编译器替你数数
int a[10]={ [0]=2,[2]=3,6, } 用[n]在初始化数据中给出定位 没有定位的数据接在前面的位置后面 其他位置的值补零 也可以不给出数组大小,让编译器算 特别适合初始数据稀疏的数组
数组大小 sizeof给出整个数组所占据的内容的大小,单位是字节
2.5 二维数组
int a[3][5]; 通常理解为a是一个3行5列的矩阵
#include<stdio.h> int main() { int a[3][5]; //赋值 for(int i=0;i<3;i++) { for(int j=0;j<5;j++) { a[i][j]=i*j; } } //取值 for(int i=0;i<3;i++) { for(int j=0;j<5;j++) { printf("%d\t",a[i][j]); } printf("\n"); } }
二维数组的初始化 int a[][5]= { {0,1,2,3,4}, {2,3,4,5,6}, }; 列数是必须给出的,行数可以是由编译器来数 每行一个{},逗号分隔 最后的逗号可以存在,由古老的传统 如果省略,表示补零 也可以用定位(*C99 ONLY)
tic-tac-toe游戏 读入一个3*3的矩阵,矩阵中的数字为1表示该位置上有一个X,为0表示为O 程序判断这个矩阵是否有获胜的一方,输出表示获胜一方的字符X或O,或输出无人获胜
#include<stdio.h> int main() { const int size=3; int board[size][size]; int i,j; int num0fX; int num0fO; int result=-1;//-1:没人鹰,1:X赢,0:O赢 //读入矩阵 for(i=0;i<size;i++) { for(j=0;j<size;j++) { scanf("%d",&board[i][j]); } } //检查行 for(i=0;i<size && result == -1;i++) { num0fO=num0fX=0; for(j=0;j<size;j++) { if(board[i][j]==1) { num0fX++; } else { num0fO++; } } if(num0fO==size) { result=0; } else if(num0fX==size) { result=1; } } if(result=-1) { for(j=0;j<size && result==-1;j++) { num0fO=num0fX=0; for(i=0;i<size;i++) { if(board[i][j]==1) { num0fX++; } else { num0fO++; } } if(num0fO==size) { result=0; } else if(num0fX==size) { result=1; } } } num0fO=num0fX=0; for(i=0;i<size;i++) { if(board[i][i]==1) { num0fX++; } else { num0fO++; } } if(num0fO==size) { result=0; } else if(num0fX==size) { result=1; num0fO=num0fX=0; for(i=0;i<size;i++) { if(board[i][size-i-1]==1) { num0fX++; } else { num0fO++; } } } }
三.指针
3.1 指针变量就是记录地址的变量
scanf 如果能够取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量? scanf("%d",&i); scanf()的原型应该是怎么样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?
指针就是保存地址的变量 int i; int* p = &i; int* p,1;(与下面的定义一样) int *p,q;
变量的值是内存的地址 普通变量的值是实际的值 指针变量的值是具有实际值的变量的地址
作为参数的指针
void f(int *p); 在被调用的时候得到了某个变量的地址 int i=0; f(&i); 在函数里面可以通过整个指针访问外面这个i
#include<stdio.h> void f(int *p); int main(void) { int i=6; printf("&i=%p\n",&i); f(&i); return 0; } void f(int *p) { printf("p=%p\n",p); }
访问那个地址上的变量* *是一个单目运算符,用来访问指针的值所表示的地址上的变量 可以做右值也可以做左值 int k=*p; *p=k+1;
#include<stdio.h> void f(int *p); int main(void) { int i=6; printf("&i=%p\n",&i); f(&i); return 0; } void f(int *p) { printf("p=%p\n",p); printf("*p=%d\n",*p); }
#include<stdio.h> void f(int *p); void g(int k); int main(void) { int i=6; printf("&i=%p\n",&i); f(&i); g(i); return 0; } void f(int *p) { printf("p=%p\n",p); printf("*p=%d\n",*p); *p=8; } void g(int k) { printf("k=%d\n",k); }
左值之所以叫左值 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果: a[0]=2; *p=3; 是特殊的值,所以叫做左值
指针的运算符&*
互相反作用 *&yptr -> *(&yptr) -> *(yptr的地址) -> 得到那个地址上的变量 ->yptr &*yptr ->&(*yptr) -> &(y) ->得到y的地址,也就是yptr ->yptr
3.2 指针的使用:指针有什么用呢?
指针应用场景一
3.2.1 交换两个变量的值
注意:指针传的参数大多数是地址 &变量
void swap(int *pa,int *pb) { int t= *pa; *pa=*pb; *pb=t; }
#include<stdio.h> void swap(int *pa,int *pb); int main(void) { int a=5; int b=6; swap(&a,&b); printf("a=%d,b=%d\n",a,b); return 0; } void swap(int *pa,int *pb) { int t=*pa; *pa=*pb; *pb=t; }
3.2.2 函数返回多个值,某些值就只能通过指针返回,传入的参数实际上是需要保存带回的结果的变量
#include<stdio.h> void minmax(int a[],int len,int *max,int *min); int main(void) { int a[]={23,56,434,12,3,5,88,90,234,334,45,66,334,7,18,}; int min,max; minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); printf("min=%d,max=%d\n",min,max); return 0; } void minmax(int a[],int len,int *min,int *max) { int i; *min=*max=a[0]; for(i=1;i<len;i++) { if(a[i]<*min) { *min=a[i]; } if(a[i]>*max) { *max=a[i]; } } }
3.3.3 函数返回运算的状态,结果通过指针返回
常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错: -1或0(在文件操作会看到大量的例子) 但是当任何数值都是有效的可能结果时,就得分开返回了
#include<stdio.h> /** @return 如果除法成功,返回1;否则返回0 */ int divide(int a,int b,int *result); int main(void) { int a=5; int b=2; int c; if(divide(a,b,&c)) { printf("%d/%d=%d\n",a,b,c); } return 0; } int divide(int a,int b,int *result) { int ret=1; if (b==0) ret =0; else{ *result=a/b; } return ret; }
指针最常见的错误
定义了指针变量,还没有指向任何变量,就开始使用指针
3.3 指针与数组:为什么数组传进函数后的sizeof不对了?
#include<stdio.h> void minmax(int a[],int len,int *max,int *min); int main(void) { int a[]={23,56,434,12,3,5,88,90,234,334,45,66,334,7,18,}; int min,max; printf("main sizeof(a)=%lu\n",sizeof(a)); minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); printf("min=%d,max=%d\n",min,max); return 0; } void minmax(int a[],int len,int *min,int *max) { int i; printf("minmax sizeof(a)=%lu\n",sizeof(a)); *min=*max=a[0]; for(i=1;i<len;i++) { if(a[i]<*min) { *min=a[i]; } if(a[i]>*max) { *max=a[i]; } } }
#include<stdio.h> void minmax(int a[],int len,int *max,int *min); int main(void) { int a[]={23,56,434,12,3,5,88,90,234,334,45,66,334,7,18,}; int min,max; printf("main sizeof(a)=%lu\n",sizeof(a)); printf("main a=%p\n",a); minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); printf("min=%d,max=%d\n",min,max); return 0; } void minmax(int a[],int len,int *min,int *max) { int i; printf("minmax sizeof(a)=%lu\n",sizeof(a)); printf("minmax a=%p\n",a); *min=*max=a[0]; for(i=1;i<len;i++) { if(a[i]<*min) { *min=a[i]; } if(a[i]>*max) { *max=a[i]; } } }
#include<stdio.h> void minmax(int *a,int len,int *max,int *min); int main(void) { int a[]={23,56,434,12,3,5,88,90,234,334,45,66,334,7,18,}; int min,max; printf("main sizeof(a)=%lu\n",sizeof(a)); printf("main a=%p\n",a); minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); printf("min=%d,max=%d\n",min,max); return 0; } void minmax(int a[],int len,int *min,int *max) { int i; printf("minmax sizeof(a)=%lu\n",sizeof(a)); printf("minmax a=%p\n",a); *min=*max=a[0]; for(i=1;i<len;i++) { if(a[i]<*min) { *min=a[i]; } if(a[i]>*max) { *max=a[i]; } } }
函数参数表中的数组实际上是指针 sizeof(a)==sizeof(int *) 但是可以用数组的运算符[]进行运算
数组参数
以下四种函数原型是等价的: int sum(int *ar,int n); int sum(int *,int); int sum(int ar[],int n); int sum(int [],int);
数组变量是特殊的指针
数组变量本身表达地址,所以 int a[10];int *p=a; //无需用&取地址 但是数组的单元表达的是变量,需要用&取地址 a==&a[0] []运算符可以对数组做,也可以对指针做; p[0]<==>a[0]
#include<stdio.h> void minmax(int a[],int len,int *max,int *min); int main(void) { int a[]={23,56,434,12,3,5,88,90,234,334,45,66,334,7,18,}; int min,max; printf("main sizeof(a)=%lu\n",sizeof(a)); printf("main a=%p\n",a); minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); printf("min=%d,max=%d\n",min,max); int *p=&min; printf("*p=%d\n",*p); printf("p[0]=%d\n",p[0]); return 0; } void minmax(int a[],int len,int *min,int *max) { int i; printf("minmax sizeof(a)=%lu\n",sizeof(a)); printf("minmax a=%p\n",a); *min=*max=a[0]; for(i=1;i<len;i++) { if(a[i]<*min) { *min=a[i]; } if(a[i]>*max) { *max=a[i]; } } }
运算符可以对指针做,也可以对数组做; *a=25
#include<stdio.h> void minmax(int a[],int len,int *max,int *min); int main(void) { int a[]={23,56,434,12,3,5,88,90,234,334,45,66,334,7,18,}; int min,max; printf("main sizeof(a)=%lu\n",sizeof(a)); printf("main a=%p\n",a); minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); printf("min=%d,max=%d\n",min,max); int *p=&min; printf("*p=%d\n",*p); printf("p[0]=%d\n",p[0]); printf("*a=%d\n",*a); return 0; } void minmax(int a[],int len,int *min,int *max) { int i; printf("minmax sizeof(a)=%lu\n",sizeof(a)); printf("minmax a=%p\n",a); *min=*max=a[0]; for(i=1;i<len;i++) { if(a[i]<*min) { *min=a[i]; } if(a[i]>*max) { *max=a[i]; } } }
数组变量是const的指针,所以不能被赋值 int a[]<==>int * const a=....
3.4 指针与const:指针本身和所指的变量都可能const
只适用于C99
指针是const
表示一旦得到了某个变量的地址,不能再指向其他变量 int *const q=&i;//q是const *q=26;//OK q++;//ERROR
所指是const
表示不能通过这个指针去修改那个变量(并不能使得那个变量称为const) const int *p=&i; *p=26;//ERROR!(*P)是const i=26;//OK p=&j;//OK
判断哪个被constl的标志是const在*的前面还是后面
保护数组值
因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值 为了保护数组不被函数破坏,可以设置参数为const int sum(const int a[],int length);
3.5 指针运算
#include<stdio.h> int main(void) { char ac[]={0,1,2,3,4,5,6,7,8,9,}; char *p=ac; printf("p =%p\n",p); printf("p+1 =%p\n",p+1); //sizeof(char)=1;sizeof(int)=4 int ai[]={0,1,2,3,4,5,6,7,8,9,}; int *q=ai; printf("q =%p\n",q); printf("q+1 =%p\n",q+1); return 0; }
指针+1等于指针+sizeof(类型)
#include<stdio.h> int main(void) { char ac[]={0,1,2,3,4,5,6,7,8,9,}; char *p=ac; printf("p =%p\n",p); printf("p+1 =%p\n",p+1); printf("*(p+1) =%d\n",*(p+1)); //sizeof(char)=1;sizeof(int)=4 int ai[]={0,1,2,3,4,5,6,7,8,9,}; int *q=ai; printf("q =%p\n",q); printf("q+1 =%p\n",q+1); printf("*(q+1) =%d\n",*(q+1)); return 0; }
如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义
这些算数运算可以对指针做: 给指针加、减一个整数(+,+=,-,-=) 递增递减(++/--) 两个指针相减
#include<stdio.h> int main(void) { char ac[]={0,1,2,3,4,5,6,7,8,9,}; char *p=ac; char *p1=&ac[6]; printf("p1-p=%d\n",p1-p); printf("p =%p\n",p); printf("p+1 =%p\n",p+1); printf("*(p+1) =%d\n",*(p+1)); //sizeof(char)=1;sizeof(int)=4 int ai[]={0,1,2,3,4,5,6,7,8,9,}; int *q=ai; int *q1=&ai[6]; printf("q1-q=%d\n",q1-q); printf("q =%p\n",q); printf("q+1 =%p\n",q+1); printf("*(q+1) =%d\n",*(q+1)); return 0; }
*p++ 取出p所指的那个数据来,完事之后顺便把p移动下一个位置去 *的优先级虽然高,但是没有++高 常用于数组类的连续空间操作 在某些CPU上,这可以直接被翻译成一条汇编指令
#include<stdio.h> int main(void) { char ac[]={0,1,2,3,4,5,6,7,8,9,-1}; char *p=ac; while(*p !=-1) { printf("%d\n",*p++); } int ai[]={0,1,2,3,4,5,6,7,8,9,}; int *q=ai; return 0; }
指针比较
<,<=,==,>,>=,!=都可以对指针做 比较它们在内存中的地址 数组中的单元的地址肯定是线性递增的
0地址
当然你的内存中有0地址,但是0地址通常是个不能随便的地址 所以你的指针不应该具有0值 因此可以用0地址来表示特殊的事情: 返回的指针是无效的 指针没有被真正初始化(先初始化为0) NULL是一个预定定义的符号,表示0地址 有的编译器不愿意你用0来表示0地址
指针的类型
无论指向什么类型,所有的指针的大小都是一样的,因为都是地址 但是指向不同类型的指针是不能直接互相赋值的 这是为了避免用错指针
指针类型的转换
void*表示不知道指向什么东西的指针 计算时与char*相同(但不相通) 指针也可以转换类型 int *p=&i;void *q=(void*)p 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量 我不再当你是int啦,我认为你就是个void
3.6 动态内存分配
输入数据 如果输入数据时,先告诉你个数,然后再输入,要记录每个数据 C99可以用变量做数组定义的大小,C99之前呢? int *a=(int*)malloc(n*sizeof(int));
#include<stdio.h> #include<stdlib.h> int main(void) { int number; int *a; int i; printf("输入数量:"); scanf("%d",&number); a=(int*)malloc(number*sizeof(int)); for(i=0;i<number;i++) { scanf("%d",&a[i]); } for(i=number-1;i>=0;i--) { printf("%d ",a[i]); } free(a); return 0; }
malloc
#include<stdlib.h> void* malloc(size_t size); 向malloc申请的空间的大小是以字节为单位的 返回的结果是void*,需要类型转换为自己需要的类型 (int*)malloc(n*sizeof(int))
没空间了?
如果申请失败则返回0,或者叫做NULL 你的系统能给你多大的空间?
#include<stdio.h> #include<stdlib.h> int main(void) { void *p; int cnt=0; while((p=malloc(100*1024*1024))) { cnt++; } printf("分配了%d00MB的空间\n",cnt); return 0; }
free()
把申请得来的空间还给“系统” 申请过的空间,最终都应该要还 混出来的,迟早都是要还的 只能还申请来的空间的首地址
#include<stdio.h> #include<stdlib.h> int main(void) { void *p; int cnt=0; p=malloc(100*1024*1024); p++; free(p); return 0; }
#include<stdio.h> #include<stdlib.h> int main(void) { int i; void *p; int cnt=0; //p=malloc(100*1024*1024); //p++; p=&i; free(p); return 0; }
好习惯,在给指针初始化时就要给指针赋值
#include<stdio.h> #include<stdlib.h> int main(void) { void *p=0; int cnt=0; free(p); return 0; }
常见的问题
申请了没free->长时间运行内存逐渐下降 新手:忘了 老手:找不到合适的free的时间 free过了再free 地址变过了,直接去free
四.字符串
4.1初始字符串
字符数组 char word[]={'H','e','l','l','o','!'}; 字符串 char word[]={'H','e','l','l','o','!','\0'};
字符串 以0(整数0)结尾的一串字符 0或'\0'是一样的,但是和‘0’不同 0标志字符串的结束,但它不是字符串的一部分 计算字符串长度的时候不包含这个0 字符串以数组的形式存在,以数组或指针的形式访问 更多的是以指针的形式 string.h里有很多处理字符串的函数
字符串变量 char *str="Hello"; char word[]="Hello"; char line[10]="Hello";
字符串常量 “Hello” “Hello”会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0 两个相邻的字符串常量会被自动连接起来
字符串 C语言的字符串是以字符串数组的形态存在的 不能用运算符对字符串做运算 通过数组的方式可以遍历字符串 唯一特殊的地方是字符串字面量可以用来初始化字符串变量可以用来初始化字符数组 以及标准库提供了一系列字符串函数
4.2字符串变量
#include<stdio.h> #include<stdlib.h> int main(void) { char *s="Hello World"; char *s2="Hello World"; printf("s=%p\n",s); printf("s2=%p\n",s2); printf("Here!s[0]=%c\n",s[0]); return 0; }
char* s="Hello,world!"; s是一个指针,初始化为指向一个字符串常量 由于这个常量所在的地方,所以实际上是const char* s,但是由于历史的原因,编译器接受不带const的写法 但是试图对s所指的字符串做写入会导致严重的后果 如果需要修改字符串,应该用数组: char s[]="Hello,world!";
#include<stdio.h> #include<stdlib.h> int main(void) { int i=0; char *s="Hello World"; char *s2="Hello World"; char s3[]="Hello World"; printf("&i=%p\n",&i); printf("s=%p\n",s); printf("s2=%p\n",s2); printf("s3=%p\n",s3); s3[0]='B'; printf("Here!s3[0]=%c\n",s3[0]); return 0; }
指针还是数组?
char *str="Hello"; char word[]="Hello"; 数组:这个字符串在这里 作为本地变量空间自动被回收 指针:这个字符串不知道在哪里 处理参数 动态分配空间 如果要构造一个字符串->数组 如果要处理一个字符串->指针
char*是字符串
字符串可以表达为char*的形式 char*不一定是字符串 本意是指向字符的指针,可能指向的是字符的数组(就像int*一样) 只有它所指的字符数组有结尾的0,才能说它所指的是字符串
4.3 字符串输入输出
字符串赋值?
char *t ="title"; char *s; s=t; 并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t所做的
字符串输入输出
char string[8]; scanf("%s",string); printf("%s",string);
#include<stdio.h> int main(void) { char word[8]; scanf("%s",word); printf("%s##\n",word); return 0; }
#include<stdio.h> int main(void) { char word[8]; char word2[8]; scanf("%s",word); scanf("%s",word2); printf("%s##%s##\n",word,word2); return 0; }
scanf读入一个单词(到空格、tab或回车为止) scanf是不安全的,因为不知道要读入的内容的长度
#include<stdio.h> void f(void) { char word[8]; char word2[8]; scanf("%7s",word); scanf("%7s",word2); printf("%s##%s##\n",word,word2); } int main(void) { f(); return 0; }
常见错误
char *string scanf("%s",string); 以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了 由于没有对string初始化为0,所以不一定每次运行都出错
空字符串
char buffer[100]=""; 这是一个空的字符串,buffer[0]=='\0' char buffer[]=""; 这个数组的长度只有1!
4.4字符串数组,以及程序参数
4.4.1 字符串数组
char **a a是一个指针,指向另一个指针,那个指针指向一个字符(串) char a[][]
4.4.2 程序参数
int main(int argc,char const *argv[]) argv[0]是命令本身 当使用Unix的符号链接时,反映符号链接的名字
#include<stdio.h> int main(int argc.char const *argv[]) { int i; for(i=0;i<argc;i++) { printf("%d:%s\n",i,argv[i]); } return 0; }
4.5 单字符输入输出,用putchar和getchar
4.5.1 putchar
int putchar(int c); 向标准输出写一个字符 返回写了几个字符,EOF(-1)表示写失败
4.5.2 getchar
int getchar(void); 从标准输入读入一个字符 返回类型时int是为了返回EOF(-1) Windows->Ctrl-Z Unix->Ctrl-D
#include<stdio.h> int main(int argc,char const *argv[]) { int ch; while((ch=getchar())!=EOF) { putchar(ch); } printf("EOF\n"); return 0; }
4.6 字符串函数strlen
strlen size_t strlen(const char *s); 返回s的字符串长度(不包括结尾的0)
#include<stdio.h> #include<string.h> int main(int argc,char const *argv[]) { char line[]="Hello"; printf("strlen=%lu\n",strlen(line)); printf("sizeof=%lu\n",sizeof(line)); return 0; }
#include<stdio.h> #include<string.h> size_t mylen(const char *s) { int cnt=0; int idx=0; while(s[idx]!='\0') { idx++; cnt++; } return cnt; } int main(int argc,char const *argv[]) { char line[]="Hello"; printf("strlen=%lu\n",mylen(line)); printf("sizeof=%lu\n",sizeof(line)); return 0; }
4.7 字符串函数strcmp
int strcmp(const char *s1,const char *s2); 比较两个字符串,返回: 0:s1==s2 1:s1>s2 -1:s1<s2
#include<stdio.h> #include<string.h> int main(int argc,char const *argv[]) { char s1[]="abc"; char s2[]="abc"; printf("%d\n",strcmp(s1,s2)); return 0; }
s1==s2(比较的是两个变量的地址)
#include<stdio.h> #include<string.h> int main(int argc,char const *argv[]) { char s1[]="abc"; char s2[]="bbc"; printf("%d\n",strcmp(s1,s2)); return 0; }
如果不相等,结果为两个字符串的插值
#include<stdio.h> #include<string.h> int main(int argc,char const *argv[]) { char s1[]="abc"; char s2[]="Abc"; printf("%d\n",strcmp(s1,s2)); return 0; }
#include<stdio.h> #include<string.h> int mycmp(const char *s1,const char *s2) { int idx=0; while(s1[idx]==s2[idx]&& s1[idx]!='\0') { idx++; } return s1[idx] -s2[idx]; } int main(int argc,char const *argv[]) { char s1[]="abc"; char s2[]="abc "; printf("%d\n",strcmp(s1,s2)); return 0; }
#include<stdio.h> #include<string.h> int mycmp(const char *s1,const char *s2) { while(*s1==*s2&& *s1!='\0') { s1++; s2++; } return *s1-*s2; } int main(int argc,char const *argv[]) { char s1[]="abc"; char s2[]="abc "; printf("%d\n",strcmp(s1,s2)); return 0; }
4.8 字符串函数 strcpy
char *strcpy(char *restrict dst,const char *restrictsrc); 把src的字符串拷贝到dst restrict表明src和dst不重叠(C99) 返回dst 为了能链起代码来
复制一个字符串
char *dst=(char*)malloc(strlen(src)+1); strcpy(dst,src)
#include<stdio.h> #include<string.h> char *mycpy(char *dst,const char *src) { int index=0; while(src[idx]) { dst[idx]=src[idx]; idx++; } dst[idx]='\0'; return dst; } int main(int argc,char const *argv[]) { char s1[]="abc"; char s2[]="abc"; strcpy(s1,s2); return 0; }
#include<stdio.h> #include<string.h> char *mycpy(char *dst,const char *src) { char *ret=dst; while(*src) { *dst++=*src++; } *dst='\0'; return ret; } int main(int argc,char const *argv[]) { char s1[]="abc"; char s2[]="abc"; strcpy(s1,s2); return 0; }
4.9 字符串函数strcat
char *strcat(char *restrict s1,const char *restrict s2); 把s2拷贝到s1的后面,接成一个长的字符串 返回s1 s1必须具有足够的空间
4.10 字符串中找字符
char *strchr(const char *s,int c); char *strrchr(const char *s,int c); 返回NULL表示没有找到 如何寻找第2个?
#include<stdio.h> #include<string.h> int main(int argc,char const *argv[]) { char s[]="hello"; char *p=strchr(s,'l'); p=strchr(p+1,'l'); printf("%s\n",p); return 0; }
#include<stdio.h> #include<string.h> int main(int argc,char const *argv[]) { char s[]="hello"; char *p=strchr(s,'l'); char *t=(char*)malloc(strlen(p)+1); strcpy(t,p); p=strchr(p+1,'l'); printf("%s\n",t); free(t); return 0; }
#include<stdio.h> #include<string.h> int main(int argc,char const *argv[]) { char s[]="hello"; char *p=strchr(s,'l'); char c=*p; *p='\0'; char *t=(char*)malloc(strlen(s)+1); strcpy(t,s); printf("%s\n",t); free(t); return 0; }
字符串中找字符串
char * strstr(const char *s1,const char *s2); char *strcasestr(const char *s1,const char *s2);
五.枚举
#include<stdio.h> enum COLOR {RED,YELLOW,GREEN}; int main(int argc,char const *argv[]) { int color =-1; char *colorName=NULL; printf("输入你喜欢的颜色的代码:"); scanf("%d",&color); switch(color) { case RED:colorName="red";break; case YELLOW:colorName="yellow";break; case GREEN:colorName="green";break; default:colorName="unknown";break; } printf("你喜欢的颜色是%s\n",colorName); return 0; }
用枚举而不是定义独立的const int变量
枚举 枚举是一种用户定义的数据类型,它用关键字enum以如下语法来声明: enum 枚举类型名字{名字0,...,名字n}; 枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是常量符号,它们的类型是int,值则依次从0到n 如:enum colors{red,yellow,green}; 就创建了三个常量,red的值是0,yellow是1,而green是2 当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字
//自动计数的枚举 #include<stdio.h> enum COLOR {RED,YELLOW,GREEN,NumCOLORS}; int main(int argc,char const *argv[]) { int color =-1; char *ColorNames[NumCOLORS]={ "red","yellow","green", }; char *colorName=NULL; printf("请输入你喜欢的颜色的代码:"); scanf("%d",&color); if(color>=0 &&color<NumCOLORS) { colorName=ColorNames[color]; } else { colorName="unknown"; } printf("你喜欢的颜色是%s\n",colorName); return 0; }
这样需要遍历所有的枚举量或者需要建立一个用枚举量做下标的数组的时候就很方便了
声明枚举量的时候可以指定值 enum COLOR{RED=1,YELLOW,GREEN=5};
#include<stdio.h> enum COLOR {RED=1,YELLOW,GREEN=5,a}; int main(int argc,char const *argv[]) { printf("code for GRREN is %d\n",GREEN); return 0; }
枚举 虽然枚举类型可以当作类型使用,但是实际上很少用 如果有意义上排比的名字,用枚举比const int方便 枚举比宏(macro)好,因为枚举类型有int类型
六.结构体
6.1结构类型
#include<stdio.h> int main(int argc,char const *argv[]) { struct date{ int month; int day; int year; }; struct date today; today.month=9; today.day=26; today.year=2022; printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day); return 0; }
和本地变量一样,在函数内部声明的结构类型只能在函数内部使用 所以通常在函数外部声明结构类型,这样就可以被多个函数所使用了
#include<stdio.h> struct date{ int month; int day; int year; }; int main(int argc,char const *argv[]) { struct date today; today.month=9; today.day=26; today.year=2022; printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day); return 0; }
声明结构的形式 1) struct point{ int x; int y; }; struct point p1,p2; p1和p2都是point里面有x和y的值 2) struct{ int x; int y; } p1,p2; p1和p2都是一种无名结构,里面有x和y 3) struct point { int x; int y; } p1,p2; p1和p2都是point里面有x和y的值
结构的初始化
#include<stdio.h> struct date{ int month; int day; int year; }; int main(int argc,char const *argv[]) { struct date today={07,31,2014}; struct date thismonth={ .month=7,.year=2014 }; printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day); printf("This month is %i-%i-%i.\n",thismonth.year,thismonth.month,thismonth.day); return 0; }
结构成员
结构和数组有点像 数组用[]运算符和下标访问其成员 a[0]=10; 结构用.运算符和名字访问其成员 today.day student.fristName p1.x p1.y
结构运算
要访问整个结构,直接用结构变量的名字 对于整个结构,可以做赋值,取地址,也可以传递给函数参数 p1=(struct point){5,10};相当于p1.x=5;p1.y=10; p1=p2;//相当于p1.x=p2.x;p1.y=p2.y;
#include<stdio.h> struct date{ int month; int day; int year; }; int main(int argc,char const *argv[]) { struct date today={07,31,2014}; struct date thismonth={ .month=7,.year=2014 }; struct date day; day=today; printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day); printf("day's date is %i-%i-%i.\n",day.year,day.month,day.day); printf("This month is %i-%i-%i.\n",thismonth.year,thismonth.month,thismonth.day); return 0; }
每一个类型定义出来的变量都不相同
#include<stdio.h> struct date{ int month; int day; int year; }; int main(int argc,char const *argv[]) { struct date today={07,31,2014}; struct date thismonth={ .month=7,.year=2014 }; struct date day; day=today; day.year=2015; printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day); printf("day's date is %i-%i-%i.\n",day.year,day.month,day.day); printf("This month is %i-%i-%i.\n",thismonth.year,thismonth.month,thismonth.day); return 0; }
结构指针
和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符 struct date *pDate=&today;
#include<stdio.h> struct date{ int month; int day; int year; }; int main(int argc,char const *argv[]) { struct date today={07,31,2014}; struct date thismonth={ .month=7,.year=2014 }; struct date day; struct date *pDate=&today; day=today; day.year=2015; printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day); printf("day's date is %i-%i-%i.\n",day.year,day.month,day.day); printf("This month is %i-%i-%i.\n",thismonth.year,thismonth.month,thismonth.day); printf("address of today is %p\n",pDate); return 0; }
6.2 结构与函数
结构作为函数参数
int numberOfDays(struct date d) 整个结构可以作为参数的值传入函数 这时候是在函数内新建一个结构变量,并复制调用者的结构的值 也可以返回一个结构 这与数组完全不同
#include<stdio.h> #include<stdbool.h> struct date{ int month; int day; int year; }; bool isLeap(struct date d); int numberOfDays(struct date d); int main(int argc,char const *argv[]) { struct date today,tomorrow; printf("Enter today's date(mm dd yyyy):"); scanf("%i %i %i",&today.month,&today.day,&today.year); if(today.day!=numberOfDays(today)) { tomorrow.day=today.day+1; tomorrow.month=today.month; tomorrow.year=today.year; } else if(today.month==12) { tomorrow.day=1; tomorrow.month=1; tomorrow.year=today.year+1; } else { tomorrow.day=1; tomorrow.month=today.month+1; tomorrow.year=today.year; } printf("Tomorrow's date is %i-%i-%i.\n", tomorrow.year,tomorrow.month,tomorrow.day); return 0; } int numberOfDays(struct date d) { int days; const int daysPerMonth[12]={31,28,31,30,31,30,31,31,30,31,30,31}; if(d.month==2&&isLeap(d)) days=daysPerMonth[d.month-1]; return days; } bool isLeap(struct date d) { bool leap=false; if((d.year%4==0&&d.year%100!=0)||d.year%400==0) leap=true; return leap; }
输入结构
没有直接的方式可以一次scanf一个结构, 如果写函数时,传入的函数是临时变量不会影响外面的变量
结构指针作为参数
指向结构的指针 struct date { int month; int day; int year; }myday; struct date *p=&myday; (*p).month=12; p->month=12; 用->表示指针所指的结构变量中的成员
6.3 结构中的结构
结构数组
struct date dates[100]; struct date dates[]={ {4,5,2005},{2,4,2005} };
结构中的结构
struct dateAndTime{ struct date sdate; struct time stime; }
结构中的结构的数组
6.4 类型定义
自定义数据类型(typedef)
c语言提供了一个叫做typedef的功能来声明一个已有的数据类型的新名字。比如: typedef int Length 使得Length 成为int类型的别名。 这样,Length这个名字就可以代替int 出现在变量定义和参数声明的地方了: Length a,b,len; Length numbers[10];
Typedef
声明新的类型的名字 新的名字是某种类型的别名 改善了程序的可读性
typedef long int64_t; typedef struct ADate { int month; int day; int year; }Date; int 64_t i =100000000; Date d={9,1,2005};