Как работает диапазон на основе для простых массивов? - PullRequest
72 голосов
/ 29 октября 2011

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

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

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

Ответы [ 5 ]

48 голосов
/ 29 октября 2011

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

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 в качестве членов, если вы передаете объект класса или (если члены не называются таким образом) функции, не являющиеся членами.Эти функции дадут начальный и конечный итераторы (указывающие сразу после последнего элемента и начала последовательности соответственно).

Этот вопрос проясняет, почему существует такая разница.

30 голосов
/ 11 октября 2014

Я думаю, что наиболее важной частью этого вопроса является то, как 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;
16 голосов
/ 16 сентября 2012

В соответствии с последним C ++ Working Draft (n3376) оператор for ranged эквивалентен следующему:

{
    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.

3 голосов
/ 29 октября 2011

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

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

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

2 голосов
/ 19 августа 2015

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

Это читать как " Скажите, что делает дальний поиск (с массивами)? "

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

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] раз.Каждый цикл создает одно из [3] *1021* первичных значений, начиная с первого и заканчивая последним. - Массив, содержащий [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. Итак, 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, которая автоматически рассчитывает на вас, но не имеет способа извлечь текущий цикл, не делая это вручную.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...