Как работает range-based для простых массивов?


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

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

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

5 64

5 ответов:

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

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

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

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

согласно последнему рабочему проекту C++ (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;
};

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

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

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

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

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

для получения дополнительной информации, в стандарте C++11 (или общественные проекты его), "6.5.4 диапазона на основе for заявление", ПГ.145

как работает range-based для простых массивов?

это читать как "скажите мне, что ranged-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 ' s [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. Итак,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 цикл, который автоматически считается для вас, но не имеет способа получить текущий цикл, не делая это вручную.