08. Указатели и ссылки
Что такое память и адрес
Каждая переменная хранится в памяти компьютера. У неё есть:- Значение — то, что в ней записано (например, 42).
- Адрес — место, где это значение хранится.
Можно представить память как огромный шкаф с ячейками. Каждая ячейка имеет свой номер (адрес), а внутри хранится значение.
Что такое указатель
Указатель — это переменная, которая хранит адрес другой переменной.Пример
#include <iostream>
using namespace std;int main() {
int x = 10;
int* ptr = &x; // указатель хранит адрес переменной x
cout << "Значение x: " << x << endl;
cout << "Адрес x: " << &x << endl;
cout << "Значение ptr (адрес x): " << ptr << endl;
cout << "Значение по адресу ptr: " << *ptr << endl;
return 0;
}
Разбор кода
- int* ptr — создаём указатель на целое число.
- &x — оператор, который возвращает адрес переменной x.
- *ptr — оператор разыменования: достаёт значение по адресу.
Результат
Значение x: 10 Адрес x: 0x7ffeefbff4ac Значение ptr (адрес x): 0x7ffeefbff4ac Значение по адресу ptr: 10(Адреса будут отличаться на вашем компьютере.)
Зачем нужны указатели
- Для передачи данных между функциями без копирования (ускоряет работу).
- Для динамического управления памятью (создание и удаление переменных во время работы программы).
- Для работы с массивами и структурами данных (например, списками, деревьями).
Изменение значения через указатель
С помощью указателя можно изменить значение переменной, на которую он указывает.
#include <iostream>
using namespace std;int main() {
int x = 5;
int* p = &x;
*p = 15; // изменяем значение переменной x через указатель
cout << "Новое значение x: " << x << endl;
return 0;
}
Вывод: Новое значение x: 15
NULL и nullptr
Если указатель ни на что не ссылается, ему нужно присвоить специальное значение — nullptr.
int* ptr = nullptr;
if (ptr == nullptr) {
cout << "Указатель пуст." << endl;
}
Динамическая память
Иногда нужно создать переменные во время выполнения программы. Для этого используется оператор new.
#include <iostream>
using namespace std;int main() {
int* p = new int; // создаём переменную в динамической памяти
*p = 25;
cout << "Значение: " << *p << endl;
delete p; // освобождаем память
p = nullptr; // сбрасываем указатель
return 0;
}
Объяснение
- new — выделяет память под переменную.
- delete — освобождает память, чтобы избежать утечек.
Массивы в динамической памяти
Можно создавать массивы динамически.
#include <iostream>
using namespace std;int main() {
int size;
cout << "Введите размер массива: ";
cin >> size;
int* arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = i * 2;
}
cout << "Массив: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
delete[] arr; // освобождение памяти
arr = nullptr;
return 0;
}
Что такое ссылка
Ссылка — это другое имя для уже существующей переменной. Она не хранит адрес, а просто «указывает» на тот же объект.
#include <iostream>
using namespace std;int main() {
int x = 10;
int& ref = x; // создаём ссылку
ref = 20; // изменяем x через ссылку
cout << "x = " << x << endl;
return 0;
}
Объяснение
- int& ref = x — создаёт ссылку на x.
- Теперь ref и x — одно и то же место в памяти.
- Ссылку нельзя изменить, чтобы она указывала на другую переменную.
Передача по ссылке
Ссылки часто используются в функциях, чтобы изменять значения прямо в вызывающем коде.
#include <iostream>
using namespace std;void addOne(int& n) {
n++;
}
int main() {
int a = 5;
addOne(a);
cout << a << endl; // 6
return 0;
}
Разница между указателем и ссылкой
- Указатель можно «переназначить» — ссылку нельзя.
- Указатель может быть nullptr — ссылка всегда должна ссылаться на существующую переменную.
- Указатель нужно разыменовывать через * — ссылка используется напрямую.
Типичные ошибки новичков
- Использование неинициализированных указателей (могут указывать куда угодно).
- Забыли delete — утечка памяти.
- Удалили память, но продолжают использовать указатель — это dangling pointer.
Мини-практика
Попробуйте выполнить задания:- Создайте переменную и выведите её адрес и значение через указатель.
- Напишите функцию, которая принимает указатель на число и увеличивает его значение в 2 раза.
- Сделайте программу, которая создаёт динамический массив, заполняет числами и считает сумму элементов.
Пример: удвоение через указатель
#include <iostream>
using namespace std;void doubleValue(int* p) {
*p = *p * 2;
}
int main() {
int n = 7;
doubleValue(&n);
cout << n << endl; // 14
return 0;
}
Советы новичкам
- Используйте nullptr, когда указатель не используется.
- Не забывайте освобождать память после new.
- Предпочитайте ссылки, если не нужно вручную управлять памятью.
- Когда освоитесь — изучите умные указатели (smart pointers) в C++11 и новее.
Итоги урока
Теперь вы знаете:- Что такое указатели и как они работают с адресами памяти.
- Как использовать операторы & и *.
- Как создавать и удалять динамические объекты.
- Что такое ссылки и чем они отличаются от указателей.