我在实际编码的时候,实质上很少用到数组指针。于是,对于数组指针,总给我一种模糊的感觉。
花了一上午时间专门去研究数组指针,数组指针终于在我心中终于变的明朗起来。
何为数组指针?
数组指针:就是指向数组的指针。说到底还是指针,只不过数组指针指向的对象是数组而已。
在要搞清楚数组指针之前,必须先要把指针搞清楚。
何为指针?
我不想按照书上的解释来解释(太过理论,很多人不是很明白)。我用我的理解给大家讲讲。
指针就好比一只手,想指哪就指哪。这句话引申到C里,说明C的指针很灵活。
指针常量:就是这个手一出来,一旦指向某个地方就再也不能指向其他地方了。
1 #include <stdio.h> 2 3 int main(void) 4 { 5 int a=100; 6 int *const p=&a;//定义一个指针常量 7 printf("%d ",*p); 8 9 return 0; 10 }
输出结果是100.
接下来我将试图让p改变指向。
1 #include <stdio.h> 2 3 int main(void) 4 { 5 int a=100,b=200; 6 int *const p=&a;//定义一个常量指针 7 printf("%d ",*p); 8 //尝试让p指向b 9 p=&b ; 10 printf("%d ",*p); 11 }
在C-Free里程序给出了警告: assignment of read-only variable `p'(意思是给只读变量的p赋值)
在vc6.0里程序给出了错误信息:l-value specifies const object。(意思是左边这个值是常量)
C-Free和vc6.0用的编译器不一样,一个是minGW,另一个是cl 所以给出的错误提示不一样。
指针变量:就是指针的指向是可以改变。可以想指谁就指谁,如可以指向整型数据,也可以指向指针,也可以指向结构体。
一级指针:就是只有一只手。如 int *p;//p就是一级指针
二级指针:也是一只手,呵呵。很好理解,不管是几级指针,它都是指针,只要是指针,就可以认为是一只手,只不过指向的对象(必须是二级指针)变了。如int **p;
三级指针:和二级指针类似,指向的对象是三级指针,可以是三级指针变量,可以是三级指针常量。如int ***p;。
依此类推。。。
说明:不管是什么类型的指针在内存中都是占4字节。(如果用的很老的系统和编译器,指针所占的空间可能不是4字节)
接下来进入主题讲数组指针。
现在有一个一维数组:int b[5];
我现在想要定义一个指针变量,指向b,该怎么做呢?
先分析一下数组b, b是数组名,如果我要取数组里的第二个元素,可以这样做 b[1] ,这样就ok了。实质上编译时b[1]会展开,变成 *(b+1),
由此可见b是一个指针。实质上是一个指针常量。指向这个数组的第一个元素b[0].也就是说 b的值和&b[0]的值是一样的。
呵呵,答案已经清晰了,如果我想要指向这个数组,只需要指向数组的第一个元素就可以了。可以这样写 int *p=&b[0];//或者int *p=b;
我相信学C/C++的人都遇到过这样定义,int (*p)[5];//这个数组指针,这里的p就是一个指针,但是指向的对象是什么还需要研究
并且很多人(包括本人在内)认为p指向一维数组,并且这个数组的长度是5。
但是其实并不然,p是指向二维数组的。
其实int (*p)[5];等价于int[5] *p; 这就不难看出p指向的对象是int[5]类型的数据。
int b[5];等价于int[5] b;如果我想要让p指向b,必须这么写,p=&b;//因为b是一个指针,对b取地址,也是一个指针,那么p是一个二级指针。
现在通过代码证实一下:
1 #include <stdio.h> 2 3 int main(void) 4 { 5 int i; 6 int b[5]={1,2,3,4,5}; 7 int (*p)[5]; 8 p=&b; 9 for(i=0;i<5;i++) 10 printf("%d ",p[i]); 11 printf(" "); 12 }
运行的结果:
看到这样的结果是不是很吃惊。
从打印出来的结果看,p[i]也是地址。
如果p是一级指针,那么p[i]打印出的肯定是这个地址内容,应该是1,2,3,4,5。但是实际结果并不是如此,由此断定p是一个二级指针。
b[0],b[1],b[2],b[3],b[4]的地址是:
1 #include <stdio.h> 2 3 int main(void) 4 { 5 int i; 6 int b[5]={1,2,3,4,5}; 7 int (*p)[5]; 8 p=&b; 9 for(i=0;i<5;i++) 10 printf("%d ",&b[i]);//打印数组b每一个元素的地址 11 printf(" "); 12 }
运行结果:
从两次的结果上显示,p[i]打印出的地址并不是b[i]的地址值。为什么呢?
这得从p的类型讲起。p的定义可以这么写:int[5] *p;
现在p的值是&b,&b的值其实和b的值一样,所以p的值是2686736
那么p+1的值是多少呢。实质上偏移的字节数是sizeof(int[5]), 一个int是4个字节,那么5个int就是20个字节。所以第一张运行的结果图中,都是以20的大小在增长。
呵呵,如果我想要用p打印出1,2,3,4,5.就可以这么写了(*p)[i]. 因为(*p)的值和b一样
1 #include <stdio.h> 2 3 int main(void) 4 { 5 int i; 6 int b[5]={1,2,3,4,5}; 7 int (*p)[5]; 8 p=&b; 9 for(i=0;i<5;i++) 10 printf("%d ",(*p)[i]);//打印数组b每一个元素的地址 11 printf(" "); 12 }
运行结果:
综合:
如果有定义int b[5],(*p)[5];要想利用p打印b的所以元素,需要将p指向b .然后再(*p)[i]。
现在看一下p指向二维数组的情况:
#include <stdio.h> int main(void) { int i; int a[3][5]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}; int (*p)[5]; p=a;//因为p是一个二级指针,所以可以直接把a赋值给p,此时p是指向第一行的 for(i=0;i<5;i++) printf("%d ",p[0][i]);//打印数组b每一个元素的地址 printf(" "); }
运行结果:
p=a;后相当于a取了一个别名,所以对p操作,实质上是在操作a.
注意事项:二维数组的第二维的长度必须与数组指针的维度保持一致,如果不是这样,会导致警告或错误(是编译器而定)
总结:
如果想要用数组指针指向一维数组,就用形如 int *p;的方式。//虽然可以用int (*p)[n];的方式,但是增加了繁琐。也不便理解,容易出错。
如果想要用数组指针指向二维数组,就用形如int (*p)[n];的方式。
如果想要用数组指针指向三维数组,就用形如int (*p)[m][n];的方式。//如 int b[5][m][n]; p=b;
扩展:现在讲一个动态分配二维数组的程序
#include <stdio.h> #include<malloc.h> typedef int datatype; datatype** fun(int m,int n) { //先申请二维数组的第二维空间 datatype** p=(datatype**)malloc(sizeof(datatype*)*m) ; int i; for(i=0;i<m;i++) p[i]=(datatype*)malloc(sizeof(datatype)*n); return p; } int main(void) { int i,j,m=4,n=5,k=1; datatype **p=fun(m,n); for(i=0;i<m;i++) for(j=0;j<n;j++) p[i][j] = k++; //打印二维数组p的每一个元素 for(i=0;i<m;i++) { for(j=0;j<n;j++) printf("%4d",p[i][j]); printf(" "); } printf(" "); }
//动态分配二维思路很简单,就是动态申请地址空间,这些空间里存放指向每一行的首地址。然后在申请真正可以用来存放数据的空间。