На даному уроці ми розглянемо використання масивів з циклами, а також ті підводні камені, які можуть при цьому виникнути.
Навіщо використовувати цикли з масивами?
Розглянемо випадок, коли потрібно обчислити середній бал студентів в групі. Використовуючи окремі змінні:
1 2 3 4 5 6 7 8 9 |
const int numStudents = 5; int student0 = 73; int student1 = 85; int student2 = 84; int student3 = 44; int student4 = 78; int totalScore = student0 + student1 + student2 + student3 + student4; double averageScore = static_cast<double>(totalScore) / numStudents; |
Ми отримаємо багато оголошень змінних і, відповідно, багато коду — а це всього лише 5 студентів! А уявіть, якби їх було 30 чи 150.
Крім того, щоб додати нового студента, нам доведеться оголосити нову змінну, ініціалізувати її і додати в змінну totalScore
. І це все вручну. А кожен раз при зміні старого коду є ризик наробити нових помилок. А ось з використанням масиву:
1 2 3 4 |
const int numStudents = 5; int students[numStudents] = { 73, 85, 84, 44, 78}; int totalScore = students[0] + students[1] + students[2] + students[3] + students[4]; double averageScore = static_cast<double>(totalScore) / numStudents; |
Кількість оголошених змінних зменшиться, але в totalScore
як і раніше доведеться заносити кожен елемент масиву вручну. І, як зазначено вище, зміна кількості студентів означає, що формулу totalScore
необхідно буде змінювати також вручну.
От якби був спосіб автоматизувати цей процес.
Цикли і масиви
З попереднього уроку ми вже знаємо, що індекс масиву не обов’язково повинен бути константним значенням — він може бути і звичайною змінною. А це означає, що ми можемо використовувати лічильник циклу в якості індексу масиву для доступу до елементів і виконання з ними необхідних математичних та інших операцій. Це настільки поширена практика, що майже завжди при виявленні масиву, ви знайдете поруч з ним цикл! Коли цикл використовується для доступу до кожного елементу масиву по черзі, то це називаються ітерацією по масиву. Наприклад:
1 2 3 4 5 6 7 8 9 |
int students[] = { 73, 85, 84, 44, 78}; const int numStudents = sizeof(students) / sizeof(students[0]); int totalScore = 0; // Використовуємо цикл для обчислення totalScore for (int person = 0; person < numStudents; ++person) totalScore += students[person]; double averageScore = static_cast<double>(totalScore) / numStudents; |
Це рішення ідеально підходить як в плані зручності і читання, так і в плані підтримки. Оскільки доступ до кожного елементу масиву виконується через цикл, то формула підрахунку суми всіх значень автоматично налаштовується з урахуванням кількості елементів в масиві. І для обчислення середньої оцінки нам вже не потрібно буде вручну додавати нових студентів і індекси нових елементів масиву!
А ось приклад використання циклу для пошуку в масиві найбільшого значення (найкращої оцінки серед всіх студентів в групі):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> int main() { int students[] = { 73, 85, 84, 44, 78}; const int numStudents = sizeof(students) / sizeof(students[0]); int maxScore = 0; // відслідковуємо найвищу оцінку for (int person = 0; person < numStudents; ++person) if (students[person] > maxScore) maxScore = students[person]; std::cout << "The best score was " << maxScore << '\n'; return 0; } |
Ось тут вже використовується змінна maxScore
(не з циклу) для відслідковування найбільшого значення масиву. Спочатку ми ініціалізуємо maxScore
значенням 0
, що означає, що ми ще не бачили ніяких оцінок. Потім перебираємо кожен елемент масиву і, якщо знаходимо оцінку, яка більша за попередню, присвоюємо її значення змінній maxScore
. Таким чином, maxScore
завжди зберігатиме найбільше значення зі всіх елементів масиву.
Використання циклів з масивами
Цикли з масивами зазвичай використовуються для виконання одного з трьох наступних завдань:
Обчислити значення (наприклад, середнє арифметичне чи суму всіх значень).
Знайти значення (наприклад, найбільше чи найменше).
Відсортувати елементи масиву (наприклад, по зростанню чи по спаданню).
При обчисленні значення, змінна зазвичай використовується для зберігання проміжного результату, який використовується для обчислення кінцевого значення. У вищенаведеному прикладі, де ми обчислюємо середній бал, змінна totalScore
містить суму значень всіх розглянутих елементів.
При пошуку значення, змінна зазвичай використовується для зберігання найкращого варіанту (або індексу найкращого варіанту) з усіх переглянутих. У вищенаведеному прикладі, де ми використовуємо цикл для пошуку найбільшої оцінки, змінна maxScore
використовується для зберігання найбільшої кількості балів з переглянутих раніше елементів масиву.
Сортування масиву відбувається дещо складніше, оскільки в цьому процесі використовуються вкладені цикли (про це трохи пізніше).
Масиви і помилка неврахованої одиниці
Найскладнішим при використанні циклів з масивами є переконатися, що цикл виконується правильну кількість разів. Помилку неврахованої одиниці зробити легко, а спроба отримати доступ до елементу, індекс якого більший за довжину масиву, може мати найрізноманітніші наслідки. Розглянемо наступну програму:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> int main() { int students[] = { 73, 85, 84, 44, 78 }; const int numStudents = sizeof(students) / sizeof(students[0]); int maxScore = 0; // відслідковуємо найбільшу оцінку for (int person = 0; person <= numStudents; ++person) if (students[person] > maxScore) maxScore = students[person]; std::cout << "The best score was " << maxScore << '\n'; return 0; } |
Тут проблема полягає в неправильній умові оператора if в циклі for! Оголошений масив містить 5 елементів, проіндексованих від 0 до 4. Проте цикл всередині перебирає елементи від 0 до 5. Отже, на останній ітерації в циклі for виконається наступне:
1 2 |
if (students[5] > maxScore) maxScore = students[5]; |
Але ж students[5]
не визначений! Його значенням, швидше за все, буде сміття. І в результаті ми отримаємо помилковий maxScore
.
Проте уявіть, що сталося б, якби ми ненароком присвоїли значення елементу students[5]
! Ми могли б перезаписати іншу змінну (або її частину) або зіпсувати що-небудь — ці типи помилок дуже важко відстежити!
Отже, при використанні циклів з масивами, завжди перевіряйте умови в циклах, щоб переконатися, що їх виконання не призведе до помилки неврахованої одиниці.
Тест
Завдання №1
Виведіть на екран наступний масив, використовуючи цикл:
1 |
int array[] = { 7, 5, 6, 4, 9, 8, 2, 1, 3 }; |
Відповідь №1
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { int array[] = { 7, 5, 6, 4, 9, 8, 2, 1, 3 }; const int arrayLength = sizeof(array) / sizeof(array[0]); for (int index=0; index < arrayLength; ++index) std::cout << array[index] << " "; return 0; } |
Завдання №2
Використовуючи масив з завдання №1, попросіть користувача ввести число від 1 до 9. Якщо користувач введе що-небудь інше — попросіть його ввести число ще раз і так до тих пір, поки він не введе коректне значення з вказаного діапазону. Як тільки користувач введе число від 1 до 9, виведіть масив на екран. Потім знайдіть в масиві елемент з числом, яке ввів користувач, і виведіть його індекс.
Для обробки некоректного користувацького вводу ви можете використати наступний код:
1 2 3 4 5 6 |
// Якщо користувач ввів некоректне значення if (std::cin.fail()) { std::cin.clear(); std::cin.ignore(32767, '\n'); } |
Відповідь №2
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include <iostream> int main() { // Спочатку приймаємо коректний користувацький ввід int number = 0; do { std::cout << "Enter a number between 1 and 9: "; std::cin >> number; // Якщо користувач ввів некоректне значення if (std::cin.fail()) std::cin.clear(); std::cin.ignore(32767, '\n'); } while (number < 1 || number > 9); // Далі виводимо масив на екран int array[] = { 7, 5, 6, 4, 9, 8, 2, 1, 3 }; const int arrayLength = sizeof(array) / sizeof(array[0]); for (int index=0; index < arrayLength; ++index) std::cout << array[index] << " "; std::cout << "\n"; // Потім шукаємо в масиві число, яке ввів користувач і виводимо його індекс for (int index=0; index < arrayLength; ++index) { if (array[index] == number) { std::cout << "The number " << number << " has index " << index << "\n"; break; // оскільки кожен елемент в масиві унікальний, то немає необхідності в тому, щоб продовжувати перебирати елементи далі } } return 0; } |
Завдання №3
Змініть наступну програму так, щоб замість maxScore
з найбільшим значенням, змінна maxIndex
містила індекс елементу з найбільшим значенням:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> int main() { int scores[] = { 73, 85, 84, 44, 78 }; const int numStudents = sizeof(scores) / sizeof(scores[0]); 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; } |
Відповідь №3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> int main() { int scores[] = { 73, 85, 84, 44, 78 }; const int numStudents = sizeof(scores) / sizeof(scores[0]); int maxIndex = 0; // відслідковуємо найвищу оцінку for (int student = 0; student < numStudents; ++student) if (scores[student] > scores[maxIndex]) maxIndex = student; std::cout << "The best score: " << scores[maxIndex] << '\n'; std::cout << "Index of the best score: " << maxIndex << '\n'; return 0; } |