为什么要在嵌套的基于范围的 for 循环中引用
Why referencing in a nested ranged-based for-loops
我是C++的新手。
- 为什么我们不能遍历
int*
? &
在这里有什么用?- 这种基于嵌套范围的 for 如何深入执行?
.
int arr[10][3];
for (auto &i : arr)
{
for (auto j : i)
{
//sth
}
}
首先,我们需要知道int arr[10][3];
的确切数据类型。它是一个由 10 个数组组成的数组,每个数组 3 个int
.
循环通常遍历多维容器的一个维度,例如
for(int i = 0; i < 10; ++i)
{
for(int j = 0; j < 3; ++j)
{
arr[i][j] = 0;
}
}
第一个循环遍历 10 个X
数组,第二个循环遍历 X
,这里是一个 3 个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;
}
}
int (&x)[3]
行声明了对 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>>
这样的容器是不可能的,即使
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;
}
}
使用基于范围的 for 循环的全部意义在于摆脱显式迭代器。 int*
就是这样一个迭代器,因此没有必要使用基于范围的 for 循环遍历int*
IMO。
这种基于范围的嵌套 for 如何深入执行?
C++语言标准在 [stmt.ranged] 中定义了基于范围的 for 语句,如下所示(请注意,我已经简化了一点(:
for (
范围声明:
表达式)
语句
已解决:
{
for ( auto __begin = /*begin-expr*/,
__end = /*end-expr*/;
__begin != __end;
++__begin )
{
/*for-range-declaration*/ = *__begin;
/*statement*/
}
}
其中 for-range 声明和语句本质上是从未解析的基于范围的 for 循环中复制粘贴的。其余的(begin-expr,end-expr(有一些复杂性,这是一个简化版本:
{
using std::begin;
using std::end;
for ( auto __begin = begin(/*expression*/),
__end = end(/*expression*/);
__begin != __end;
++__begin )
{
/*for-range-declaration*/ = *__begin;
/*statement*/
}
}
我的基于范围的 for 循环示例是从
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)
中的&
是必需的:
int arr[10][3];
int (&x)[3] = arr[0]; // well-formed
int x [3] = arr[0]; // ill-formed for arrays
不允许直接分配原始/C 样式的数组,正如您可能从示例中知道的那样
int my_arr[10];
int my_sec_arr[10] = my_arr; // not legal, ill-formed
这就是为什么你必须使用引用。
使用其他容器,如标准库的std::array
,可以避免引用:
std::array<int, 10> my_arr;
std::array<int, 10> my_sec_arr = my_arr; // well-formed
但是赋值意味着复制,所以必须复制整个数组;而这里的引用不需要复制。
正如 Yakk 在评论中指出的那样,这并不是在您的示例中需要&
的原因 for (auto &i : arr)
,因为auto &i = arr[0];
已解析为int (*i)[3] = arr[0];
。但如您所见,auto
数组衰减为指针,因此第二次迭代失败:
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?
{
/* ... */
}
}
更准确地说:你可以迭代一个数组,因为编译器知道数组中有多少元素;它是类型的一部分,例如 3 int
数组,并且编译器知道该类型。
编译器不知道指针是引用单个元素还是元素数组,在后一种情况下,它不知道该数组有多大。在任何情况下,类型都只是,例如指向int
的指针:
int my_arr[10];
int my_int;
int *p;
p = my_arr;
p = &my_int;
p = new int[25];
- 在基于范围的for循环中使用结构化绑定声明
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 基于范围的 for 循环:迭代使用一个元素扩展的向量
- 基于范围的 for 循环unordered_map和引用
- C++基于范围的 for 循环和元素副本
- 实现基于链表的堆栈的基于范围的 for 循环
- 在基于范围的 for 循环期间插入 std::list 的后面
- 基于范围的 for 循环range_declaration中各种说明符之间的性能差异
- 避免在基于反向范围的for循环实现中悬挂参考
- C++ - 使用基于范围的 for 循环将字符值分配给向量中的字符串不会分配值
- 转到基于范围的 for 循环中的下一个迭代器
- 使用基于数组和范围的 For 循环替换一些基本代码行
- C++:返回一个基于范围 for 循环迭代器,其中包含继承对象
- DEV C++ 第 69 行 [错误] ISO "for" 范围更改了"i"的名称查找 [-允许]
- 迭代在包含向量的vor in for for范围循环中迭代
- 错误:ISO 'for'范围更改了'i'的名称查找
- 错误消息:"jj"的名称查找已更改为ISO"for"范围,(如果您使用"
- toupper() 在 for 范围循环中不起作用
- for范围循环如何推断普通数组的大小?
- "a"的名称查找更改为新的 ISO"for"范围