Крім динамічного виділення змінних ми також можемо динамічно виділяти і масиви. На відміну від фіксованого масиву, де його розмір повинен бути відомий під час компіляції, динамічне виділення масиву в мові C++ дозволяє нам встановлювати довжину масиву під час виконання програми.
Динамичні масиви
Для виділення динамічного масиву і роботи з ним використовуються окремі форми операторів new і delete: new[]
і delete[]
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> int main() { std::cout << "Enter a positive integer: "; int length; std::cin >> length; int *array = new int[length]; // використовуємо оператор new[] для виділення масиву. Зверніть увагу, змінна length не обов'язково повинна бути константою! std::cout << "I just allocated an array of integers of length " << length << '\n'; array[0] = 7; // присвоюємо елементу під індексом 0 значення 7 delete[] array; // використовуємо оператор delete[] для звільнення виділеної масиву пам'яті array = 0; // використовуйте nullptr замість 0 в C++11 return 0; } |
Оскільки ми виділяємо масив, то C++ розуміє, що він повинен використовувати іншу форму оператора new — форму для масиву, а не для змінної. По факту, викликається оператор new[]
, навіть якщо ми і не вказуємо []
відразу після ключового слова new
.
Зверніть увагу, оскільки пам’ять для динамічних і фіксованих масивів виділяється з різних “резервуарів”, то розмір динамічного масиву може бути досить великим. Ви можете запустити вищенаведену програму, але вже виділити масив довжиною 1 000 000 (або, можливо, навіть 100 000 000) елементів без проблем. Спробуйте!
Видалення динамічного масиву
При видаленні динамічних масивів також використовується форма оператора delete для масивів — delete[]
. Таким чином, ми повідомляємо процесору, що йому потрібно очистити пам’ять від декількох змінних замість однієї. Найпоширеніша помилка, яку роблять новачки при роботі з динамічним виділенням пам’яті, є використання delete
замість delete[]
для видалення динамічних масивів. Використання форми оператора delete для змінних при видаленні масиву призведе до таких несподіваних результатів, як пошкодження даних, витік пам’яті, збій або інші проблеми.
Ініціалізація динамічного масиву
Якщо ви хочете ініціалізувати динамічний масив значенням 0
, то все досить просто:
1 |
int *array = new int[length](); |
До C++11 не було простого способу ініціалізувати динамічний масив ненульовими значеннями (список ініціалізаторів працював тільки з фіксованими масивами). А це означає, що потрібно перебрати кожен елемент масиву і явно присвоїти йому значення:
1 2 3 4 5 6 |
int *array = new int[5]; array[0] = 9; array[1] = 7; array[2] = 5; array[3] = 3; array[4] = 1; |
Трохи стомлює, чи не так?
Однак, починаючи з C++11, з’явилася можливість ініціалізації динамічних масивів через списки ініціалізаторів:
1 2 |
int fixedArray[5] = { 9, 7, 5, 3, 1 }; // ініціалізуємо фіксований масив int *array = new int[5] { 9, 7, 5, 3, 1 }; // ініціалізуємо динамічний масив |
Зверніть увагу, в синтаксисі динамічного масиву між довжиною масиву і списком ініціалізаторів оператора присвоювання (=
) немає.
У C++11 фіксовані масиви також можуть бути ініціалізовані з використанням uniform-ініціалізації:
1 2 |
int fixedArray[5] { 9, 7, 5, 3, 1 }; // ініціалізуємо фіксований масив в C++11 char fixedArray[14] { "Hello, world!" }; // ініціалізуємо фіксований масив в C++11 |
Однак, будьте обережні, так як в C++11 ви не можете ініціалізувати динамічний масив символів рядком C-style:
1 |
char *array = new char[14] { "Hello, world!" }; // не працює в C++11 |
Замість цього ви можете динамічно виділити std::string (або виділити динамічний масив символів, а потім за допомогою функції strcpy_s() скопіювати вміст потрібного рядка в цей масив).
Також зверніть увагу на те, що динамічні масиви повинні бути оголошені з явним зазначенням їх довжини:
1 2 3 4 5 |
int fixedArray[] {1, 2, 3}; // ок: неявне зазначення довжини фіксованого масиву int *dynamicArray1 = new int[] {1, 2, 3}; // не ок: неявне зазначення довжини динамічного масиву int *dynamicArray2 = new int[3] {1, 2, 3}; // ок: явне зазначення довжини динамічного масиву |
Зміна довжини масиву
Динамічне виділення масивів дозволяє задавати довжину масивів під час операції їх виділення. Однак C++ не надає вбудований спосіб зміни довжини масиву, який вже був виділений. Але і це обмеження можна обійти, динамічно виділивши новий масив, скопіювавши всі елементи зі старого масиву, а потім видаливши старий масив. Однак цей спосіб вразливий до помилок (про це трохи пізніше).
На щастя, в мові C++ є масиви, розмір яких можна змінювати, і називаються вони векторами (std::vector). Про них ми поговоримо на відповідному уроці.
Тест
Напишіть програму, яка:
запитує у користувача, скільки імен він хоче ввести;
просить користувача ввести кожне ім’я;
викликає функцію для сортування імен в алфавітному порядку (ви можете змінити код сортування методом вибору з уроку №80);
виводить відсортований список імен на екран.
Підказки:
Використовуйте динамічне виділення std::string для зберігання імен.
std::string підтримує порівняння рядків за допомогою операторів порівняння <
і >
.
Приклад результату виконання вашої програми:
How many names would you like to enter? 5
Enter name #1: Jason
Enter name #2: Mark
Enter name #3: Alex
Enter name #4: Chris
Enter name #5: John
Here is your sorted list:
Name #1: Alex
Name #2: Chris
Name #3: Jason
Name #4: John
Name #5: Mark
Відповідь
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include <iostream> #include <string> #include <utility> // для std::swap(). Якщо у вас не підтримується C++11, то тоді #include <algorithm> void sortArray(std::string *array, int length) { // Перебираємо кожний елемент масиву for (int startIndex = 0; startIndex < length; ++startIndex) { // smallestIndex - індекс найменшого елементу, який ми зустріли int smallestIndex = startIndex; // Шукаємо найменший елемент, який залишився в масиві (починаючи з startIndex+1) for (int currentIndex = startIndex + 1; currentIndex < length; ++currentIndex) { // Якщо поточний елемент менше нашого раніше знайденного елементу, if (array[currentIndex] < array[smallestIndex]) // то тоді це нове найменше значення в цій ітерації smallestIndex = currentIndex; } // Змінюємо місцями наш початковий елемент зі знайденим найменшим елементом масиву std::swap(array[startIndex], array[smallestIndex]); } } int main() { std::cout << "How many names would you like to enter? "; int length; std::cin >> length; // Виділяємо масив для зберігання імен std::string *names = new std::string[length]; // Просимо користувача ввести всі імена for (int i = 0; i < length; ++i) { std::cout << "Enter name #" << i + 1 << ": "; std::cin >> names[i]; } // Сортуємо масив sortArray(names, length); std::cout << "\nHere is your sorted list:\n"; // Виводимо відсортований масив for (int i = 0; i < length; ++i) std::cout << "Name #" << i + 1 << ": " << names[i] << '\n'; delete[] names; // не забуваємо використовувати оператор delete[] для звільнення пам'яті names = nullptr; // використовуйте 0, якщо не підтримується C++11 return 0; } |