Как диапазон основан на работе для простых массивов?

В С++ 11 вы можете использовать for на основе диапазона, который действует как foreach других языков. Он работает даже с обычными массивами C:

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

Как он знает, когда остановиться? Работает ли он только со статическими массивами, которые были объявлены в той же области, в которой используется for? Как бы вы использовали этот for с динамическими массивами?

Ответ 1

Он работает для любого выражения, тип которого является массивом. Например:

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

Для более подробного объяснения, если тип выражения, переданного справа от :, является типом массива, тогда цикл выполняет итерацию от ptr до ptr + size (ptr, указывая на первый элемент массив, size является числом элементов массива).

Это отличается от пользовательских типов, которые работают, просматривая begin и end как члены, если вы передаете объект класса или (если нет членов, называемых таким образом), не являющихся членами. Эти функции будут давать итераторы начала и конца (указывающие непосредственно после последнего элемента и начало последовательности соответственно).

Этот вопрос подтверждает, почему эта разница существует.

Ответ 2

Я думаю, что самая важная часть этого вопроса заключается в том, как С++ знает, какой размер массива (по крайней мере, я хотел знать его, когда нашел этот вопрос).

С++ знает размер массива, поскольку он является частью определения массива - это тип переменной. Компилятор должен знать тип.

Так как С++ 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;

Ответ 3

В соответствии с последним рабочим проектом С++ (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;
};

Этот шаблон шаблона затем можно использовать для создания диапазона, по которому вы можете выполнять итерацию с использованием нового синтаксиса для диапазона. Я использую это для запуска всех объектов анимации в сцене, которая импортируется с использованием библиотеки, которая возвращает только указатель на массив и размер как отдельные значения.

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

Этот синтаксис, на мой взгляд, гораздо яснее, чем то, что вы получили бы с помощью std::for_each или простого цикла for.

Ответ 4

Он знает, когда остановиться, потому что он знает границы статических массивов.

Я не уверен, что вы подразумеваете под "динамическими массивами", в любом случае, если не итерировать по статическим массивам, неформально компилятор ищет имена begin и end в области класса объекта, который вы перебираете, или ищет begin(range) и end(range), используя зависящий от аргумента поиск и использует их как итераторы.

Для получения дополнительной информации в стандарте С++ 11 (или публичном проекте) "6.5.4 Операция for на основе диапазона", стр. 145

Ответ 5

Как работает диапазон для работы с равными массивами?

Является ли это следующим: "Скажи мне, что делает диапазон (с массивами)?"

Я отвечу на предположение, что - возьмите следующий пример, используя вложенные массивы:

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 equals {1,2,3,4} - массив
  • Второй цикл: pl equals {5,6,7,8} - массив
  • Третий цикл: pl equals {9,10,11,12} - массив

Прежде чем мы объясним процесс, вот несколько дружественных напоминаний о массивах:

  • Массивы интерпретируются как указатели на их первое значение. Использование массива без какой-либо итерации возвращает адрес первого значения
  • pl должен быть ссылкой, потому что мы не можем копировать массивы
  • С массивами, когда вы добавляете число в объект массива, он продвигает вперед много раз и "точки" к эквивалентной записи. Если n - это число, о котором идет речь, то ia[n] совпадает с *(ia+n) (Мы разыскиваем адрес, в котором n записи пересылаются вперед), а ia+n совпадает с &ia[n] (мы получаем адрес этой записи в массиве).

Вот что происходит:

  • В каждом цикле pl устанавливается как ссылка на ia[n], при этом n равен текущему количеству циклов, начиная с 0. Итак, pl - ia[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 цикл, который автоматически подсчитывается для вас, но не имеет способа получить текущий цикл, не делая его вручную.

Ответ 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 on Stack
  int StackInts[Size];        // 5 int 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 on Heap
  int *HeapInts = new int[Size];            // 5 int on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square 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
}