我是C ++的新手。为什么我们不能遍历int*
,这里有&
的用法,这个嵌套的基于范围的表现如何深入?
int arr[10][3];
for (auto &i : arr)
{
for (auto j : i)
{
//sth
}
}
答案 0 :(得分:6)
首先,我们需要知道int arr[10][3];
的确切数据类型。它是一个包含10个int
的数组的数组。
循环通常遍历多维容器的一个维度,例如
for(int i = 0; i < 10; ++i)
{
for(int j = 0; j < 3; ++j)
{
arr[i][j] = 0;
}
}
第一个循环迭代10 X
的数组,然后第二个循环遍历X
,这里是一个3 {{1>的数组}}
下一步是在代码中明确使用此int
:
X
行for(int i = 0; i < 10; ++i)
{
int (&x)[3] = arr[i]; // you won't see this syntax often
for(int j = 0; j < 3; ++j)
{
int &elem = x[j];
elem = 0;
}
}
声明对3 int (&x)[3]
的数组的引用,这是访问多维数组int
的第一级的结果。
我们也可以使用迭代器编写这个例子:
arr
注意我在这里使用的功能是将数组转换为指向其第一个元素的指针。这称为衰减:一个数组/可以衰减为指针(指向该数组的第一个元素),例如
for(int (*px)[3] = arr; px != arr+10; ++px)
{
// `px` is a _pointer to an array of 3 `int`_
// `*px` then is an _array of 3 `int`_
for(int *pelem = *px; pelem != (*px)+3; ++pelem)
{
*pelem = 0;
}
}
对于多维数组,这变为
int my_arr[3];
int *p = my_arr; // `p` now points to the first element of `my_arr`
p = &my_arr[0]; // equivalent
最后但并非最不重要的是,对于多维数组,也可以编写:
int arr[10][3];
int (*p)[3]; // a pointer to an _array of 3 `int`_
p = arr; // `p` now points to the first element of `arr`, i.e.
// the first _array of 3 `int`_
但这只适用于多维数组,因为它们在内存中是连续布局的,并且指定了多维数组的内存布局。
对于像for(int *pelem = arr[0]; pelem != arr[0]+10*3; ++pelem)
{
*pelem = 0;
}
这样的容器来说,这是不可能的,即使
vector<vector<int>>
格式正确,没有未定义的行为。
同样的逻辑现在适用于基于范围的for循环:
vector<int> v = {1,2,3,4,5};
for(int* i = &v[0]; i != &v[0] + 5; ++i)
{
*i = 0;
}
基于范围的for循环的重点是摆脱显式迭代器。 for(int (&x)[3] : arr)
{
for(int &elem : x)
{
elem = 0;
}
}
就是这样一个迭代器,因此在int*
IMO上迭代基于范围的for循环是没有意义的。
这个嵌套的基于范围的表现如何深入?
C ++语言标准在[stmt.ranged]中定义了基于范围的for语句,如下所示(注意我已经简化了一点):
int*
for-range-declarationfor (
表达:
声明
解决了:
)
其中 for-range-declaration 和语句基本上是从未解析的基于范围的for循环中复制粘贴的。其余的( begin-expr , end-expr )有一些复杂性,这是一个简化版本:
{
for ( auto __begin = /*begin-expr*/,
__end = /*end-expr*/;
__begin != __end;
++__begin )
{
/*for-range-declaration*/ = *__begin;
/*statement*/
}
}
我从
解决了基于范围的for循环的示例{
using std::begin;
using std::end;
for ( auto __begin = begin(/*expression*/),
__end = end(/*expression*/);
__begin != __end;
++__begin )
{
/*for-range-declaration*/ = *__begin;
/*statement*/
}
}
到
for(int (&x)[3] : arr)
{
/*statements*/
}
或者,通过解析{
using std::begin;
using std::end;
for ( auto __begin = begin(arr),
__end = end(arr);
__begin != __end;
++__begin )
{
int (&x)[3] = *__begin;
/*statements*/
}
}
/ begin
来电:
end
标有{
for ( int (*__begin)[3] = arr,
__end = arr + 10;
__begin != __end;
++__begin )
{
int (&x)[3] = *__begin; // (A)
/*statements*/
}
}
的行也说明了为什么示例(A)
中的&
是必要的:
for (int x[3] : arr)
不允许直接分配原始/ C风格的数组,您可以从
等示例中了解到这一点int arr[10][3];
int (&x)[3] = arr[0]; // well-formed
int x [3] = arr[0]; // ill-formed for arrays
这就是你必须使用引用的原因。
使用标准库int my_arr[10];
int my_sec_arr[10] = my_arr; // not legal, ill-formed
等其他容器,可以避免引用:
std::array
但是分配意味着一个副本,所以必须复制整个数组;而这里的引用不需要复制。
正如Yakk指出in the comments,这不是你的示例std::array<int, 10> my_arr;
std::array<int, 10> my_sec_arr = my_arr; // well-formed
中&
所必需的原因,因为for (auto &i : arr)
已解析为auto &i = arr[0];
。但正如您所看到的,int (*i)[3] = arr[0];
将数组衰减为指针,因此您的第二次迭代失败:
auto
更精确一点:你可以迭代一个数组,因为编译器知道数组中有多少元素;它是该类型的一部分,例如 3 for(auto i : arr)
{
// type of `i` now is _pointer to an array of 3 `int`_
for(auto j : i) // can't iterate over a pointer: what are the boundaries?
{
/* ... */
}
}
的数组,编译器已知该类型。
对于指针,编译器不知道指针是指单个元素还是指向元素数组,而在后一种情况下,它不知道该数组有多大。任何情况下的类型都是,例如指向int
的指针:
int