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 и новее.

Итоги урока

Теперь вы знаете:
  • Что такое указатели и как они работают с адресами памяти.
  • Как использовать операторы & и *.
  • Как создавать и удалять динамические объекты.
  • Что такое ссылки и чем они отличаются от указателей.