【问题标题】:How does the range-based for work for plain arrays?基于范围的 for 如何适用于普通数组?
【发布时间】:2011-12-17 20:55:43
【问题描述】:

在 C++11 中,您可以使用基于范围的 for,它充当其他语言的 foreach。它甚至适用于普通的 C 数组:

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

它如何知道何时停止?它是否仅适用于已在 for 使用的同一范围内声明的静态数组?您如何将这个 for 与动态数组一起使用?

【问题讨论】:

  • C 或 C++ 本身没有“动态”数组 - 有数组类型,然后有指针可能指向也可能不指向数组或动态分配的内存块表现得像一个数组。对于任何类型为 T[n] 的数组,它的大小在类型中编码并且可以通过for 访问。但是一旦数组衰减为指针,大小信息就会丢失。
  • 在您的示例中,numbers 中的元素数为 sizeof(numbers)/sizeof(int),例如。

标签: c++ arrays foreach c++11


【解决方案1】:

它适用于任何类型为数组的表达式。例如:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

为了更详细的解释,如果:右边传递的表达式的类型是数组类型,那么循环从ptr迭代到ptr + sizeptr指向第一个元素数组,size 是数组的元素计数)。

这与用户定义类型形成对比,用户定义类型通过查找 beginend 作为成员来工作,如果您传递一个类对象或(如果没有以这种方式调用的成员)非成员函数。这些函数将产生开始和结束迭代器(分别直接指向最后一个元素和序列的开始之后)。

This question 解释了为什么存在这种差异。

【讨论】:

  • 我认为问题是如何工作,而不是何时工作
  • @sehe 问题包含多个“?”。一个是“它适用于......吗?”。我解释了 如何何时 它的工作原理。
  • @JohannesSchaub:我认为这里的“如何”问题首先是如何准确地获得数组类型的对象的大小(因为指针与数组的混淆,几乎不是每个人知道数组的大小可供程序员使用。)
  • 我相信它寻找非成员 begin`end. It just happens that std::begin`std::end 使用成员函数,如果 a没有更好的匹配。
  • @Dennis no 在马德里,决定改变这一点并支持开始和结束成员。不支持开始和结束成员会导致难以避免的模棱两可。
【解决方案2】:

我认为这个问题最重要的部分是,C++如何知道数组的大小是多少(至少我发现这个问题的时候就想知道)。

C++ 知道数组的大小,因为它是数组定义的一部分——它是变量的类型。编译器必须知道类型。

由于C++11std::extent可以用来获取数组的大小:

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

当然,这没有多大意义,因为您必须在第一行明确提供大小,然后在第二行获得。但你也可以使用decltype,然后它会变得更有趣:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;

【讨论】:

  • 这确实是我最初要问的。 :)
【解决方案3】:

根据最新的 C++ 工作草案 (n3376),ranged for 语句等效于以下内容:

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

所以它知道如何以与使用迭代器的常规 for 循环相同的方式停止。

我认为您可能正在寻找类似以下的内容,以提供一种将上述语法与仅包含指针和大小的数组(动态数组)一起使用的方法:

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

然后可以使用此类模板创建一个范围,您可以使用新的 ranged for 语法对其进行迭代。我正在使用它来运行场景中的所有动画对象,该场景是使用库导入的,该库仅返回指向数组的指针和作为单独值的大小。

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

在我看来,这种语法比使用 std::for_each 或普通的 for 循环所得到的要清晰得多。

【讨论】:

    【解决方案4】:

    它知道何时停止,因为它知道静态数组的边界。

    我不确定“动态数组”是什么意思,无论如何,如果不迭代静态数组,编译器会在类的范围内查找名称 beginend您迭代的对象,或使用参数相关查找查找 begin(range)end(range) 并将它们用作迭代器。

    有关更多信息,请参阅 C++11 标准(或其公开草案),“6.5.4 基于范围的for 语句”,pg.145

    【讨论】:

    • 一个“动态数组”是用new[] 创建的。在这种情况下,您只有一个不指示大小的指针,因此基于范围的 for 无法使用它。
    • 我的答案包括一个动态数组,其大小 (4) 在编译时已知,但我不知道对“动态数组”的解释是否是提问者的意图。
    【解决方案5】:

    基于范围的 for 如何适用于普通数组?

    是不是读作“告诉我 ranged-for 做了什么(使用数组)?

    我会假设 - 以使用嵌套数组为例:

    int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    
    for (auto &pl : ia)
    

    文字版:

    ia 是一个数组数组(“嵌套数组”),包含[3] 数组,每个数组都包含[4] 值。上面的示例通过 ia 的主要“范围” ([3]) 循环,因此循环 [3] 次。每个循环都会产生一个 ia[3] 主要值,从第一个开始到最后一个结束 - 一个包含 [4] 值的数组。

    • 第一个循环:pl 等于 {1,2,3,4} - 一个数组
    • 第二个循环:pl 等于 {5,6,7,8} - 一个数组
    • 第三个循环:pl 等于 {9,10,11,12} - 一个数组

    在我们解释过程之前,这里有一些关于数组的友情提示:

    • 数组被解释为指向其第一个值的指针 - 使用不经过任何迭代的数组返回第一个值的地址
    • pl 必须作为参考,因为我们不能复制数组
    • 对于数组,当您向数组对象本身添加一个数字时,它会向前推进很多次并“指向”等效条目 - 如果n 是有问题的数字,那么ia[n]*(ia+n)(我们正在取消引用 n 条目的地址),ia+n&amp;ia[n] 相同(我们正在获取数组中该条目的地址)。

    这是发生了什么:

    • 在每个循环中,pl 设置为ia[n]引用n 等于从 0 开始的当前循环计数。因此,plia[0] on第一轮,第二轮是ia[1],以此类推。它通过迭代检索值。
    • 只要ia+n 小于end(ia),循环就会继续。

    ...就是这样。

    这真的只是一个简化的写法

    int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    for (int n = 0; n != 3; ++n)
      auto &pl = ia[n];
    

    如果你的数组不是嵌套的,那么这个过程会变得更简单一点,因为不需要需要引用,因为迭代的值不是数组而是而是一个“正常”值:

     int ib[3] = {1,2,3};
    
     // short
     for (auto pl : ib)
       cout << pl;
    
     // long
     for (int n = 0; n != 3; ++n)
       cout << ib[n];
    

    一些附加信息

    如果我们不想在创建pl 时使用auto 关键字怎么办?那会是什么样子?

    在以下示例中,pl 指的是 array of four integers。在每个循环上 pl 被赋予值 ia[n]

    int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    for (int (&pl)[4] : ia)
    

    而且...这就是它的工作原理,通过附加信息来消除任何混乱。它只是一个“速记”for 循环,它会自动为您计数,但缺少一种无需手动操作即可检索当前循环的方法。

    【讨论】:

    • @Andy title 10 次中有 9 次匹配 Google/任何搜索 - 标题询问 这些是如何工作的?,而不是它何时知道何时停止?。即便如此,隐含的基本问题 在某种程度上包含在此答案中,并继续为其他寻找 other 答案的人回答。诸如此类的语法问题应该有标题,以便可以单独使用它来编写答案,因为这是搜索者找到问题所需的所有信息。你当然没有错 - 这个问题的标题不应该是这样。
    【解决方案6】:

    一些示例代码来演示堆栈上的数组与堆上的数组之间的区别

    
    /**
     * Question: Can we use range based for built-in arrays
     * Answer: Maybe
     * 1) Yes, when array is on the Stack
     * 2) No, when array is the Heap
     * 3) Yes, When the array is on the Stack,
     *    but the array elements are on the HEAP
     */
    void testStackHeapArrays() {
      int Size = 5;
      Square StackSquares[Size];  // 5 Square's on Stack
      int StackInts[Size];        // 5 int's on Stack
      // auto is Square, passed as constant reference
      for (const auto &Sq : StackSquares)
        cout << "StackSquare has length " << Sq.getLength() << endl;
      // auto is int, passed as constant reference
      // the int values are whatever is in memory!!!
      for (const auto &I : StackInts)
        cout << "StackInts value is " << I << endl;
    
      // Better version would be: auto HeapSquares = new Square[Size];
      Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
      int *HeapInts = new int[Size];            // 5 int's on Heap
    
      // does not compile,
      // *HeapSquares is a pointer to the start of a memory location,
      // compiler cannot know how many Square's it has
      // for (auto &Sq : HeapSquares)
      //    cout << "HeapSquare has length " << Sq.getLength() << endl;
    
      // does not compile, same reason as above
      // for (const auto &I : HeapInts)
      //  cout << "HeapInts value is " << I << endl;
    
      // Create 3 Square objects on the Heap
      // Create an array of size-3 on the Stack with Square pointers
      // size of array is known to compiler
      Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
      // auto is Square*, passed as constant reference
      for (const auto &Sq : HeapSquares2)
        cout << "HeapSquare2 has length " << Sq->getLength() << endl;
    
      // Create 3 int objects on the Heap
      // Create an array of size-3 on the Stack with int pointers
      // size of array is known to compiler
      int *HeapInts2[]{new int(23), new int(57), new int(99)};
      // auto is int*, passed as constant reference
      for (const auto &I : HeapInts2)
        cout << "HeapInts2 has value " << *I << endl;
    
      delete[] HeapSquares;
      delete[] HeapInts;
      for (const auto &Sq : HeapSquares2) delete Sq;
      for (const auto &I : HeapInts2) delete I;
      // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-01
      • 2016-10-31
      • 1970-01-01
      • 1970-01-01
      • 2012-02-18
      • 1970-01-01
      • 2013-04-18
      相关资源
      最近更新 更多