(1)指针与二维数组
一个数组的名字代表该数组的的首地址,是地址常量(作为形式参数的数组名除外),这一规定对二维数组或更高维数组同样适用。
在c语言中定义的任何一个二维数组实际上都可以看做是一个一维数组,该一维数组中的每一个成员又是一个一维数组。
若有定义int d[3][4],i,j;且0<=i<=2,0<=j<=3;
1.数组元素d[i][j]的表示方法为:
d[i][j]
*(d[i]+j)
*(*(d+i)+j)
(*(d+i))[j]
*(&d[0][0]+4*i*j)
*(d[0]+4*i+j)
2.数组元素d[i][j]地址的表示方法为:
&d[i][j]
d[i]+j
*(d+i)+j
&((*(d+i))[j])
&d[0][0]+4*i*j
d[0]+4*i*j
例如:设数组d的首地址是无符号十进制整数40000,则各表示形式、含义与地址的关系如下所示。其中地址值增减变化随程序运行环境的不同而不同
二维数组d各表示形式、含义与地址的关系
d 二维数组名,数组首地址,第0行首地址 40000 d[0],*(d+0),*d 第0行第0列元素d[0][0]的地址 40000 d+1 第1行首地址 40008 d[1],*(d+1) 第1行第0列元素d[1][0]的地址 40008 d[1]+2,*d[+1]+2,&d[1][2] 第1行第2列元素d[1][2]地址 40012
二维数组名d是指向行的,例如d+2表示二维数组d第2行的首地址;一维数组名d[0],d[1],d[2]是指向各自对应的列元素的,例如d[0]+2表示元素d[0]+2表示元素d[0][2]的地址。*d与*(d+1)都不代表任何数组元素。
例子:将a矩阵与b矩阵相加,所得之和存入c矩阵中。a,b,c矩阵都是3*4的矩阵
#include<stdio.h> #include<stdlib.h> #include<time.h> int i,j; void maxtrix(int *x,int *y,int *z){ for(i=0;i<3;i++){ for(j=0;j<4;j++){ *(z+i*4+j)=*(x+i*4+j)+*(y+i*4+j); } } } int main(){ srand((int)time(NULL)); int (*p)[3][4],a[3][4],b[3][4],c[3][4]; printf("The value of a:\n"); for(i=0;i<3;i++){ for(j=0;j<4;j++){ *(a[i]+j)=(int)rand%1000; } } printf("the value of b:\n"); for(i=0;i<3;i++){ for(j=0;j<4;j++){ *(*(b+i)+j)=(int)rand%1000; } maxtrix(*a,b[0],&c[0][0]); } printf("The value of c:"); printf("%d",a[2][1]); return 0; }
这个程序运行时会出现错误,以后再讨论。
3.指针数组
如果一个数组的元素都是指针变量,则称这个数组是指针数组。
指针数组定义的一般形式:
类型说明符 *数组名[]......
4.指针与字符串数组
字符数组中每个元素都是一个字符,而字符串数组中指的是数组中的每个成员都是存放字符串的数组。
1)直接给字符串数组赋初值
char b[4][8]={"Turbo C","FORTRAN","BASIC","Foxpro"};
char b[][8]={"Turbo C","FORTRAN","BASIC","Foxpro"};
2)用给字符型指针数组赋初值的方式构成字符串数组
char *f[4]={"Turbo C","FORTRAN","BASIC","Foxpro"};
char *f[]={"Turbo C","FORTRAN","BASIC","Foxpro"};
实例:利用数字月份查找其英文月份名:
1 #include<stdio.h> 2 3 char *month_name(int n){ 4 char *name[]={ 5 "Illegal month", 6 "January","February","March", 7 "April","May","June","July", 8 "August","September","October", 9 "November","December"}; 10 return (n<1|n>12)?name[0]:name[n]; 11 } 12 13 int main(){ 14 int n; 15 scanf("%d",&n); 16 printf("%d month name is %s\n",n,month_name(n)); 17 return 0; 18 }
5.指向数组的指针变量
指向数组的指针变量定义的一般形式:
类型说明符 (*变量名)[正整型常量表达式];
功能:定义一个名为"变量名"的指针变量,该指针变量所指向的是一个具有"正整型常量表达式"个元素的一维数组,该数组的每个元素都是"类型说明符"类型的。
例如:int (*p)[10],定义p是一个指针变量,它所指向的对象是一个具有10个元素的数组int型数组。
这种形式定义的指针一般用于指向二维的数组。
例如:int d[3][2],(*p)[2];p=d;
p=d;//p指向第0行,即指向整个数组d[0][0],d[0][1],d[0][2]......;
p++;//p指向第1行,即指向整个数组d[1][0],d[1][1],d[1][2]......;
(2) 二级指针
如果一个变量的值是其他变量的地址,而这些其他变量的值不再是内存地址,则这个变量是一级指针变量;
如果一个变量的值是一级指针的地址,则称这个变量是二级指针变量。
定义方法:
类型说明符 **标识符
int b,*m,**p;
m=&b;
p=&m;
实例1:二级指针输出整型数据:
1 #include<stdio.h> 2 3 void swap(int **m,int **n){ 4 int *i; 5 i=*m; 6 *m=*n; 7 *n=i; 8 } 9 int main(){ 10 int a,b,*pa,*pb; 11 pa=&a; 12 pb=&b; 13 scanf("a=%d,b=%d",pa,pb); 14 swap(&pa,&pb); 15 printf("pa=%d,pb=%d\n",*pa,*pb); 16 printf("a=%d,b=%d",a,b); 17 return 0; 18 }
这里输入时要注意一个问题,就是输入时,要满足scanf语句中的格式,必须进行格式的完全匹配。
实例2:二级指针输出字符串型数据
1 #include<stdio.h> 2 3 int main(){ 4 char **p,*address[]={ 5 "China","Japan","English","" 6 }; 7 p=address; 8 for(;**p!=‘\0‘;){ 9 printf("%s\n",*p++); 10 } 11 return 0; 12 }
实例3:指针数组(区别于指向数组的指针)
1 #include<stdio.h> 2 3 int main(){ 4 int x[5]={ 5 2,4,6,8,10 6 }; 7 int *y[5]; 8 int **a,b,i; 9 for(i=0;i<5;i++){ 10 y[i]=&x[i]; 11 } 12 a=y; 13 for(b=4;b>=0;b--){ 14 printf("%3d",**a); 15 a++; 16 } 17 printf("\n"); 18 return 0; 19 }
本程序还可以写成下面的形式:
1 #include<stdio.h> 2 void s(int **a){ 3 int b; 4 for(b=0;b<5;b++){ 5 printf("%3d",**a); 6 a++; 7 } 8 printf("\n"); 9 } 10 int main(){ 11 int x[5]={ 12 2,4,6,8,10 13 }; 14 int *y[5],i; 15 for(i=0;i<5;i++){ 16 y[i]=&x[i]; 17 } 18 s(y); 19 return 0; 20 }
本例用二级指针变量输出了数组各元素的值。指针数组名作为函数实参时,形参得到的是实参指针数组的地址,这是一个二级指针。因此,形参可以写成int **a形式,也可以写成int *a[]的形式,两种方法等价。
(3)内存空间的动态分配
1.指向void类型的指针
void 类型是一种抽象的数据类型,如果用指针的void类型说明符定义一个指针变量,则该指针变量并没有被确定存储具体哪一种数据类型变量的地址。
void类型的指针和其他类型的指针可以相互赋值,且不必强制类型转换。
指向任何类型的指针都可以转换为指向void类型,如果将结果再转换为初始指针类型,则可以恢复初始指针类型并不会丢失信息。
1 #include<stdio.h> 2 3 int main(){ 4 float a[4]={ 5 1,2,3,5 6 },*p1,*p3; 7 //测试void型指针,进行连续的类型转换 8 void *p2=a; 9 p1=(float *)p2; 10 p2=(void *)p1; 11 12 p3=&a[2]; 13 p2=p3; 14 p3=(float *)p2; 15 printf("a=%p,p1=%p,p2=%p,p3=%p,*p3=%f",a,p1,p2,p3,*p3); 16 printf("\n"); 17 return 0; 18 }
将void型指针赋值给其他类型时,仍然需要强制类型转换。如上述14行所示。
从运行的结果可以看出,指向void的类型的指针起到了通用指针的作用。
2.常用内存管理函数
在此之前编写的程序,在程序运行之前所需要的变量数或者数组的大小就已经确定了。
C语言提供了一些内存管理函数,这些内存管理函数可以在程序运行期间分配内存空间,即可动态的分配内存空间,也可以将已经分配的内存空间释放。常用的内存管理函数有calloc,malloc,free函数。
1)calloc函数的功能是向系统申请分配连续的内存空间,如果申请获得成功,则把所分配内存区域的首地址作为函数返回值返回,该函数返回的是void类型的指针;如果申请获得成功,则函数返回空指针。原型:void *calloc(unsigned n,unsigned size);
int *p;
p=calloc(20,sizeof(int));
该程序段的功能是申请一块连续的能保存20个int型数据的内存区域,并使p指向该块内存区域。
2)malloc的功能与calloc函数类似,malloc函数用于向系统申请分配一块连续的size个字节的内存区域。
函数的原型:void *malloc(unsigned size);
3)free函数的原型是void free(void *p);其功能是释放p所指向的由calloc函数或者malloc函数已经申请成功的内存区域,free没有返回值。
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 int main(){ 5 int i,n,sum=0,*p; 6 printf("Please input a interger number imply that how many numbers you need to input next:\n"); 7 //scanf输入数据以空格结尾 8 scanf("%d",&n); 9 p=(int *)malloc(n*sizeof(int));//也可以改成p=malloc(n*size0f(int));//或者p=(int *)calloc(n,sizeof(int)); 10 printf("please input %d number:\n",n); 11 for(i=0;i<n;i++){ 12 scanf("%d",&p[i]); 13 sum+=p[i]; 14 } 15 free(p); 16 printf("sum=%d\n",sum); 17 return 0; 18 }
malloc和calloc函数用于创建动态数组,还与后续的结构体有关。
(4)main函数的参数
在此前写的main函数总是main(),实际上main()函数也可以有参数。
1.命令行参数
在操作系统提示符状态下为了执行某个操作系统命令或某个可执行文件,而键入的一行字符称为命令行。
命令行的一般形式为:命令名[参数1][参数2]......[参数n]
命令名与参数之间以及各参数之间用空格或者Tab键隔开,若参数本身含有空格,则给参数要用""包含起来。
例子:C>copy f1.c f2.c
2.指针数组作为main函数的形参
在操作系统下运行一个c程序,实际上就是操作系统调用该函数的主函数,主函数再调用其他函数。
主函数main的形参一般有两个参数,例如:
main(int argc,char *argv[]){};
第一个参数是int型,习惯上记作argc,表示命令行中参数的个数(包括命令名在内),在运行c程序时由系统自动计算出参数的个数,第二个参数是指向字符型的指针数组,习惯上记作argv,用来存放命令行中的各个参数。(作为形参,char *argv[]等于char **argv);名字是可以自定的。
实例:假设以下程序存放在xx.c文件中,编译之后已经生成一个xx.c文件:
1 #include<stdio.h> 2 3 4 int main(int argc,char *argv[]){ 5 int i=1; 6 printf("argc=%d",argc); 7 printf("argv= "); 8 while(i<argc){ 9 printf("%s ",argv[i]); 10 i++; 11 } 12 return 0; 13 }
命令行:(当输入为)xx shenyang liaoning China
如何能让这个程序运行,并执行呢?后面再解决这个问题。
本例命令行共有四项,其中xx是执行程序的命令,其与三项是命令行参数,argc中存放的是命令行中包括命令在内的字符串的个数。argv中存放的是命令行各项的首地址。ANSI要求argv[argc]的值必须是一个空指针。
(5)指针的编程练习
1.将直角坐标系的一个坐标点(x,y),转换为极坐标系中的坐标值(p,角度):
1 #include<stdio.h> 2 #include<math.h> 3 4 //用变量的地址值当形式参数,改变对应的地址所在单元的值。 5 double change(double x,double y,double *p){ 6 double m; 7 m=sqrt(x*x+y*y); 8 *p=atan(y/x); 9 return m; 10 } 11 int main(){ 12 double a,b,c,q; 13 printf("please input x,y:\n"); 14 scanf("%lf%lf",&a,&b); 15 c=change(a,b,&q); 16 printf("c=%f,1=%f",c,q); 17 return 0; 18 }
2.将10个国家的名字按照字母由小到大的顺序排列:
1 #include<stdio.h> 2 #include<string.h> 3 //用字符数组指针指向字符数组,作为形参 4 void sort(char *name[],int n){ 5 char *temp; 6 int i,j,k; 7 for(i=0;i<n-1;i++){ 8 k=i; 9 for(j=i+1;j<n;j++){ 10 if(strcmp(name[k],name[j])>0) 11 k=j; 12 } 13 if(k!=i){ 14 temp=name[i]; 15 name[i]=name[k]; 16 name[k]=temp; 17 } 18 } 19 } 20 int main(){ 21 int n=10,i; 22 char *name[]={ 23 "China","America","France","Britain","Canada", 24 "Australia","Switzerland","Japan","Italy","Germany" 25 }; 26 sort(name,n); 27 for(i=0;i<n;i++){ 28 printf("%s\n",name[i]); 29 } 30 return 0; 31 }
3.编写程序,删除字符串的所有尾部空格:
1 #include<stdio.h> 2 #include<string.h> 3 4 int main(){ 5 char *p; 6 char str[20]; 7 for(int k=0;k<20;k++){//将字符数组初始化为‘\0‘不是必须的。 8 str[k]=‘\0‘; 9 } 10 p=str; 11 printf("Please input a string:\n"); 12 gets(p); 13 printf("打印一边字符串:"); 14 puts(p); 15 //此语句用于将指针移到字符串末尾的‘\0‘处。 16 while((*p)!=‘\0‘){p++;} 17 //上述语句等价于while(p++!=‘\0‘);p--; 18 printf("字符串的长度为%d\n",p-str); 19 do 20 p--; 21 while(*p==‘ ‘);//此语句用于将指针位置移动到‘\0‘前面第一个不为空格的字符上。 22 *++p=‘\0‘; 23 printf("%s",str);//puts(str)等同于printf("%s\n",str); 24 return 0; 25 }
在做这个程序时,犯得错误有“指针前面忘了加*符,导致内存访问错误”,“printf和puts函数的功能的区别有点模糊”。
4.编写函数output和input,其功能分别于gets和puts相同,函数中分别用getchar()和putchar()读入和输出字符:
1 #include<stdio.h> 2 #include<string.h> 3 void input1(char *s) 4 { 5 int i=0; 6 char c; 7 while((c=getchar())!=‘\n‘) 8 s[i++]=c; 9 s[i]=0; 10 } 11 void input2(char *a){ 12 char c; 13 int i=0; 14 c=getchar(); 15 while(c!=‘\n‘){ 16 a[i++]=c; 17 c=getchar(); 18 } 19 } 20 void output(char a[]){ 21 while(*a!=‘\0‘){ 22 putchar(*a++); 23 } 24 } 25 int main(){ 26 char *p; 27 char str[20]; 28 output("please input a string:\n"); 29 input1(p);//同于input1; 30 output(p); 31 }
在本例中我出错的地方是getchar()函数用出错了,char c=getchar();我用成char c;getchar(c);
5.统计一个字符串中所有单词的个数:
1 #include<stdio.h> 2 #include<string.h> 3 4 int count(char *q){ 5 char *p=q; 6 int flag=0;//用于返回的单词个数。 7 //将指针指到字符数组的最后的‘\0‘的位置 8 while(*p++!=‘\0‘); 9 p--; 10 11 for(p--;q<=p;p--){ 12 if(*p!=‘ ‘&&*(p-1)!=‘ ‘|*p==‘ ‘&&*(p-1)==‘ ‘){ 13 continue; 14 }else if(*p!=‘ ‘&&*(p-1)==‘ ‘){ 15 flag++; 16 }else if(*p!=‘ ‘&&p==q){ 17 flag++; 18 } 19 } 20 return flag; 21 } 22 23 int main(){ 24 char str[30],*p; 25 puts("Please input a string containing many space to divide them: "); 26 p=str; 27 gets(p); 28 printf("单词的个数为:%d",count(p)); 29 }
不知道应该写什么,自己怎么搞定的好像都不太清晰。醉了!
6.编写一个小学生做加减乘除的程序。例如在操作系统下,键入cal 15 * 15<回车>则在屏幕上显示15*15=225:
1 #include<stdio.h> 2 #include<string.h> 3 4 int main(int argv,char (*argc)[]){ 5 double a,b; 6 char c; 7 double result; 8 a=(double)argc[1]; 9 c=argc[3]; 10 if(strlen(argc[2])==1){ 11 if(argc[2]=="+"){ 12 result=a+b; 13 }else if(argc[2]=="-"){ 14 result=a-b; 15 } 16 else if(argc[2]=="*"){ 17 result=a*b; 18 } 19 else if(argc[2]=="/"){ 20 result=a/b; 21 } 22 printf("%lf %s %lf=%lf",a,argc[2],b,result); 23 }else 24 { 25 printf("输入错误!\n"); 26 } 27 printf("输入错误!\n"); 28 }
这种程序的设计方法还不太清楚,目前先写到这。