Как диапазон основан на работе для простых массивов?
В С++ 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
}