На цьому уроці ми розглянемо використання циклу 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: Maruna
Maruna 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; } |