C++ для новичков: ссылки и указатели — в чём разница
Указатель можно перенаправить и обнулить, ссылку — нет. За полчаса разберётесь, когда что писать, и перестанете ловить segfault на пустом месте.

К концу этого гайда вы будете понимать, чем int& отличается от int*, почему компилятор ругается на ссылку без инициализации и когда указатель может внезапно указывать в никуда. Разберёмся на компилируемых примерах — каждый можно скопировать и запустить. Времени уйдёт минут двадцать-тридцать, если под рукой есть компилятор.
Ориентируемся на стандарт C++17 и новее — примеры собираются любым современным компилятором: g++ 11+, clang 14+, MSVC 2022. Всё нужное — только компилятор командной строки или любая IDE. Никаких библиотек ставить не придётся.
Что нужно до начала
- Установленный компилятор:
g++,clang++или Visual Studio. Проверьте командойg++ --version— должна вывестись версия. - Понимание, что такое переменная и как объявить
int x = 5;. - Умение собрать и запустить программу:
g++ main.cpp -o main && ./main.
Если g++ --version выдаёт «command not found», компилятор не установлен или не в PATH. На Ubuntu поможет sudo apt install g++, на macOS — xcode-select --install.
Шаг 1. Что такое указатель
Указатель хранит адрес другой переменной. Объявляется со звёздочкой, а адрес берётся оператором &.
#include <iostream>
int main() {
int x = 10;
int* p = &x; // p хранит адрес x
std::cout << "значение x: " << x << "\n";
std::cout << "адрес x: " << p << "\n";
std::cout << "значение по адресу: " << *p << "\n"; // разыменование
*p = 20; // меняем x через указатель
std::cout << "новое значение x: " << x << "\n";
return 0;
}Скомпилируйте и запустите. В выводе значение x: 10, дальше — адрес вида 0x7ffe... (у вас будет свой), затем значение по адресу: 10 и новое значение x: 20. Последняя строка доказывает: через *p вы действительно изменили x.
Шаг 2. Что такое ссылка
Ссылка — это второе имя для уже существующей переменной. Объявляется с амперсандом и обязательно инициализируется сразу.
#include <iostream>
int main() {
int x = 10;
int& ref = x; // ref — другое имя для x
std::cout << "x: " << x << "\n";
std::cout << "ref: " << ref << "\n";
ref = 20; // никакого разыменования, просто присваивание
std::cout << "после ref = 20, x = " << x << "\n";
return 0;
}Вывод: x: 10, ref: 10, затем после ref = 20, x = 20. Обратите внимание: у ссылки нет звёздочки при использовании. Вы пишете ref, а не *ref — она ведёт себя как сама переменная.
Шаг 3. Три отличия, которые важны на практике
Синтаксис — вершина айсберга. Разница в поведении важнее.
- Ссылку нельзя оставить неинициализированной. Строка
int& ref;не соберётся — компилятор выдаст ошибку. Указатель можно объявить пустым:int* p = nullptr;. - Ссылку нельзя переназначить. После
int& ref = x;записьref = y;не сделаетrefпсевдонимомy— она просто скопирует значениеyвx. Указатель же перенаправляется свободно:p = &y;. - У указателя есть nullptr, у ссылки — нет. Указатель может явно «ни на что не указывать». Ссылка всегда привязана к чему-то (пока это что-то живо).
#include <iostream>
int main() {
int a = 1, b = 2;
int* p = &a;
p = &b; // указатель переназначили — теперь он на b
std::cout << *p << "\n"; // 2
int& r = a;
r = b; // это НЕ переназначение: в a записалось значение b
std::cout << a << "\n"; // 2
std::cout << b << "\n"; // 2 (b не менялось)
return 0;
}Запустите и сверьте: три двойки подряд. Если понимаете, почему вторая двойка появилась в a, а не сделала r псевдонимом b, — ключевую идею вы уловили.
Правило на первое время: если объект может отсутствовать или его нужно переключать — берите указатель. Если он есть всегда и один — берите ссылку. Ссылка честнее сообщает читателю кода: «здесь точно что-то есть».
Шаг 4. Передача в функцию — самый частый случай
Именно здесь новичок впервые сталкивается с выбором. Без ссылки или указателя функция получает копию и не может изменить оригинал.
#include <iostream>
void поКопии(int n) { n = 100; } // меняет только копию
void поСсылке(int& n) { n = 100; } // меняет оригинал
void поУказателю(int* n) { *n = 100; } // меняет оригинал через адрес
int main() {
int x = 1;
поКопии(x); std::cout << x << "\n"; // 1
поСсылке(x); std::cout << x << "\n"; // 100
int y = 1;
поУказателю(&y); std::cout << y << "\n"; // 100
return 0;
}Вывод: 1, 100, 100. Разница на вызывающей стороне: ссылку передаёте как обычную переменную поСсылке(x), а указателю нужен адрес поУказателю(&y). Ссылка читается чище — поэтому в современном C++ для «просто изменить аргумент» чаще берут именно её.
Когда что писать: короткая шпаргалка
| Ситуация | Что брать |
|---|---|
| Аргумент функции всегда существует, надо изменить | Ссылка int& |
| Аргумент может отсутствовать (необязательный) | Указатель, проверять на nullptr |
| Большой объект передаём только для чтения | Константная ссылка const T& |
| Нужно переключать, на что смотрим | Указатель |
| Обход динамической структуры (список, дерево) | Указатель |
Если не получилось
- error: declaration of reference variable 'ref' requires an initializer — вы написали
int& ref;без присваивания. Ссылку нужно инициализировать в той же строке:int& ref = x;. - Segmentation fault при запуске — почти всегда разыменование пустого или испорченного указателя:
*p, гдеp == nullptrили указывает на уже несуществующую переменную. Проверяйте указатель перед*p:if (p) { *p = 5; }. - error: invalid conversion from 'int' to 'int*' — вы присвоили указателю значение вместо адреса:
int* p = x;. Нужен адрес:int* p = &x;. - Значение оригинала не меняется, хотя должно — функция принимает аргумент по копии (
void f(int n)), а не по ссылке (void f(int& n)). Добавьте амперсанд в сигнатуру. - warning: reference to local variable returned — вы вернули ссылку на переменную, которая исчезнет после выхода из функции. Такую ссылку использовать нельзя: возвращайте по значению.
Что дальше
Освоив базовое различие, посмотрите на const-ссылки — это стандартный способ передавать большие объекты без копирования и без риска их изменить. Дальше идут «умные указатели» std::unique_ptr и std::shared_ptr из заголовка <memory>: они снимают ручное управление памятью, из-за которого у новичков и случаются утечки и висячие указатели. Ручной new/delete в современном коде почти не пишут — сразу привыкайте к умным указателям, это отдельная тема для следующего шага.
Дефектный акт по фото: Осмотр по фотографиям: классификация дефектов, ссылки на нормы, требования к устранению. Готовый акт в Word.
Спросить за 80 ₽Источники: cppreference — Reference declaration, cppreference — Pointer declaration
Частые вопросы
А если у меня старый компилятор, скажем C++11 — примеры соберутся?
Segfault возникает, хотя указатель я вроде инициализировал — в чём дело?
Ссылка занимает память, как указатель?
Можно ли сделать ссылку на ссылку или указатель на указатель?
Что выбрать по умолчанию, если сомневаюсь?
Обязательно ли ставить звёздочку у имени при использовании указателя?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.