Лекція 8: Вказівники, Посилання та Динамічна пам'ять
Досі ми оголошували масиви так: int arr[10];. Але що, якщо ми не знаємо заздалегідь, скільки елементів нам знадобиться? Якщо ми виділимо arr[1000], а використаємо 5 — це марнотратство. Якщо виділимо arr[5], а треба 6 — програма не спрацює.
Рішення — динамічна пам'ять. Але щоб з нею працювати, спершу треба зрозуміти, що таке адреса.
1. Вказівники (Pointers)
Кожна змінна в комп'ютері живе за певною адресою в оперативній пам'яті (наприклад, 0x7ffee4). Вказівник — це змінна, яка зберігає не саме число (наприклад, 50), а адресу, де це число лежить.
Два головні оператори:
&(Амперсанд) — Взяття адреси. "Де ти живеш?"*(Зірочка) — Розіменування. "Піди за цією адресою і дай мені те, що там лежить".
Приклад:
#include <iostream>
using namespace std;
int main() {
int number = 42; // Звичайна змінна
int* ptr = &number; // Вказівник 'ptr' тепер зберігає адресу 'number'
cout << "Змінна: " << number << endl; // Виведе 42
cout << "Адреса: " << ptr << endl; // Виведе щось типу 0x61ff0c
cout << "Значення через адресу: " << *ptr; // Виведе 42 (пішли за адресою)
*ptr = 100; // Змінюємо значення за адресою
cout << "\nНове значення number: " << number; // Виведе 100!
return 0;
}
2. Посилання (References)
Вказівники — потужні, але небезпечні (можна випадково звернутися до чужої пам'яті). Тому в C++ додали посилання.
Посилання — це псевдонім (альтернативне ім'я) для вже існуючої змінної. Це як "Сашко" і "Олександр" — імена різні, а людина одна.
#include <iostream>
using namespace std;
int main() {
int original = 10;
int& ref = original; // ref — це посилання на original
ref = 20; // Змінюємо ref, а змінюється original!
cout << "original: " << original << endl; // Виведе 20
return 0;
}
Різниця:
- Вказівник (*) може бути пустим (
nullptr) і його можна перенаправити на іншу змінну. - Посилання (&) має бути прив'язане одразу і назавжди.
3. Динамічна пам'ять (new та delete)
Пам'ять, яку ми використовували раніше (Стек), очищається автоматично. Динамічна пам'ять (Куча / Heap) — це "ручне керування".
new— виділяє пам'ять і повертає адресу (вказівник).delete— звільняє пам'ять.
⚠️ Золоте правило C++:
На кожен new має бути свій delete. Якщо ви не звільните пам'ять, вона залишиться зайнятою до перезавантаження програми. Це називається витік пам'яті (memory leak).
Робота з однією змінною:
#include <iostream>
using namespace std;
int main() {
int* p = new int; // Просимо систему виділити місце під одне ціле число
*p = 55; // Записуємо туди значення
cout << *p; // Користуємося
delete p; // Повертаємо пам'ять системі!
p = nullptr; // Хороший тон: занулити вказівник, щоб випадково не використати його знову
return 0;
}
4. Динамічні масиви
Це найкорисніша частина. Ми можемо створити масив будь-якої довжини прямо під час виконання програми.
Синтаксис: тип* ім'я = new тип[розмір];
Звільнення масиву вимагає квадратних дужок: delete[] ім'я;.
Приклад: Створення списку студентів
#include <iostream>
using namespace std;
int main() {
int size;
cout << "Скільки студентів у групі? ";
cin >> size;
// Виділяємо пам'ять рівно під потрібну кількість
int* grades = new int[size];
// Працюємо як зі звичайним масивом
for (int i = 0; i < size; i++) {
cout << "Оцінка студента " << i + 1 << ": ";
cin >> grades[i];
}
cout << "Перша оцінка: " << grades[0] << endl;
// ОБОВ'ЯЗКОВО звільняємо пам'ять
delete[] grades;
return 0;
}
Практичні завдання до Лекції 8
Виконайте ці завдання в своєму середовищі розробки.
Завдання 1: Магія вказівників
- Створіть три змінні:
a,b,c(цілі числа). - Створіть вказівник
ptr. - За допомогою
ptrпо черзі змініть значення кожної змінної на 10, 20 та 30. - Направте
ptrнаa, змініть значення через*ptr. - Потім направте
ptrнаb... і так далі. - Виведіть
a,b,c, щоб переконатися, що вони змінилися.
Завдання 2: Динамічний розворот
- Запитайте у користувача: "Якої довжини масив створити?".
- Створіть динамічний масив (
new) введеного розміру. - Заповніть його числами, які вводить користувач.
- Виведіть цей масив у зворотному порядку.
- Не забудьте очистити пам'ять (
delete[]).
Завдання 3: Парні та Непарні (Складніше)
- Створіть динамічний масив на 10 випадкових чисел.
- Порахуйте, скільки там парних і скільки непарних чисел.
- Створіть два нових динамічних масиви:
- Один розміром точно під кількість парних чисел.
- Другий — під кількість непарних.
- Перепишіть числа з головного масиву у відповідні маленькі масиви.
- Виведіть обидва нові масиви.
- Очистіть пам'ять для всіх трьох масивів.