gpt4 book ai didi

c++ - 为什么在嵌套的基于范围的 for 循环中引用

转载 作者:太空狗 更新时间:2023-10-29 20:26:53 26 4
gpt4 key购买 nike

我是 C++ 的新手。

  1. 为什么我们不能遍历 int*
  2. 这里的&有什么用?
  3. 这种嵌套的基于范围的 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*海事组织。


how does this nested range-based for perform, deeply?

C++ 语言标准在 [stmt.ranged] 中定义了基于范围的 for 语句如下(注意我已经简化了一点):

for ( for-range-declaration : expression ) statement

决议为:

{
for ( auto __begin = /*begin-expr*/,
__end = /*end-expr*/;
__begin != __end;
++__begin )
{
/*for-range-declaration*/ = *__begin;
/*statement*/
}
}

for-range-declarationstatement 本质上是从未解析的基于范围的 for 循环中复制粘贴的。其余的(begin-exprend-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指出in the comments ,这并不是 & 的原因在你的例子中是必要的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];

关于c++ - 为什么在嵌套的基于范围的 for 循环中引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17907913/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com