基于范围的普通数组的工作原理如何?

时间:2011-10-29 14:08:11

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

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

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

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

6 个答案:

答案 0 :(得分:48)

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

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

有关更详细的说明,如果传递到:右侧的表达式的类型是数组类型,则循环从ptr迭代到ptr + size({{1指向数组的第一个元素,ptr是数组的元素数。)

这与用户定义的类型形成对比,后者通过查找sizebegin作为成员来工作,如果您传递类对象或(如果没有这样称为成员)非成员功能。这些函数将产生开始和结束迭代器(分别指向最后一个元素和序列的开头)。

This question清除了存在差异的原因。

答案 1 :(得分:29)

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

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

由于C ++ 11 std::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;

答案 2 :(得分:16)

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

{
    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循环更清晰。

答案 3 :(得分:3)

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

我不确定“动态数组”是什么意思,在任何情况下,如果不是在静态数组上进行迭代,非正式地,编译器会查找名称beginend迭代的对象类的范围,或者使用依赖于参数的查找查找begin(range)end(range)并将它们用作迭代器。

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

答案 4 :(得分:2)

  

基于范围的普通数组的工作原理如何?

是否可以读作&#34; 告诉我什么是ranged-for(带数组)?&#34;

我将回答假设 - 使用嵌套数组采用以下示例:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

文字版:

ia是一个数组数组(&#34;嵌套数组&#34;),包含[3]个数组,每个数组包含[4]个值。上面的示例通过它的主要&#39;范围循环ia。 ([3]),因此循环[3]次。每个循环生成ia [3]个主要值之一,从第一个开始到最后一个结束 - 包含[4]值的数组。

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

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

  • 数组被解释为指向其第一个值的指针 - 使用不带任何迭代的数组返回第一个值的地址
  • pl 必须作为参考,因为我们无法复制数组
  • 对于数组,当你向数组对象本身添加一个数字时,它会向前推进很多次并且#39;指向&#39;到等效条目 - 如果n是相关数字,则ia[n]*(ia+n)相同(我们会取消引用{{1}的地址}条目转发),nia+n相同(我们正在获取数组中该条目的地址)。

以下是

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

......那就是它。

它真的只是一种简化的方式来写这个

ia+n

如果你的数组没有嵌套,那么这个过程变得有点简单,因为引用,因为迭代值不是。一个阵列,而不是一个正常的&#39;值:

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]; 关键字,该怎么办?那会是什么样的?

在以下示例中,auto指的是pl。在每个循环pl上给出值array of four integers

pl

并且...它是如何运作的,有额外的信息可以消除任何混乱。它只是一个简短的缩写&#39; ia[n]循环会自动为您计算,但缺少一种方法来检索当前循环,而无需手动执行。

答案 5 :(得分:0)

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


/**
 * 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
}