Як працює діапазон для роботи для звичайних масивів?


87

У C ++ 11 ви можете використовувати діапазон for, який діє як foreachінші мови. Це працює навіть із простими масивами C:

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

Звідки воно знає, коли зупинятись? Чи працює це лише зі статичними масивами, які були оголошені в тому самому обсязі, в якому forвикористовується? Як би ви використали це forз динамічними масивами?


10
У C або C ++ як такої не існує "динамічних" масивів - існують типи масивів, а потім є покажчики, які можуть вказувати або не вказувати на масив або динамічно виділений блок пам'яті, який здебільшого поводиться як масив. Для будь-якого масиву типу T [n] його розмір кодується у типі, і до нього можна отримати доступ for. Але в той момент, коли масив перетворюється на покажчик, інформація про розмір втрачається.
JohannesD

1
У вашому прикладі, наприклад, кількість елементів у numbersє sizeof(numbers)/sizeof(int).
JohannesD

Відповіді:


57

Він працює для будь-якого виразу, типом якого є масив. Наприклад:

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

Це питання з’ясовує, чому існує ця різниця.


8
Я думаю, що питання полягало в тому, як це працює, а не коли це працює
sehe

1
@sehe питання містило кілька знаків "?". Один з них був "Чи працює це з ...?". Я пояснив, як і коли це працює.
Йоханнес Шауб - Litb

8
@JohannesSchaub: Я думаю , що «як» проблема тут в тому , як саме ви отримаєте розмір об'єкта типу масиву , в першу чергу (з - за покажчики проти масивів плутанини, хай ніхто не всі знають , що розмір масиву є доступний програмісту.)
JohannesD

Я вважаю , що це тільки шукає , який не є членом begin`кінця . It just happens that станд :: почати `std::endвикористовувати функції - члени, і буде використовуватися , якщо краще підходить не доступний.
Dennis Zickefoose

3
@Dennis no у Мадриді було вирішено змінити це і надавати перевагу початковому та кінцевому членам. Не надавати перевагу початковому та кінцевому членам викликало неясність, якої важко уникнути.
Йоханнес Шауб - Litb

43

Я думаю, що найважливішою частиною цього питання є те, як 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;

6
Це справді те, про що я спочатку запитував. :)
Пол Манта

19

Відповідно до останнього робочого проекту C ++ (n3376), діапазон для оператора еквівалентний наступному:

{
    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

Він знає, коли зупинитися, бо знає межі статичних масивів.

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

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


4
"Динамічний масив" буде створений за допомогою new[]. У такому випадку ви отримали лише вказівник без вказівки розміру, тому для діапазону немає можливості forпрацювати з ним.
Mike Seymour

Моя відповідь включає динамічний масив, розмір якого (4) відомий під час компіляції, але я не знаю, чи саме таке тлумачення "динамічного масиву" передбачало запитувача.
Йоханнес Шауб - Litb

3

Як працює діапазон для роботи для звичайних масивів?

Чи читати це як: " Скажи мені, що робить діапазон (з масивами)? "

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

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цикл, який автоматично підраховує вас, але не має способу отримати поточний цикл, не роблячи цього вручну.


@Andy 9 із 10 разів заголовок - це те, що збігається в Google / незалежно від пошукових запитів - Заголовок запитує, як вони працюють? , а не коли воно знає, коли зупинятись? . Тим не менш , основне питання мав на увазі буде покритий в цьому відповідь на який - то ступеня, і продовжує відповідати за кого - або ще , шукаючи інший відповідь. Такі запитання щодо синтаксису повинні мати заголовки, сформульовані таким чином, щоб відповідь могла бути написана лише за допомогою цього, оскільки це вся інформація, яка потрібна шукачеві для пошуку запитання. Ви, звичайно, не помиляєтесь - запитання не має назви як слід.
Super Cat

0

Деякі зразки коду, щоб продемонструвати різницю між масивами на Stack та масивами на Heap


/**
 * 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's on Stack
  int StackInts[Size];        // 5 int's 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's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's 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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.