二维数组和一维数组的数据分布和存取
标签:c/c++
二维数组在存储分布上和一维数组是一样的,但是存取的写法却是有很大差异的
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int i, j; char mark[64][64] = {0}; char a = 1; for (i = 0; i < 64; i++){ for (j = 0; j < 64; j++){ memcpy(mark + i * 64 + j, &a, sizeof(char)); } } return 0; }
上述代码会越界(如果换作一维数组就不会发生越界),原因是mark是二维数组的首地址,所以对mark进行+1操作,事实上得到的结果表示的是mark的下一维[64]数组的首地址,即一次加上的是整个第二维数组的大小。
如何像操作一维数组一样操作二维数组
在实际的项目中,可能会经常涉及到将二维数组改成一维数组的情况,也会经常执行类似于从数组中取值的操作。如果操作是从数组第一个元素(0, 0)开始
我们可以用数组名初始化指针,之后用指针进行相关操作,没有问题(见下面例子,这种方法其实也不好操作)。此时如果是选用数组名进行操作,需要特别注意不能出现上述情况(在二维数组名上进行加法运算不等同于“遍历”数组元素)。如果是从数组中间进行取值
我们可以用指针指向该元素,之后用指针进行相关的操作。但是,前提是一定要找准该元素的地址。举两个例子(例子中将二维数组的第一维第二维分别成作“行”和“列”;mark_[64*64]是mark对应的转换后的一维数组):
(1)从第二列首地址开始
char *p = mark[1];
对应的将二维数组转换为一维数组的写法:
&mark_[1*64] <=> mark[1]
(2)从第二列的第三个元素开始
char *p = &mark[1][2];
对应的将二维数组转换一维数组的写法:
&mark_[1*64+2] <=> &mark[1][2]
二维数组名初始化指针问题
// 略去头文件 int main() { int a[3][3] = { {1, 2, 3}, {3, 4, 5}, {6, 7, 8} }; int *p1 = a; //[1] printf("*p:%d %d\n", *p1 , *(p1+1)); int **p2 = a; //[2] printf("**p:%d %d\n", *p2, *(p2+1)); int(*p3)[3] = a; //[3] printf("(*p)[]:%d %d\n", (*p3)[0], (*p3)[1]); printf("(*p)[]:%d %d\n", *(*p3), *(*(p3)+1)); int sd = 0; return 0; }
注释[1]
g++或gcc编译器会直接报错:test.cpp:18:14: error: cannot convert ‘int (*)[3]’ to ‘int*’ in initialization int *p1 = a;
;vs下可以编译通过,且通过p1指针(+1或++),我们可以对二维数组进行遍历操作,可见vs的编译器在这种情况下会对数组名a进行类型转换,即将int (*)[3]
转换为int *
。不要使用这种写法,因为会出现不兼容的问题。注释[2]
g++或gcc编译器会直接报错:test.cpp:18:14: error: cannot convert ‘int (*)[3]’ to ‘int**’ in initialization int **p2 = a;
;vs下可以编译通过,且通过p2二维指针(+1或++),我们可以对二维数组进行遍历操作,可见vs的编译器在这种情况下也会对数组名a进行类型转换,即将int (*)[3]
转换为int **
。
注意这里是【int 】,因为是二维数组的缘故,此时会发现*p2依旧是一个指针,只不过这个指针存储的“地址”是a[0][0]对应的值;那么相应的,p2是指针的指针,存储的是*p2的地址;但是,**p存储的就是未知的值了,因为a[0][0]作为地址再去取地址的值,会发生意想不到的问题,内存越界!**不要使用这种写法,因为会出现不兼容的问题。注释[3]
正常的写法,不会出现强制类型转换,但是你会发现这种方法和数组操作没有太大区别,因为类型的关系,*p表示的是只是一个地址,如果需要取值,则外层需要再有一个取地址符 * 。正常的写法,推荐。
小结
代码中使用的应该是一维数组到指针的转换,或者是多维指针通过calloc/malloc获取空间之后的相互转换(指向)。不应该存在上述过程中的多维数组到指针之间的转换。