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


87

在C ++ 11中,您可以使用基于范围的for,它foreach与其他语言一样。它甚至适用于纯C数组:

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

它怎么知道什么时候停止?它仅适用于在相同范围内声明的静态数组for吗?您如何for在动态数组中使用它?


10
C或C ++本身不存在“动态”数组-存在数组类型,然后有可能指向或可能不指向数组或动态分配的内存块的指针,这些内存块的行为大致类似于数组。对于任何类型为T [n]的数组,其大小均以该类型进行编码,并且可以通过进行访问for。但是数组衰减到指针的那一刻,大小信息丢失了。
JohannesD

1
在你的榜样,元素的数量numberssizeof(numbers)/sizeof(int),例如。
JohannesD

Answers:


57

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

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

要进行更详细的说明,如果传递到右边的表达式的类型:是数组类型,则循环从迭代ptrptr + sizeptr指向数组的第一个元素,即数组size的元素计数)。

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

这个问题弄清楚了为什么存在这种差异。


8
我认为问题在于它是如何工作的,而不是它何时工作
sehe 2011年

1
@sehe该问题包含多个“?”。其中一个是“它可以与...一起使用吗?”。我解释了它的工作方式时间
Johannes Schaub-litb 2011年

8
@JohannesSchaub:我认为这里的“如何”问题首先是如何精确地获得数组类型对象的大小(由于指针与数组的混淆,并非所有人都知道数组的大小可供程序员使用。)
JohannesD

我相信这只是看起来非会员begin'结束. It just happens that 的std ::开始`std::end使用成员函数,如果一个更好的匹配不可将被使用。
2011年

3
@Dennis no在马德里,决定改变这一点,并青睐开始和结束成员。不喜欢开始和结束成员造成的歧义很难避免。
约翰尼斯·绍布

43

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

6
这确实是我最初要问的问题。:)
保罗·曼塔

19

根据最新的C ++ Working Draft(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;
};

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

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

在我看来,此语法比您将使用的语法std::for_each或普通for循环更清楚。


3

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

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

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


4
“动态数组”将是使用创建的new[]。在这种情况下,您只有一个没有大小指示的指针,因此基于范围的方法for无法使用它。
迈克·西摩

我的答案包括一个动态数组,其大小(4)在编译时已知,但是我不知道对“动态数组”的这种解释是否是发问者想要的。
Johannes Schaub-litb 2011年

3

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

是这样写的:“告诉我(带数组的)range-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&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];

一些其他信息

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

在以下示例中,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自动为您计数的“速记”循环,但是缺少一种无需手动执行即可检索当前循环的方法。


@安迪10的9倍 夺冠与Google /任何搜索中的内容相匹配-标题询问它们如何工作?,不是什么时候知道何时停止?。即使这样,隐含的潜在问题在一定程度上包含在此答案中,并继续为寻找其他答案的其他人回答。诸如此类的语法问题应加上标题,以便仅使用答案即可写出答案,因为这是搜索者查找问题所需要的全部信息。您当然没有错-这个问题的标题不应该是正确的。
超级猫

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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.