关于用指针方式访问二维数组的问题
发现二维指针和二维数组很容易被混淆。这里详细梳理一遍
首先说明几种类型:
int a[2][4] = { 1,2,3,4,5,6,7,8 };//二维数组
int **p; //二重指针,跨度为sizeof(int *)
int(*q)[4]; //定义一个指向数组的指针,指向含4个int元素的一维数组。跨度为4*sizeof(int)=16;
int * q2[4]; //定义了一个数组 里面存有int * 类型的指针 跨度为sizeof(int *)
关于跨度,是指针偏移一个单位,其对应的物理地址的偏移量。举个例子:
int *p =0 //p 指向地址0
p2=p+1 //p2指向地址4
这里p的跨度为4,因为p增加一位,对应地址增加4位(int 在计算机中占4个字节的存储量)
每个指针,在机器中就是一个类型的变量,他所占字节数都是固定的(32位或者64位),但是其不同的类型和跨度完全是编译器阶段赋予他们的含义,比如:
int a=3
int *p1=&a;
float *p2=&a;
他们在存贮在计算机中只是两个数,都指向a的地址,但是你定义的时候一个使用int,一个使用float,所以编译器认定p1跨度为4,p2跨度为8。
二维数组的在计算机中依然是以一维线性关系排布的。
以二维数组a[2][3]
为例,a是由数组a[0]、a[1]组成的一维数组,因此,数组名a可以看作是数组 { a[0], a[1] } 的首地址,即a==&a[0]。
定义一个二维数组和二级指针变量
int a[2][3],**p;
这里要是想使用p访问二维数组a,编译器会报错
p=a;
因为p的定义可以解读为:
int *(*p);
即指针p的关联类型为int*
而数组a的定义可以解读为:
int(a[2])[3];
a是关联类型为int[3]的一维数组。很显然,a与p的关联类型都不一样,故而不能用p去操作a。在这举一个例子来说明:
如p+i
的地址偏移量为
p+sizeof(int*) *i = p+4*i Byte //假定计算机中的用4个字节Byte来存储指针变量
而a+i的地址偏移量为
a+sizeof(int[3])*i = p+3*4*i Byte
显然二级指针变量与二维数组名的跨度是不同的,所以赋值会导致编译器不能确定其跨度,因为编译器选择报错。 二维数组的在计算机中的空间类型是以一维线性关系排布的,二维数组的指针使用方式本质上来说属于一维指针的调用方式。但是为了与一维指针相区别,所以必须调用两次*
号运算符来接引用。但是二维数组指针与二重指针并不一样。 在未加*
号之前 a的跨度为(int的大小×列数),在第一次解引用之后*a
的跨度为(int 的大小)比如
a[2][4],*(*(a+1)+3),**(a+7)
都是对a[2][4
]的最后一个元素(第二行第四列)进行访问的操作。
其实*a
和a
指向的地址是一模一样的,只是编译器理解的含义不用,对于a
编译器会将其看成是一个指向数组的指针,而*a
编译器则会将其看成某一具体数组的首地址,其跨度也完全不同。
重点来了:
对于使用指针访问2维数组的话,
Int a[2][4]={1,2,3,4,5,6,7,8};
Int **p=(int **)a;
Cout<< *(*(p+1)+2);
这种方法是错误的,不可以使用二维指针直接访问,即使p和a的指向地址相同也不可!
但是如下方法又是可行的:
Int a[2][4]={1,2,3,4,5,6,7,8};
Int **p=(int **)a;
Cout<< (int (*)[4])p;
只要你进行强制类型转换告诉编译器这是个指向4维数组的指针,编译器就可以正确的解码了。这是为什么呢?,
上文我说过:二维数组本质上其实是一维指针。所以只能进行一次物理地址的访问。*a
和a
指向的是一个地址,第一次取*
号其实不是真正的去访问物理地址,只不过的编译器看他的方式改变了(对于a编译器会将其看成是一个指向数组的指针,而*a
编译器则会将其看成某一具体数组的首地址),所以第二次取*
号才是真正的访问物理地址上数组的元素。
但是对于二维指针(int **)p
来说,这是真正的二维指针,你对它取第一次*
号的时候 *p
已经真正的访问了物理地址,这时*p
是a[2][4]
中的第一个元素a[0][0]
,其值为1。那么第二次取*
的时候,**p
就会要求访问的物理地址为1的单元,很明显这是我们的进程不能访问的地址段,所以程序出错。