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

时间:2013-07-28 11:52:26

标签: c++ c++11

我是C ++的新手。为什么我们不能遍历int*,这里有&的用法,这个嵌套的基于范围的表现如何深入?

int arr[10][3];

for (auto &i : arr)
{
    for (auto j : i)
    {
        //sth
    }
}

1 个答案:

答案 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-declaration for ( 表达 : 声明

解决了:

)

其中 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