Как работает range-based для простых массивов?
в C++11 вы можете использовать диапазон на основе for
, который действует как foreach
других языках. Он работает даже с простыми массивами в C:
int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
n *= 2;
}
как он знает, когда остановиться? Работает ли он только со статическими массивами, которые были объявлены в той же области for
используется? Как бы вы использовали это for
с динамическими массивами?
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
цикл, который автоматически считается для вас, но не имеет способа получить текущий цикл, не делая это вручную.