1 引例
下面的程序很简单,定义了一个PrintMatrix
函数将一个二维数组以矩阵的形式打印出来。
#include <stdio.h>
#define SIZE (4)
void PrintMatrix(int **arr)
{
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
printf(" %d", arr[i][j]);
}
printf("\n");
}
}
int main(void)
{
int matrix[SIZE][SIZE] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{1, 2, 3, 4},
{5, 6, 7, 8}
};
PrintMatrix(matrix);
return 0;
}
当我们编译这个程序的时候会出现下面的错误信息:
error: cannot convert 'int (* )[4]' to 'int**'
按理来说,一维数组对应着一级指针,就像我们经常使用的,作为函数参数的时候也不理外:
int arr[4] = {1, 2, 3, 4};
int *p = arr;
那么二维数组对应着二级指针怎么就出错了呢?
要想解释清楚这个问题,首先需要知道二级指针和二维数组的定义:
- 二维数组:二维数组本质上是以数组作为数组元素的数组,即“数组的数组”;
- 二级指针:指向指针的指针。
下面将会从两种观点来分析这个问题。
2 观点1 这种使用方法是错误的
举一个简单的例子,定义一个二维数组。
int a[2][2] = {1, 2, 3, 4};
假如用一个二级指针指向它。
int **p = a;
运行如下代码(打印了a
的值和a
,a[0]
,a[0][0]
的地址):
printf("%#x \n", a);
printf("%#x \n", &a);
printf("%#x \n", &a[0]);
printf("%#x \n", &a[0][0]);
运行的结果是输出的四行结果是完全一样的(不同计算机输出的地址可能不同):
0x9ffe40
0x9ffe40
0x9ffe40
0x9ffe40
既然 int **p = a;
,可以推出 p
保存的是 a[0][0]
的地址,那么用 *
号可以对其解引用指针而取出地址的内容,于是测试以下代码:
printf("%d \n", *&a[0][0]);
为 p
指针进行了一次解引用,和预期的一样,输出了二维数组 a
的第一个元素 1,理所当然的 p
是二级指针,那么也可以对其进行二次解引用,当我们对其进行二次解引用的时候,编译器报错了:
error: invalid type argument of unary '*' (have 'int')
还有一点值得注意的是,在二维数组中,a + 1
,可以移动一个元素的位置(也就是移动一个一维数组的大小),而用二级指针,p + 1
永远移动一个 sizeof(int)
的大小(本例中)。
3 观点2 根本不需要这么做
学过 C/C++ 的朋友应该都知道,一位数组在内存中是线性排列的,那么二维数组也不理外,它也是线性排列的。
假如只知道 arr
的地址,那么能得到 arr[1][1]
的地址吗?
答案是可以的,因为数组在内存中是线性排列,那么就代表着只要知道数组的起始地址,就可以通过一次取址获得任意元素的位置,所以根本不需要二次取址。
这也就解释了为什么声明二维数组的时候,编译器根本“不关心”一维的大小,以至于可以省略掉它,如:
int a[][2];
4 二维数组做函数参数的方法
4.1 方法1
void fun(int arr[2][2]);
这种方法导致只能处理2行2列的int型数组,即固定了数组大小。
4.2 方法2
void fun(int arr[][2]);
可以省略一维的大小,这种方法的限制略微宽松了一些,但是还是只能处理每行是2个整数长度的数组。
4.3 方法3
void fun(int (*arr)[2]);
这个方法需要重点讨论,这里引入了一个新的概念叫做数组指针。
int (*arr)[2];
在解释这个概念之前要比较一下数组指针和指针数组的不同,以防止混淆:
- 指针数组(array of pointers):即用于存储指针的数组,也就是数组元素都是指针,如
int* a[4]
,表示数组a
中的元素都为int
型指针; - 数组指针(a pointer to an array):即指向数组的指针,如
int (*a)[4]
,表示指向数组a
的指针。
通过上述概念就知道了应该用数组指针指向一个二维数组,以下是正确的示例:
int a[2][2] = {1, 2, 3, 4};
int (*p)[2] = a;
5 与Java的不同
Java中声明一个二维数组:
int[][] arr = new int[2][2];
不同于 C/C++ 中的:
int arr[2][2];
也不同与 C/C++ 中的:
int (*p)[2] = new int[2][2];
在 Java 中则是分配了一个包含 2 个指针的数组,指针数组的每个元素包含一个一维数组,在 C++ 中的声明如下:
int **p = new int *[2];