如果,数组的维数不止一个,我们通常称为多维数组。例如,下面的声明。
int num[3][3];
直观看起来,这是一个3行3列的数组。但是实际上在内存中,数组是按照线性顺序存放的。也就是说,我们把上面这个数组理解为一个一维数组,只不过这个一维数组的3个元素都是一维数组而已。如下所示:
那么,如果是个三维数组,也按照同样的方式来理解,例如:int num[3][6][9]; 我们可以理解它是一个包含3个元素的数组,而这3个元素是包含6个元素的数组,这6个元素又都是包含9个元素的数组。
正如一维数组那样,一维数组名是指向数组起始元素的指针。那么多维数组名就是指向一个包含X个XXX类型的数组的指针。举个例子,我们还是拿上面的num来说事。这个num是一个指向包含3个int类型的数组的指针。即int (*num) [3];
二位数组的初始化可以按照我们上述的理解来进行。例如:
int num[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
当然了,还可以这样
int num[3][3] = { 1,2,3,4,5,6,7,8,9 };
只不过这样的书写方式,是按照顺序给数组元素赋值的。
在初始化多维数组的时候,我们应该采用第一种方式,这种方式使得这个初始化的整个列表更容易阅读。其次,如果是不完整初始化,那么使用第一种方式就非常有用,如下所示。
int num[3][3] = { {1, 2, 3}, {}, {7, 8, 9} };
打印,输出这个数组如下:
定义如下数组:
int num[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
我们可以这样访问数组中的元素。
num[1][1]; //下标访问
*(*(num + 1) + 1); //间接访问
上面的访问是等价的,我们可以尝试打印它们,如下所示:
实际上,我们可以混合使用它们,只不过这看起来将不是很容易理解。我并不建议混用他们。例如,下面的表达:
cout << *(num[1] + 1) << endl;
cout << (*(num + 1))[1] << endl;
它们实际上访问的依旧是num[1][1]这个元素,输出的值将会是5。
前面我们说过,多维数组可以理解为一维数组,只不过这个一维数组的每一个元素也都是数组。那么我们声明一个指向数组的指针应该是下面这个样子的。
int num[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
int(*p)[3] = num;
这样,p就会指向num的第一行。而p++就会指向第二行,依此类推。这样初始化p以后,我们就可以按照如下的方式使用p
也可以使用指针来进行访问,这也没有问题,毕竟数组在内存中是线性存储的。下面初始化p的操作是等价的。
int* p = &num[0][0]; //数组起始元素的地址
int* p = num[0]; //数组第一行第一个元素的地址 == 数组起始元素的地址
这样的指针指向数组以后,在使用的时候多维数组就好像被压扁了一样,变成了一维数组。我们来看一个例子。
实际上,多维数组做函数参数和一维数组并没有什么区别。实际传递给函数的是指向数组起始元素的指针,只不过这个指针在多维数组这里变成了数组指针。例如:
void function(int (*num)[3]); //这里的这个3不要省略
void function(int num[][3]); //只能省略第一个下标,第二个下标的3一定要有。
int (*p) [];这样的指针虽然是合法的,但是它不应该出现,因为编译器不知道数组的长度,它的结果可能不是你想要的。另外形参num[][3]这里的3不能省略,编译器需要知道第2个以及之后的各个维度的长度才能对各下标进行求值。
下面这个函数将遍历传入的数组num,同时我们将数组第一维的大小也作为参数传入给了函数。
void function(int num[][3],int len)
{
for (size_t i = 0; i < len; i++)
{
for (size_t j = 0; j < 3; j++)
{
cout << num[i][j] << ' ';
}
}
}