数组的数组
严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。谨记这一点,对今后理解和使用多维数组大有益处。当一个数组的元素仍然是数组时,通常使用两个维度来定义它:一个维度表示数组本身大小,另外一个维度表示其元素(也是数组)大小:
void multi_array()
{
//大小为3的数组,每个元素是含有4个整数的数组
int ia[3][4];
//大小为10的数组,他的每个元素都是大小为20的数组
//这些数组的元素是含有30个整数的数组
int arr[10][20][30] = {0};
}
多维数组初始化
允许使用花括号括起来的一组值初始化多维数组,这点和普通的数组一样。下面的初始化形式中,多维数组的每一行分别用花括号括了起来:
void multi_init()
{
//三个元素,每个元素大小都是4的数组
int ia[3][4] = {
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}};
//可以用一个花括号初始化二维数组
int ib[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
//显示初始化每行的首元素
int ic[3][4] = {{0}, {4}, {8}};
//显示初始化第一行,其他元素被初始化为0
int id[3][4] = {0, 3, 4, 6};
}
下标访问
可以使用下标运算符来访问多维数组的元素,此时数组的每个维度对应一个下标运算符。
int arr[3][3][3] = {
0,
1,
2,
};
//用arr的首元素为ia最后一行的最后一个元素赋值
int ia[3][4] = {0};
ia[2][3] = arr[0][0][0];
// row是一个ia第二个元素数组的引用,包含四个元素
int(&row)[4] = ia[1];
程序中经常会用到两层嵌套的for循环来处理多维数组的元素
int ia[rowCnt][colCnt]={0};
//遍历
constexpr size_t rowCnt = 3, colCnt = 4;
for (size_t i = 0; i != rowCnt; ++i)
{
//对于行内的每一列
for (size_t j = 0; j != colCnt; ++j)
{
//将元素的位置索引作为他的值
ia[i][j] = i * colCnt + j;
}
}
可以使用范围 for语句处理多维数组,由于在C++11新标准中新增了范围for语句,所以前一个程序可以简化为如下形式:
size_t cnt = 0;
for (auto &row : ia)
{
for (auto &col : row)
{
col = cnt++;
}
}
因为要改变数组元素的值,所以我们选用引用类型作为循环控制变量,但其实还有一个深层次的原因促使我们这么做。举一个例子,考虑如下的循环
for (auto &row : ia)
{
for (auto col : row)
{
cout << col << endl;
}
}
这个循环中并没有任何写操作,可是我们还是将外层循环的控制变量声明成了引用类型,这是为了避免数组被自动转成指针,假设不用引用类型,则循环如下述形式:
for (auto row : ia)
{
for (auto col : row)
{
cout << col << endl;
}
}
程序将无法通过编译。这是因为,像之前一样第一个循环遍历ia的所有元素,注意这些元素实际上是大小为4的数组。因为row不是引用类型,所以编译器初始化row时会自动将这些数组形式的元素(和其他类型的数组一样)转换成指向该数组内首元素的指针。这样得到的row的类型就是int*,显然内层的循环就不合法了,编译器将试图在一个int*内遍历,这显然和程序的初衷相去甚远。 要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
指针和多维数组
当程序使用多维数组的名字时,也会自动将其转换成指向数组首元素的指针。 定义指向多维数组的指针时,千万别忘了这个多维数组实际上是数组的数组。 因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:
void multi_pointer()
{
//大小为3的数组,每个元素是含有4个整数的数组
int ia[3][4];
// p指向含有4个整数的数组
int(*p)[4] = ia;
// p指向ia的尾元素
p = &ia[2];
}
我们首先明确(*p)意味着p是一个指针。接着观察右边发现,指针p所指的是一个维度为4的数组;再观察左边知道,数组中的元素是整数。因此,p就是指向含有4个整数的数组的指针。 在上述声明中,圆括号必不可少:
//整数指针的数组
int *ib[4];
//指向含有4个整数的数组
int(*ib)[4];
随着C++11新标准的提出,通过使用auto或者decltype,就能尽可能地避免在数组前面加上一个指针类型了:
// p指向一个含有4个整数的数组
for (auto p = ia; p != ia + 3; ++p)
{
// q指向4个整数数组的首元素,也就是说q指向一个整数
for (auto q = *p; q != *p + 4; q++)
{
cout << *q << ' ';
}
cout << endl;
}
外层的for循环首先声明一个指针p并令其指向ia的第一个内层数组,然后依次迭代直到ia的全部3行都处理完为止。其中递增运算++p负责将指针p移动到ia的下一行。内层的for循环负责输出内层数组所包含的值。它首先令指针q指向p当前所在行的第一个元素。*p是一个含有4个整数的数组,像往常一样,数组名被自动地转换成指向该数组首元素的指针。内层for循环不断迭代直到我们处理完了当前内层数组的所有元素为止。为了获取内层for循环的终止条件,再一次解引用p得到指向内层数组首元素的指针,给它加上4就得到了终止条件。 当然,使用标准库函数begin和end也能实现同样的功能,而且看起来更简洁一些:
// p指向ia的第一个数组
for (auto p = begin(ia); p != end(ia); p++)
{
// q指向内层数组的首元素
for (auto q = begin(*p); q != end(*p); q++)
{
//输出q所指的整数
cout << *q << ' ';
}
cout << endl;
}
循环终止条件由end函数负责判断。虽然我们也能推断出p的类型是指向含有4个整数的数组的指针,q的类型是指向整数的指针,但是使用auto关键字我们就不必再烦心这些类型到底是什么了。
类型别名简化多维数组的指针
可以通过typedef和using等关键字定义类型
// C11新标准定义类型别名
// int_array 是一个包含四个元素的整形数组类型
using int_array = int[4];
//等价的typedef声明
// int_array_same 是一个包含四个元素的整形数组类型
typedef int int_array_same[4];
//如果不会用typedef定义数组类型,可以先定义一个数组变量
int int_array_inst[4];
如果不会用typedef定义数组类型,可以先定义一个数组变量
int int_array_inst[4];
然后加上typedef即可
typedef int int_array_inst[4];
此时int_array_inst就是一个大小为4的整形数组类型。 通过类型定义,我们重新实现遍历
int ia[3][4];
//输出ia中每个元素的值,每个内层数组各占一行
for (int_array *p = ia; p != ia + 3; p++)
{
for (int *q = *p; q != *p + 4; q++)
{
cout << *q << ' ';
}
cout << endl;
}