На цьому уроці ми розглянемо використання циклу foreach в мові С++.
Цикл foreach
На уроці про масиви і цикли ми розглядали приклади використання циклу for для здійснення ітерації по кожному елементу масиву. Наприклад:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> int main() { const int numStudents = 7; int scores[numStudents] = { 45, 87, 55, 68, 80, 90, 58 }; int maxScore = 0; // відстежуємо найвищий бал for (int student = 0; student < numStudents; ++student) if (scores[student] > maxScore) maxScore = scores[student]; std::cout << "The best score was " << maxScore << '\n'; return 0; } |
У той час як цикли for надають зручний і гнучкий спосіб ітерації по масиву, в них так само легко можна заплутатися і наробити «помилок неврахованих одиниць».
Тому в C++11 додали новий тип циклу — foreach (або «цикл діапазону»), який надає простіший і безпечніший спосіб ітерації по масиву (або по будь-якій іншій структурі типу списка).
Синтаксис циклу foreach наступний:
for (оголошення_елемента: масив)
стейтмент;
Виконується ітерація по кожному елементу масиву, присвоюючи значення поточного елементу масиву змінній, оголошеній як елемент (оголошення_елемента). З метою поліпшення продуктивності оголошений елемент повинен бути того ж типу, що й елементи масиву, інакше станеться неявна конвертація. Розглянемо простий приклад використання циклу foreach для виводу всіх елементів масиву math на екран:
|
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { int math[] = { 0, 1, 4, 5, 7, 8, 10, 12, 15, 17, 30, 41}; for (int number : math) // ітерація по масиву math std::cout << number << ' '; // отримуємо доступ до елементу масиву в цій ітерації через змінну number return 0; } |
Результат виконання програми:
0 1 4 5 7 8 10 12 15 17 30 41
Розглянемо детально, як це все працює. При виконанні циклу foreach змінній number присвоюється значення першого елементу (тобто значення 0). Далі програма виконує стейтмент виводу значення змінної number, тобто нуля. Потім цикл виконується знову, і значенням змінної number вже є 1 (другий елемент масиву). Вивід значення number виконується знову. Цикл продовжує своє виконання до тих пір, поки в масиві не залишиться непройдених елементів. В кінці виконання програма повертає 0 назад в операційну систему за допомогою оператора return.
Зверніть увагу, змінна number не є індексом масиву. Їй просто присвоюється значення елементу масиву в поточній ітерації циклу.
Цикл foreach і ключеве слово auto
Оскільки оголошений елемент циклу foreach повинен бути того ж типу, що й елементи масиву, то це ідеальний випадок для використання ключового слова auto, коли ми дозволяємо мові C++ визначити тип даних елементів масиву замість нас. Наприклад:
|
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { int math[] = { 0, 1, 4, 5, 7, 8, 10, 12, 15, 17, 30, 41 }; for (auto number : math) // тип number визначається автоматично, ґрунтуючись на типі елементів масиву math std::cout << number << ' '; return 0; } |
Цикл foreach і посилання
У прикладах, наведених вище, оголошений елемент завжди є змінною:
|
1 2 3 |
int array[7] = { 10, 8, 6, 5, 4, 3, 1 }; for (auto element: array) // element буде копією поточного елементу масиву std::cout << element << ' '; |
Тобто кожний опрацьований елемент масиву копіюється в змінну element, а це копіювання може виявитися витратним. В більшості випадків ми можемо просто посилатися на вихідний елемент за допомогою посилання:
|
1 2 3 |
int array[7] = { 10, 8, 6, 5, 4, 3, 1 }; for (auto &element: array) // символ амперсанда робить element посиланням на поточний елемент масиву, запобігаючи виконанню копіювання std::cout << element << ' '; |
У прикладі, наведеному вище, в якості оголошеного елемента циклу foreach використовується посилання на поточний елемент масиву, при цьому копіювання цього елементу не відбувається. Але, вказуючи звичайне посилання, будь-які зміни елементу впливатимуть на сам масив, що не завжди є бажано.
Звичайно ж, хорошою ідеєю буде зробити оголошений елемент константою, тоді ви зможете його використовувати в режимі «тільки для читання»:
|
1 2 3 |
int array[7] = { 10, 8, 6, 5, 4, 3, 1 }; for (const auto &element: array) // element - це константне посилання на поточний елемент масиву в ітерації std::cout << element << ' '; |
Правило: Використовуйте звичайні посилання або константні посилання в якості оголошеного елементу в циклі foreach (в цілях поліпшення продуктивності).
Ще один приклад
Ось приклад першої програми з початку цього уроку, але вже з використанням циклу foreach:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> int main() { const int numStudents = 7; int scores[numStudents] = { 45, 87, 55, 68, 80, 90, 58}; int maxScore = 0; // відстежуємо індекс найбільшого score (значення) for (const auto &score: scores) // ітерація по масиву, присвоюємо кожне значення масиву по черзі змінній score if (score > maxScore) maxScore = score; std::cout << "The best score was " << maxScore << '\n'; return 0; } |
Зверніть увагу, тут нам вже не потрібно вручну прописувати індексацію масиву. Ми можемо отримати доступ до кожного елементу масиву безпосередньо через змінну score.
Цикл foreach і не масиви
Цикли foreach працюють не тільки з фіксованими масивами, але також і з багатьма іншими структурами: вектори (наприклад, std::vector), зв’язані списки, дерева тощо. Не турбуйтеся, якщо ви не знаєте, що це таке, просто пам’ятайте, що цикли foreach забезпечують гнучкий і зручний спосіб ітерації не тільки по масивах:
|
1 2 3 4 5 6 7 8 9 10 11 |
#include <vector> #include <iostream> int main() { std::vector<int> math = { 0, 1, 4, 5, 7, 8, 10, 12, 15, 17, 30, 41}; // зверніть увагу на використання std::vector замість фіксованого масиву for (const auto &number : math) std::cout << number << ' '; return 0; } |
Цикл foreach не працює з вказівниками на масив
Для ітерації по масиву, цикл foreach повинен знати довжину масиву. Оскільки масиви, які конвертуються у вказівник, не знають своєї довжини, то цикли foreach з ними працювати не можуть!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> int sumArray(int array[]) // array - це вказівник { int sum = 0; for (const auto &number : array) // помилка компіляції, розмір масиву невідомий sum += number; return sum; } int main() { int array[7] = { 10, 8, 6, 5, 4, 3, 1 }; std::cout << sumArray(array); // array конвертується у вказівник тут return 0; } |
З цієї ж причини цикли foreach не працюють з динамічними масивами.
Чи можна отримати індекс поточного елементу?
Цикли foreach не пропонують прямий спосіб отримання індексу поточного елементу масиву. Це пов’язано з тим, що більшість структур, з якими можуть використовуватися цикли foreach (наприклад, зв’язані списки), напряму не індексуються!
Висновки
Цикли foreach забезпечують кращий синтаксис для ітерації по масиву, коли нам потрібно отримати доступ до всіх елементів масиву в послідовному порядку. Ці цикли краще використовувати замість стандартних циклів for в тих випадках, коли вони (цикли foreach) можуть використовуватися. Для запобігання створенню копій кожного елементу, в якості оголошеного елемента слід використовувати посилання.
Тест
Це повинно бути легко!
Оголосіть фіксований масив з наступними іменами: Sasha, Ivan, John, Orlando, Leonardo, Nina, Anton і Molly. Попросіть користувача ввести ім’я. Використовуйте цикл foreach для перевірки того, чи не знаходиться ім’я, введене користувачем, вже в масиві.
Приклад результату виконання програми:
Enter a name: Sasha
Sasha was found.
Enter a name: Masha
Masha was not found.
Підказка: Використовуйте std::string в якості типу масива.
Відповідь
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> #include <string> int main() { const std::string names[] = { "Sasha", "Ivan", "John", "Orlando", "Leonardo", "Nina", "Anton", "Molly" }; std::cout << "Enter a name: "; std::string username; std::cin >> username; bool found(false); for (const auto &name : names) if (name == username) { found = true; break; } if (found) std::cout << username << " was found.\n"; else std::cout << username << " was not found.\n"; return 0; } |

Добрий вечір. Мені прийшло в голову першим таке рішення. Чи нормальне таке використання return?