深度分析多维数组取址与指针的赋值关系 很多学员在学习指针与多维数组的关系时,往往搞不清楚如何取数组中的地址,并给对应的指针赋值,在看思考上面问题前,我们先看下一位数组的取址,例如int a[10];其中a代表首元素的地址,而C语言规定&a代表整个数据的地址。所以a+1和&a+1由于a和&a的含义步长不一样,导致指针跳转到的位置也不同。 接下来看下面这个关于二维数组的小程序: 1#include<stdio.h> 2void main() 3{ 4int arr_2[4][2] = { { 1, 2 }, { 5,5 }, { 2, 4 }, { 9, 0 } }; 5printf("arr_2[0]=%x\t",arr_2[0]); 6printf("arr_2[0]+1=%x\n",arr_2[0]+1); 7 8printf("arr_2=%x\t",arr_2); 9printf("arr_2+1=%x\n",arr_2 + 1); 10 11printf("&arr_2=%x \t", &arr_2); 12printf("&arr+1=%x\n",&arr_2 + 1); 13} 其运行结果是:
看到结果时,我们发现每个地址值都是相同的,但是加1之后的值就不同了,也就是说加1地址跳转的大小不同,为什么会这样呢?通过上面的数据之差,可以很明显地发现第一个arr_2[0]加1,跳转了4个字节,也就是1代表一个int数据的长度;而第二个arr_2加1后跳转了8个字节,此时1代表的是2个int数据的长度;最后&arr_2加1后跳转的是32个字节,也就是8个int数据类型的长度。到此我们知道了这是由于所加的1代表的步长不同,导致了指针在跳转到下一个时读取了不同位置的内存。 数组的维度,就是当用数组下标表示一个数组中的某个元素时,需要用几个数字来表示才能唯一确定这个元素,这个数组就是几维。例如,一个数字确定一个元素:arr_1[7]就是一维的;两个数字确定一个元素:arr_2[5][9]是二维;三个数字确定一个元素:arr_3[6][8][1]是三维。 可以把数组的维看成是“数组套数组的层数”。例如,数组int arr_1[4]={ 1, 5, 9, 0 }是一维数组,要找到数字1只需找第一个数字,即arr_1[0]。而int arr_2[4][2]={ {1,2}, {5,5},{2,4},{9,0} }是二维数组,它套了两层数组。要找到数字1,需要指出1是在外层数组中的哪一部分,参考下图可以发现是在第一个一维数组中。接下来需要找在内层数组中的哪个元素,可以看到在第一个一维数组的第一个元素,即arr_2[0][0]。 到这里为止,我们是不是已经发现了一个问题呢,既然arr_2[0][0]指向的是数组的元素,数据类型为整形,那么对arr_2[0][0]取地址,即&arr_2[0][0],就表示了一个整形变量的地址。其地址加1,表示跳过一个整形的宽度,即4个字节,与上述程序运行结果第一行相符。 现在,假若定义了一个指向整形的指针int *p,就可以给p赋值了,p=&arr_2[0][0]。我们再想一下,可以给p赋值的不仅仅是arr_2[0][0]这个元素的地址,其余元素的地址完全也可以给p赋值,例如arr_2[1][0],也就是说p=&arr_2[1][1]也是正确的。 在前面的学习中,我们知道在数组int a[3]={1,2,3}中,&a[0]和a都表示第一个元素的地址,那么在&arr_2[0][0]中,我们把arr_2[0]看成一个整体,则变为&(arr_2[0])[0],于是&(arr_2[0])[0]这个也可以简化为arr_2[0],于是p=arr_2[0]。通过前面的分析我们了解到:&运算符的作用与*还有[]的作用正好相反,一个是获取地址,另外两个是根据地址取得变量的值,是可以相互抵消的,类似于逆运算。 下图可帮助大家理解: 分析到这里继续往下看,我们知道了arr_2[0]表示的是数组元素的地址,那么对arr_2[0]再次取地址&arr_2[0],即arr_2,又是什么意思呢?我们知道上面数组是二维的,也可以看成两层的意思,取一次地址时,是在第二层内取地址,是对元素的取地址。现在再取地址,可以认为是由第二层跳到第一层,现在在第一层内取地址了,参考下图可以知道,在第一层内取地址就是把每个第二层看成取地址的对象,则取得的地址就是第二层的地址,也就是一维数组的地址。这样的话,也就是说arr_2表示的是一个有两个元素的一位数组的地址,则加1,表示跳过2个int宽度,即8个字节,与程序运行的第二行相符。 假若有这么一个指针int (*pp)[2],该指针表示指向两个整形元素的一位数组的指针,那么就可以给pp赋值了,pp=arr_2。我们知道[]和*作用都是取值,上面的指针也可以定义为int **pp;pp=arr_2。区别是前者指向的数组元素只能有两个,而后者就没有这个规定。 下图帮助大家理解: 接下来我们继续取地址,&arr_2又是什么含义呢?分析到这里相信很多学员已经明白,这个无非就是在上面的基础上再跳到外面一层的意思,即获取的是整个数组的地址,则加1表示跳过整个二维数组,就是跳过8个int类型的宽度,是32个字节,正好与程序运行的第三行相符。 假若有这么一个指针int (*ppp)[4][2],表示指向一个二维数组的指针,那么就可以给ppp赋值了,即ppp=&rr_2。我们知道[]和*作用都是取值,上面的指针也可以定义为int ***ppp。区别是前者指向的数组元素必须是个二维数组,而后者就没这个规定。 下图帮助大家理解: 通过上面的分析,我们可以知道,虽然都是指向一个位置的指针,但是指针类型不同时,加1跳转的大小,需要根据其表示的数据类型的大小来决定。当我们能够分析出地址的含义时,我们就可以定义相符的指针,反过来,知道了指针的数据类型,我们也可以选择对应的地址来赋值。 通过上面的学习,我们可以思考一下指针的深层含义:指针本质也是一种数据类型,指针的数据类型,是指它所指向内存空间的数据类型。当指针加1时,就是跳过了它所指向的数据类型的长度的字节数,这样就解决了指针的步长。 上面分析的是二维数组,其取址分别有三种情况,分别对应一级、二级、三级指针。我们可以举一反三去推测多维数组的取址情况,并对合适的指针赋值。
精华推荐: 3分钟带你读懂C/C++学习路线
为什么来黑马程序员学C/C++? 稳做IT贵族人才!
一张贴玩转C/C++:视频+源码+笔记+工具+面试
|