纯数组如何使用基于范围的for循环?

96

在C++11中,您可以使用基于范围的 for 循环,它起到了其他语言中 foreach 的作用。即使是普通的 C 数组,也可以使用这种循环:

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

它如何知道何时停止?它只能与在for中使用相同作用域内已声明的静态数组一起工作吗?如何在动态数组中使用这个for


11
在C或C++中本身没有所谓的“动态”数组,只有数组类型和指针类型,指针可能指向一个数组或一个行为类似于数组的动态分配内存块。对于任何类型为T[n]的数组,其大小被编码在类型中,并且可以通过 for 来访问。但是当数组衰变为指针时,大小信息就会丢失。 - JohannesD
2
在你的例子中,numbers 数组中元素的数量为 sizeof(numbers)/sizeof(int) - JohannesD
6个回答

64

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

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;
如果在range-based for语句中,冒号右侧表达式的类型是数组类型,那么循环将从ptrptr + size进行迭代(其中ptr指向数组的第一个元素,size为数组元素数量)。与之相反,如果传递类对象或(如果没有以这种方式称呼的成员)非成员函数,则用户定义类型通过查找成员beginend来工作。这些函数将产生begin和end迭代器(分别指向最后一个元素直接后面和序列的开头)。这个问题解释了为什么存在这种差异。

9
我认为问题是关于它如何工作,而不是何时它工作。 - sehe
1
@sehe 这个问题包含多个问号。其中一个是“它可以与...一起使用吗?”。我解释了它的工作方式以及何时工作。 - Johannes Schaub - litb
9
我认为这里的“如何”问题在于你如何准确地获取数组类型对象的大小(由于指针与数组混淆,不是每个人都知道程序员可以获得数组的大小)。 - JohannesD
我相信它仅仅寻找非成员的begin`end。只是恰好std::begin\std::end`使用成员函数,并且如果没有更好的匹配,则会被使用。 - Dennis Zickefoose
3
在马德里,决定改变这一点,支持起始和结束成员。不支持起始和结束成员会导致难以避免的歧义。 - Johannes Schaub - litb
显示剩余2条评论

57

我认为这个问题最重要的部分是,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;

8
这确实是我最初询问的内容。 :) - Paul Manta
数组大小,这正是我要找的答案。当你将旧的数组循环转换为foreach循环时,你会想知道如何在没有任何有关数组大小的附加信息的情况下在原始数组上工作...非常感谢您的好回答。 - SomethingSomething

20
根据最新的C++工作草案(n3376),范围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循环更清晰明了。


3
它知道何时停止,因为它知道静态数组的边界。如果不是遍历静态数组,则在非正式情况下,编译器会查找迭代对象所属类的作用域中的名称begin和end,或者使用参数相关查找查找begin(range)和end(range),并将它们用作迭代器。有关更多信息,在C++11标准(或其公共草案)中,“6.5.4 基于范围的for语句”,第145页。

5
使用new[]创建的数组被称为“动态数组”。在这种情况下,你只有一个指针而没有大小的指示,所以无法使用范围式的for循环。 - Mike Seymour
我的答案包括一个动态数组,其大小(4)在编译时已知,但我不知道“动态数组”的解释是否是提问者想要的。 - Johannes Schaub - litb

3

range-based for循环如何处理普通数组?

这是否可以理解为“告诉我range-based for循环(对于数组)的作用?”

我将回答并假设您已经知道以下使用嵌套数组的示例:

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

for (auto &pl : ia)
ia是一个包含[3]个数组的二维数组,每个数组包含[4]个值。上述示例通过其主要的“范围”([3])循��遍历ia,因此循环[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等同于&ia[n](我们正在获取数组中该条目的地址)。

这里发生了什么:

  • 在每次循环中,pl被设置为一个对ia[n]引用,其中n等于当前循环计数从0开始。因此,在第一轮中,plia[0],在第二轮中,它是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指的是一个四个整数的数组。在每次循环中,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 十次中有九次,标题 是在谷歌/任何搜索引擎中匹配的 - 标题询问 它们是如何工作的? 而非 它何时知道停止? 即使如此,隐含的底层问题在一定程度上也被回答了,接着解答了其他人寻找的另一个答案。这样的语法问题应该用短语命名标题,因为这是搜索者需要找到问题的所有信息。你当然没有错 - 问题的标题不应该这样写。 - Super Cat

1

一些示例代码,以展示堆栈上的数组与堆上的数组之间的区别。


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接