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

ato*_*red 3 c++ c++11

我是C++的新手.为什么我们不能迭代int*,&这里有什么用处,以及这个嵌套的基于范围的表现如何深入?

int arr[10][3];

for (auto &i : arr)
{
    for (auto j : i)
    {
        //sth
    }
}
Run Code Online (Sandbox Code Playgroud)

dyp*_*dyp 6

首先,我们需要知道确切的数据类型int arr[10][3];.它是一个包含10个3int数组的数组.

循环通常在多维容器的一个维度上迭代,例如

for(int i = 0; i < 10; ++i)
{
    for(int j = 0; j < 3; ++j)
    {
        arr[i][j] = 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个循环遍历10X数组,然后第二个循环迭代X,这里是3int数组.

下一步是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

该行int (&x)[3]声明对3int数组的引用,这是访问多维数组的第一级的结果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;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意我在这里使用的功能是将数组转换为指向其第一个元素的指针.这称为衰减:数组可以/可以衰减为指针(指向该数组的第一个元素),例如

int my_arr[3];
int *p = my_arr;  // `p` now points to the first element of `my_arr`
     p = &my_arr[0]; // equivalent
Run Code Online (Sandbox Code Playgroud)

对于多维数组,这就变成了

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`_
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的是,对于多维数组,还可以编写:

for(int *pelem = arr[0]; pelem != arr[0]+10*3; ++pelem)
{
    *pelem = 0;
}
Run Code Online (Sandbox Code Playgroud)

但这只适用于多维数组,因为它们在内存中连续布局,并且指定了多维数组的内存布局.

vector<vector<int>>尽管如此,对于容器来说这是不可能的

vector<int> v = {1,2,3,4,5};
for(int* i = &v[0]; i != &v[0] + 5; ++i)
{
    *i = 0;
}
Run Code Online (Sandbox Code Playgroud)

形式良好,没有未定义的行为.


同样的逻辑现在适用于基于范围的for循环:

for(int (&x)[3] : arr)
{
    for(int &elem : x)
    {
        elem = 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

具有基于范围的for循环的重点是摆脱显式迭代器.int*是一个这样的迭代器,所以没有必要让一个基于范围的for循环迭代int*IMO.


这个嵌套的基于范围的表现如何深入?

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

for ( for-range-declaration : 表达式 ) 语句

决心:

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

用于范围申报声明为基础的范围内循环基本上是从复制粘贴的解决.其余的(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*/
    }
}
Run Code Online (Sandbox Code Playgroud)

我从一个基于范围的for循环的例子中解决了

for(int (&x)[3] : arr)
{
    /*statements*/
}
Run Code Online (Sandbox Code Playgroud)

{
    using std::begin;
    using std::end;

    for ( auto __begin = begin(arr),
               __end = end(arr);
          __begin != __end;
          ++__begin )
    {
        int (&x)[3] = *__begin;
        /*statements*/
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,通过解析begin/ end调用:

{
    for ( int (*__begin)[3] = arr,
               __end = arr + 10;
          __begin != __end;
          ++__begin )
    {
        int (&x)[3] = *__begin;           // (A)
        /*statements*/
    }
}
Run Code Online (Sandbox Code Playgroud)

标有a的行(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
Run Code Online (Sandbox Code Playgroud)

不允许直接分配原始/ C风格的数组,正如您可能从例如

int my_arr[10];
int my_sec_arr[10] = my_arr;  // not legal, ill-formed
Run Code Online (Sandbox Code Playgroud)

这就是你必须使用参考的原因.

使用标准库等其他容器std::array,可以避免引用:

std::array<int, 10> my_arr;
std::array<int, 10> my_sec_arr = my_arr;  // well-formed
Run Code Online (Sandbox Code Playgroud)

但是分配意味着一个副本,所以必须复制整个数组; 而这里的引用不需要复制.


正如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?
    {
        /* ... */
    }
}
Run Code Online (Sandbox Code Playgroud)

更精确一点:你可以迭代一个数组,因为编译器知道数组中有多少元素; 它是类型的一部分,例如3的数组int,并且编译器已知该类型.

对于指针,编译器不知道指针是指单个元素还是指向元素数组,而在后一种情况下,它不知道该数组有多大.任何情况下的类型都是,例如指向int:

int my_arr[10];
int my_int;

int *p;
p = my_arr;
p = &my_int;
p = new int[25];
Run Code Online (Sandbox Code Playgroud)