Ссылки и указатели в C++: отличие, примеры и лучшие практики

Запрос, на который отвечает статья: «Ссылки и указатели в C++: отличие и когда что использовать».
Ключевые различия
- Ссылка — это псевдоним уже существующего объекта. Она всегда должна быть инициализирована и не может быть «пустой».
- Указатель — это переменная, хранящая адрес. Может быть нулевым (
nullptr
), его можно переназначать, поддерживает арифметику указателей.
int x = 10;
int& ref = x; // ссылка на x, обязана быть инициализирована
int* ptr = &x; // указатель на x, может стать nullptr
ref = 20; // меняем x через ссылку (x == 20)
*ptr = 30; // меняем x через указатель (x == 30)
ptr = nullptr; // допустимо для указателя
// ref = nullptr; // так нельзя: ссылки не бывают пустыми
Передача параметров в функции
Общее правило: обязательные параметры — по ссылке (либо по значению, если недорого копировать); опциональные и «выходные» параметры — через указатели или возвращаемое значение.
// Обязательный входной параметр — по константной ссылке
void print_user(const std::string& name);
// Опциональный выход — через указатель (может быть nullptr)
bool find_price(const std::string& item, double* out_price);
// Современная альтернатива «выходным указателям» — возврат значений/структур
std::optional<double> try_find_price(const std::string& item);
Ссылка vs указатель: swap
void swap_ref(int& a, int& b) {
int t = a; a = b; b = t;
}
void swap_ptr(int* a, int* b) {
if (!a || !b) return; // указатели надо проверять
int t = *a; *a = *b; *b = t;
}
int main() {
int x = 1, y = 2;
swap_ref(x, y); // удобно, нельзя вызвать «пусто»
swap_ptr(&x, &y); // более шумно, нужна проверка
}
Const-корректность: частые сочетания
const T&
— не копируемый, но неизменяемый параметр.T* const
— «константный указатель» (сам указатель нельзя переназначить, но по адресу можно менять объект).const T*
— «указатель на константу» (объект менять нельзя, переназначать указатель можно).const T* const
— и объект менять нельзя, и указатель переназначать нельзя.
void foo(const std::string& s); // читать без копии
void bar(const int* p); // читать через указатель
void baz(int* const p); // p фиксирован, *p можно менять
Время жизни и «висячие» ссылки
Ссылка должна указывать на объект, который живёт дольше ссылки. Нельзя возвращать ссылку на локальную переменную функции.
const std::string& bad() {
std::string s = "temp";
return s; // ОПАСНО: возвращаем ссылку на уничтоженный объект
}
const std::string& good(const std::string& input) {
return input; // корректно: ссылка ссылается на внешний объект
}
Если нужно вернуть «что-то похожее на ссылку», но без риска по времени жизни — возвращайте по значению или используйте std::string_view
(только если гарантирован срок жизни исходных данных).
nullptr вместо NULL
Используйте nullptr
(C++11+) — типобезопасный нулевой указатель.
void process(int* p);
process(nullptr); // корректно
// process(NULL); // может быть неоднозначно (макрос целочисленного типа)
Массивы, указатели и потеря размера
Массив «деградирует» до указателя при передаче в функцию — информация о размере теряется. Используйте шаблон/референс или std::span
.
void bad(int* a) { // размер неизвестен }
// Размер известен через ссылку на массив
template<size_t N>
void good(int (&a)[N]) {
static_assert(N > 0);
}
// Современный способ — std::span (C++20)
#include <span>
void process(std::span<const int> data) {
for (int v : data) { /* ... */ }
}
int arr[3] {1,2,3};
process(arr); // не теряем размер
std::vector<int> v{1,2,3};
process(v); // тоже работает
Когда выбирать ссылку, а когда указатель
- Ссылка: параметр обязателен, объект точно существует, вы не хотите проверок на
nullptr
. - Константная ссылка: большой объект «для чтения» без копии.
- Указатель: параметр опционален, нужен «выходной» параметр, работа с массивами/буферами на низком уровне.
- Владение динамической памятью: используйте умные указатели (
unique_ptr
/shared_ptr
) — они управляют временем жизни. В этой статье мы сосредоточены на базовых указателях, но для владения предпочтительнее RAII-инструменты.
Типичные ошибки и как их избежать
- Висячие ссылки/указатели: не храните ссылку/указатель на объект, который скоро уничтожится.
- Неправильный delete: для массивов используйте
delete[]
; не вызыватьdelete
на памяти, которой не владеете. - Неинициализированные указатели: всегда инициализируйте
nullptr
и проверяйте перед разыменованием. - Потеря размера массива: используйте
std::span
или шаблон со ссылкой на массив.
int* p; *p = 42; // неопределённое поведение (p не инициализирован)
int* q = nullptr; // хорошо: явная инициализация
if (q) *q = 42; // ничего не делаем, безопасно
Инструменты контроля качества
- Компилятор с флагами:
-Wall -Wextra -Wpedantic
. - Sanitizers:
-fsanitize=address,undefined
— ловят выход за границы, U.B. и т.п. - Статический анализ: clang-tidy, cppcheck.
Мини-чеклист
- Обязательный параметр? — ссылка (
T&
илиconst T&
). - Опциональный параметр или out-параметр? — указатель (
T*
) или возврат значения. - Нужна «пустота»? — только указатель (
nullptr
). - Следите за временем жизни объектов и используйте средства анализа.
Хотите системно прокачать основы и практику? Посмотрите пошаговый курс «C++ с Нуля до Гуру» — там много практики, разбор типичных ошибок и домашние задания.
-
-
Михаил Русаков
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.