}
}
我们来仔细看看这里究竟发生了什么。
先看外层循环。首先,我们创建了一个指向 a 这个数组首个元素的指针。我们刚才提到,a 的第一个元素是一个包含 10 个元素的数组,因此我们创建的指针必须具有正确的类型。*i 表示它是个指针,[10] 表示这个指针指向的是一个数组,且数组的长度为 10。
你应该注意到了那个括号,这是因为 [10] 的优先级更高。不打括号 int *i[10] 表示一个数组里存储 10 个 int 类型的指针,这和我们想要达到的目标完全不同。
接下来,我们每次循环时都把指针向后移动一个位置。由于已经指定了正确的类型,因此指针将移到 a 中的下一个元素,也就是第二个包含 10 个元素的数组。指针每次循环都会指向下一个数组,直到到达 a 中的最后一个数组的下一个位置,立即退出循环。
再来看内层循环。我们先定义了一个 int 类型的指针,这个指针初始化为 *i,也就是先获取外层指向包含 10 个元素数组的指针,然后解引用,获取到这个数组,而由于数组本质是一个指向首元素的指针,所以相当于 j 初始化为指向这个包含 10 个 int 的数组的首个元素的指针。
接下来,输出 j 解引用后的那个 int 元素,然后 j 向后移动一个位置,直到到达该数组末尾的下一个位置,立即退出循环。
也就是说,多维数组本质就是多个数组的嵌套,而只要记住数组即为指向其第一个元素的指针,那么无论嵌套多少都不用关心——每一次嵌套,都只是让指针指向包含数组的数组而已。
当维数越来越多的时候,我们手动指定指针指向的是怎样的对象容易累死自己,因此建议使用 auto。
for(auto i=a;i!=a+10;i++){
for(auto j=*i;j!=*i+10;j++){
cout<<*j<
}
}
注意了,既然是指针,那么修改时,修改的元素是原本数组内的对象,而并非拷贝副本:
int a[3][3]={};
for(auto i=a;i!=a+3;i++){
for(auto j=*i;j!=*i+3;j++){
cout<<*j<
*j=1;
}
cout<
}
// 输出都是 0
for(auto i=a;i!=a+3;i++){
for(auto j=*i;j!=*i+3;j++){
cout<<*j<
}
cout<
}
// 输出都是 1
当然,由于是数组,所以多维情况下 begin() end() 依然可用,而且和用指针运算相同,都能正确获取到指向对应数组的首个数组元素的指针:
for(auto i=begin(a);i!=end(a);i++){
for(auto j=begin(*i);j!=end(*i);j++){
cout<<*j<
*j=1;
}
cout<
}
范围 for 语句
当然了,正是由于这些性质,因此范围 for 语句也可用:
int a[3][3]={};
for(int i[3]:a){ // Error
for(int j:i){
cout<
}
}
……但是上面的根本就没办法通过编译。这是因为,范围 for 的本质是会把 a 中的每个元素拷贝赋值给 i,但问题是,每个元素都是一个数组,而这样就会自动变为指向数组首个元素的指针。因此,类型并非是一个包含 3 个元素的数组,而是一个指针。
但如果我们使用正确的指针类型,那么内层的 for 就不对了,因为你无法把 i 当作一个数组,因为它只是一个指针,不能遍历。所以只能改成使用一般 for:
for(int *i:a){
for(int *j=i;j!=i+3;j++){
cout<<*j<
}
}
但是等等,让我们回忆一下之前的范围 for 的介绍。我们提到过,如果想要修改,可以创建引用,那么就不会拷贝赋值了。
不会拷贝?仔细想想这意味着什么:
创建的引用指向的是原来的数组中的数组,也就是为内层数组创建了别名!这样的话,自动转换为指针就不存在了(注意这里和之前一样,也得加上括号避免优先级问题):
int a[3][3]={};
for(int (&i)[3]:a){
for(int &j:i){
cout<
}
}
当然,你把最内层的引用去掉也没事。毕竟最内层本身就是个 int,不会再遍历了,加不加取决于你要不要修改。你也可以加上 const 防止修改,同时避免赋值性能损耗,这点我们之前也提到过了。
当然,你也可以用 auto,省点事情:
for(auto &i:a){
for(auto &j:i){
cout<
}
}
总结
对于数组和指针,我们已经非常深入了。现在你应该已经知道了遍历数组的多种方法,如何使用指针控制你的循环,同时用上省事的范围 for 和 auto。下一篇,我们终于将离开这堆你已经有了深入了解的数据海洋,去拆解更多其它 C++ 的知识,同样是奶奶级哦。