多维数组
严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。谨记这一点,对今后理解和使用多维数组大有益处。
使用范围for语句处理多维数组
两层嵌套的for循环来处理多维数组的元素:
constexptr size_t rowCnt=3,colCnt=4;
int ia[rowCnt][colCnt];
for(size_t i=0;i!=rowCnt;++i)
for(size_t j=0;j!=colCnt;++j)
ia[i][j]=i*colCnt+j;
由于C++11新标准中新增了范围for语句,所以前一个程序可以简化为如下形式:
size_t cnt=0;
for(auto &row:ia)
for(auto &col:row)
{
col=cnt;
++cnt;
}
这个循环赋给ia元素的值和之前的那个循环是完全相同的,区别之处是通过使用范围for语句把管理数组索引的任务交给了系统来完成。因为要改变元素的值,所以要把控制变量row和col声明成引用类型。第一个for循环遍历ia的所有元素,这些元素是大小为4的数组,因此row的类型就应该是含有4个整数的数组的引用。第二个for循环遍历那些4元素数组中的某一个,因此col的类型是整数的引用。每次迭代把cnt的值赋给ia的当前元素,然后将cnt加1.
在上面的例子中,因为要改变数组元素的值,所以我们选用引用类型作为循环控制变量,但其实还有一个深层次的原因促使我们这么做。举一个例子,考虑如下的循环“
for(const auto row: ia) //对于外层数组的每一个元素
for(auto col:row) //对于内层数组的每一个元素
cout<
这个循环中并没有任何写操作,可是我们还是将外层循环的控制变量声明成了引用类型,这是为了避免数组被自动转成指针。假设不用引用类型,则循环如下述形式:
for(auto row:ia)
for(auto col:row)
程序将无法通过编译。这是因为,像之前一样第一个循环遍历ia的所有元素,注意这些元素实际上是大小为4的数组。因为row不是引用类型,所以编译器初始化row时会自动将这些数组形式的元素换成指向该数组内首元素的指针。这样得到的row的类型就是int*,显然内层的循环就不合法了,编译器将试图在以int*内遍历,这显然和程序的初衷相去甚远。
要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
指针和多维数组
当程序使用多维数组的名字时,也会自动将其转换成指向数组首元素的指针。
因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:
int ia[3][4];
int (*p)[4]=ia; //p指向含有4个整数的数组
p=&ia[2];//p指向ia的尾元素
我们首先明确(*p)意味着p是一个指针。接着观察右边发现,指针p所指的是一个维度为4的数组;在观察左边知道,数组中的元素是整数。因此,p就是指向含有4个整数的数组的指针。
在上述声明中,圆括号必不可少:
int *ip[4]; //整型指针的数组
int (*ip)[4]; //指向含有4个整数的数组
随着C++11新标准的提出,通过使用auto或者decltype就能尽可能地避免在数组前面加上一个指针类型了:
//输出ia中每个元素的值,每个内层数组各占一行
//p指向含有4个整数的数组
for(auto p=ia;p!=ia+3;++p)
for(auto q=*p;q!=*p+4;++q)
cout<<*q<< ' ';
外层的for循环首先声明一个指针p并令其指向ia的第一个内层数组,然后依次迭代直到ia的全部3行都处理完为止。其中递增运算++p负责将指针p移动到ia 的下一行。
内层的for循环负责输出内层数组所包含的值。它首先令指针q指向p当前所在的行的第一个元素。*p是一个含有4个整数的数组,像往常一样,数组名被自动转换成指向该数组首元素的指针。内层for循环不断迭代直到我们处理完了当前内层数组的所有元素为止。为了获取内层for循环的终止条件,再一次解引用p得到指向内层数组首元素的指针,给它加上4就得到了终止条件。
当然,使用标准库函数begin和end也能实现同样的功能,而且看起来简洁一些:
for(auto p=begin(ia);p!=end(ia);++p)
for(auto q=begin(*p);q!=end(*p);++q)
cout<<*q<<' ';