在C/C++的编程中,对指针的使用和了解,再熟悉都不为过。
C/C++毫无疑问的十分强大,但离开了指针和数组,它们就什么都干不了了,可见其重要。
使用数组和指针来描述数据,是C/C++编程中最常见的工作。
本文通过一个描述二维数据的问题,来回顾下数组和指针的使用。
假设我们要表示的是一个5行3列的数据,则共有如下五种常用方式。
每种方式各有优缺点以及适用场景。
方法1,二维数组:
int data[5][3];该方法的优点是完全不用关注内存什么时候创建和销毁的问题,直接使用即可。
但需要在程序运行之前就知道数据的行列数。
该方式的内存空间由编译器在栈空间创建。
空间示例如下图:
方法2,数组指针:
int (*data)[3]; data = (int(*)[3])malloc(sizeof(int[3]) * 5); /// some code here free(data);该方式的不便之处是需要在运行时通过malloc创建堆内存来存放二维数据,而且使用完之后需要free掉。
同时该方法可以适用于在程序运行前行数不确定的情况,即在运行时动态确定数据行数。
该方法所需创建的空间大小与方法1相同,不同在于空间是堆空间,其创建是由malloc在运行时完成的。
空间示例图如下:
方法3,指针数组:
int * data[5]; for (int i = 0; i< 5; i++){ data[i] = (int*) malloc(sizeof(int)*3); } /// some code here for (int i = 0; i< 5; i++){ free(data[i]); }
该方式与方法2的做法从逻辑上正好相反,同样适用于只知道二维数据中一维维数的情况。
但该方法与方式2相比却要多次调用malloc 和free进行内存的创建和释放,而且总使用的内存空间也更大。
其中data变量不需要free,因为其空间并不是在运行时通过malloc创建的。data自己的内存区会在程序运行出data所在的代码块之后自动销毁(栈空间)。当然data不能是static。
空间示例图如下:
方法4,指针的指针:
int ** data; data = (int **)malloc(sizeof(int*) * 5); for (int i = 0;i < 5; i++){ data[i] = (int*)malloc(sizeof(int) * 3); } /// some code here for (int i = 0;i < 5; i++){ free(data[i]); } free(data);
该方式适用于在程序运行前行列都不知道的情况下,在运行时动态创建用来存储二维数据的内存空间。
该方式最灵活。data变量自己指向的空间也是在运行时由malloc创建,需要free掉,因此与方法3相比,要多一次malloc和free操作。
但与方法3在总内存空间占用量上是一致的。
空间示例图如下:
方法5,一维数组模拟:
实际上方法1,2本质上也是一个“一维数组”,只是你需要告诉编译器row维加一的时候,步长是多少而已。
而本法发,不需要编译器计算步长,而是我们自己计算。代码如下:
int * data = NULL; data = (int *) malloc(sizeof(int)*5 *3); for (int i = 0;i < 5; i++){ for (int j = 0; j < 3j ++){ ///do something with element: data[i * 3 + j] ,这里的3就是row维加一的步长 } } free(data) data = NULL;该方法和方法2更像,因为方法1的内存空间在栈空间,方法2和该方法都在堆空间,示例图如下:

总结:
- 以上四种方式分别针对行列数已知、行数未知列数已知、行数已知列数未知、行列数都未知的情况使用。其中方式1,2使用的内存空间大小相同,方式3,4使用的内存空间大小相同。
- 方式3,4比方式1,2 多用r个指针空间,r为行数。
- 方式2比方式3在只知道一维维数的情况下要更优,只需要做数据转置存储即可。
- 当行列数都需要在运行时才能知道的情况只能使用方式4.
- 方式5需要动态计算元素的偏移量,但艰巨了方法1,2和方法3,4 的优点。