指针与数组的关系
一、概念:
1.1、指针:指针变量p的值是地址,“*”为取数据符,*p表示取p所指向的地址中的数据,“&”为取地址符。
1.2、数组:数组是用于储存连续多个相同类型数据在内存中的集合。
1.3、指针与数组的关系:指针变量p是个地址,而数组的数组名a也是个地址,从而可以这样赋值p=a,由此衍生的指针数组、数组指针所产生的表达式及运算千变万化,经常看到一个表达式都不知道,最终所求的值是地址还是数据。网络上看了好多推演的教程,也思考了很久,但看到好多推演断层式的表达式,还是想不明白。最终自己找到“以不变应万变”的方法,算是自己的感悟吧!
二、“3部曲”
部曲1:判断p在定义的指针表达式中是指针变量还是数组。用优先级原则判断,即p先跟“*”还是“[]”结合,前者p为指针变量也就是地址,后者p为数组名,以数组对待。
部曲2:数组的表达式中“[]”的个数是否等于维数,等于则表达式就是元素值,不等于则表达式的值都是地址,无论多少维数组。验证如下:
(1)
不管多少维的数组,表达式中"[]”比维数只要少一个,那么这个表达式的值就是地址,并且该地址是该维的首地址,如二维数组a[3][2],三维数据b[2][3][2],一维数组c[10]。
(2)
任何维数数组所表示的地址表达式("[]”比维数少),增加"[]"结合成新表达式时(结合“*”相当于增加一个"[]"),只要“[]”的个数少于维数,无论怎么结合都是地址偏移(有多少个“[i]”或“*”要结合,那么直接加i就行),并且它结果还是个地址,只有当“[]”的个数等于维数,表达式的值才是元素值(也就是该内存地址中的数据)。注意: “[]”与“*”都是取数据符号,等效的。
部曲3:p+1偏移代表什么。若p在指针定义表达式中是数组,则偏移一个元素;若p在指针定义表达式中是指针变量,则偏移定义的表达式中数组的长度。
三、案例分析
案例1(指针数组):
int n[3][4] = { 1, 2, 3, 4, 8, 7, 6, 5, 9, 10, 11, 12 };
int *pn[3] = { n[1], n[0], n[2] }; \\pn先与[]结合,所以pn为一维数组。
printf("%d\n", (*(pn + 2))[3]); \\数组pn少[],pn为地址且偏移2,取数据符*(pn+2)等价pn[2],即一维数组pn结合1个"[]"就是元素值(数据),因pn[2]的值为n[2],而n为二维数组,n[2]少了1个"[]"表示为地址,n[2]在结合1个“[]”即n[2][3]=12表示元素(数据)。
printf("%d\n", pn[1][2]); \\数组pn结合1个"[]"即pn[1]就是元素(数据),因pn[1]的值为n[0],而n为二维数组,n[0]少了1个"[]"表示为地址,n[0]在结合1个“[]”即n[0][2]=3表示元素(数据)
printf("%d\n", *(pn + 1)[1]); \\数组pn少[],pn为地址且偏移1,(pn+1)还是一维数组的地址,跟"[1]"结合,既结合“[]”又偏移1,满足“[]”个数 等于维数,即为pn数组的元素pn[2],因pn[2]=n[2],而n为二维数组,n[2]少了1个"[]"表示为地址,与“*”结合,相当于增加一个"[]",即*n[2][0]=9。这里要特别注意运算优先级。
案例2(数组指针):
int n[3][4] = { 1, 2, 3, 4, 8, 7, 6, 5, 9, 10, 11, 12 };
int (*pn)[4]=n; \\pn与*先结合,因此pn为指针变量,并且指向的数组长度为4。等价式pn=n,即可将pn当做二维数组n运算。
printf("%d\n", (*(pn + 2))[3]); \\pn偏移2单位还是地址,再与*结合(相当于pn增加一个"[]"),即pn[2]=n[2],最后n[2]和[3]结合即n[2][3],所以n[2][3]=12是数组的元素。
printf("%d\n", pn[1][2]); \\pn=n,即pn[1][2]=n[1][2],而n[1][2]中“[]”个数等于数组维数,n[1][2]=6为数组元素。
printf("%d\n", *(pn + 1)[1]); \\pn偏移1单位还是地址,跟"[1]"结合,既增加“[]”又偏移1,即pn[2],因pn[2]=n[2],而n为二维数组,n[2]少了1个"[]"表示为地址,与“*”结合,相当于增加一个"[]",即*n[2][0]=9。