严格地说,C++ 中没有多维数组,通常所指的多维数组其实就是数组的数组:

// array of size 3, each element is an array of ints of size 4
int ia[3][4];

如果数组的元素又是数组,则称为二维数组,其每一维对应一个下标:

ia[2][3] // fetches last element from the array in the last row

第一维通常称为行(row),第二维则称为列(column)。C++ 中并未限制可用的下标个数,也就是说,我们可以定义元素是数组(其元素又是数组,如此类推)的数组。

多维数组的初始化

和处理一维数组一样,程序员可以使用由花括号括起来的初始化式列表来初始化多维数组的元素。对于多维数组的每一行,可以再用花括号指定其元素的初始化式:

int ia[3][4] = { /* 3 elements, each element is an array of size 4 */
{0, 1, 2, 3} , /* initializers for row indexed by 0 */
{4, 5, 6, 7} , /* initializers for row indexed by 1 */
{8, 9, 10, 11} /* initializers for row indexed by 2 */
};

其中用来标志每一行的内嵌的花括号是可选的。下面的初始化尽管有点不清楚,但与前面的声明完全等价:

// equivalent initialization without the optional nested braces for each row
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

与一维数组一样,有些元素将不使用初始化列表提供的初始化式进行初始化。下面的声明只初始化了每行的第一个元素:

// explicitly initialize only element 0 in each row
int ia[3][4] = {{ 0 } , { 4 } , { 8 } };

如果省略内嵌的花括号,结果会完全不同:

// explicitly initialize row 0
int ia[3][4] = {0, 3, 6, 9};

该声明初始化了第一行的元素,其余元素都被初始化为 0

多维数组的下标引用

为了对多维数组进行索引,每一维都需要一个下标。例如,下面的嵌套 for循环初始化了一个二维数组:

const size_t rowSize = 3;
const size_t colSize = 4;
int ia [rowSize][colSize]; // 12 uninitialized elements
// for each row
for (size_t i = 0; i != rowSize; ++i)
// for each column within the row
for (size_t j = 0; j != colSize; ++j)
// initialize to its positional index
ia[i][j] = i * colSize + j;

当需要访问数组中的特定元素时,必须提供其行下标和列下标。行下标指出需要哪个内部数组,列下标则选取该内部数组的指定元素。

如果表达式只提供了一个下标,则结果获取的元素是该行下标索引的内层数组。如 ia[2] 将获得 ia 数组的最后一行,即这一行的内层数组本身,而并非该数组中的任何元素。

使用范围for语句处理多维数组

C++11中提供了范围for语句,所以前一个程序可以简化为如下的形式:

size_t cnt=0;
for(auto &row:ia)
       for(auto &col :row){
       col=cnt;
       ++cnt;
}

第一个for循环遍历ia的所有元素,这些元素都是大小为4 的数组,因此row的类型应该就是含有4个整数的数组的引用,第二个for循环遍历那些4元素数组中的某一个,因此col类型就是整数的引用。每次迭代把cnt的值赋给ia的当前元素,然后将cnt1

第一个循环中选用引用类型作为循环控制变量,如果不使用引用类型,数组会被自动转换成指针:

for(auto row:ia)
       for(auto &col :row)

程序无法通过编译,此时row的类型是int* 显然内层循环就不合法了。

使用范围for语句处理多维数组,除了最内层的循环外,其它所以循环控制变量都应该是引用类型。

指针和多维数组

与普通数组一样,使用多维数组名时,实际上将其自动转换为指向该数组第一个元素的指针。

定义指向多维数组的指针时,千万别忘了该指针所指向的多维数组其实是数组的数组。

因为多维数组其实就是数组的数组,所以由多维数组转换而成的指针类型应是指向第一个内层数组的指针。

int ia[3][4]; // array of size 3, each element is an array of ints of size 4
int (*ip)[4] = ia; // ip points to an array of 4 ints
ip = &ia[2]; // ia[2] is an array of 4 ints

如果从内向外阅读 ip 的声明,则可理解为:*ip int[4] 类型—— ip 是一个指向含有 4 个元素的数组的指针。

在下面的声明中,圆括号是必不可少的:

int *ip[4]; // array of pointers to int
int (*ip)[4]; // pointer to an array of 4 ints

typedef 简化指向多维数组的指针

typedef 类型定义可使指向多维数组元素的指针更容易读、写和理解。

typedef int int_array[4];
int_array *ip = ia;

可使用 typedef 类型输出 ia 的元素:

for (int_array *p = ia; p != ia + 3; ++p)
for (int *q = *p; q != *p + 4; ++q)
cout << *q << endl;

外层的 for 循环首先初始化 p 指向 ia 的第一个内部数组,然后一直循环到 ia 的三行数据都处理完为止。++p 使 p 1,等效于移动指针使其指向 ia的下一行。

内层的 for 循环实际上处理的是存储在内部数组中的 int 型元素值。首先让 q 指向 p 所指向的数组的第一个元素。对 p 进行解引用获得一个有 4 int 型元素的数组,通常,使用这个数组时,系统会自动将它转换为指向该数组第一个元素的指针。在本例中,第一个元素是 int 型数据,q 指向这个整数。系

统执行内层的 for 循环直到处理完当前 p 指向的内部数组中所有的元素为止。

q 指针刚达到该内部数组的超出末端位置时,再次对 p 进行解引用以获得指向下一个内部数组第一个元素的指针。在 p 指向的地址上加 4 使得系统可循环处理每一个内部数组的 4 个元素。

数组与 vector 类型相似,数组也可以保存某种类型的一组对象;而它们的区别在于,数组的长度是固定的。数组一经创建,就不允许添加新的元素。

现代 C++ 程序应尽量使用 vector 和迭代器类型,而避免使用低级的数组和指针。设计良好的程序只有在强调速度时才在类实现的内部使用数组和指针。

vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,而且程序员无法知道一个给定数组的长度。数组没有获取其容量大小的 size 操作,也不提供 push_back 操作在其中自动添加元素。如果需要更改数组的长度,程序员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组空间中去。

与使用标准 vector 类型的程序相比,依赖于内置数组的程序更容易出错而且难于调试。