Неконстантные версии
Следует использовать указатели, когда:
1. Нужна опциональность (значение указателя может быть nullptr)
// Поиск элемента - может не найти
Node* findNode(Node* root, int value) {
if (!root) return nullptr; // Вариант "не найдено"
// ... поиск
}
2. Нужна динамическая память
int* array = new int[100];
// ...
delete[] array;
3. Нужна адресная арифметика
int arr[10];
int* ptr = arr;
ptr += 5; // Перемещение по массиву
4. Нужно перенаправление
int a = 1, b = 2;
int* current = &a;
// ... позже
current = &b; // Теперь указывает на b
Следует использовать ссылки, когда:
1. Обязательный параметр функции
void process(std::string& str) { // str обязательно должен существовать
// работаем со строкой
}
2. Перегрузка операторов
Vector& operator+=(const Vector& other) {
// ...
return *this; // Возвращаем ссылку на себя
}
3. Range-based for loops
for (auto& item : container) { // Изменяем элементы
item.process();
}
4. Алиасы для длинных типов
using Matrix = std::vector<std::vector<double>>;
void foo(Matrix& m) { // Проще, чем указатель
// ...
}
Константные версии
1. Константный указатель vs Указатель на константу
int x = 10, y = 20;
const int* ptr1 = &x; // Указатель на константу
// *ptr1 = 30; // ОШИБКА: нельзя менять значение
ptr1 = &y; // OK: можно менять указатель
int* const ptr2 = &x; // Константный указатель
*ptr2 = 30; // OK: можно менять значение
// ptr2 = &y; // ОШИБКА: нельзя менять указатель
const int* const ptr3 = &x; // И то, и другое
// *ptr3 = 30; // ОШИБКА
// ptr3 = &y; // ОШИБКА
2. Константная ссылка
const int& ref = x; // Ссылка на константу
// ref = 40; // ОШИБКА: нельзя менять
Готовое практическое правило
Существует готовое практическое правило: "Use references when you can, pointers when you must", что означает:
Используйте ссылки, когда можете, а указатели — когда должны!
Например, если интерфейс системной библиотечной функции требует указателя, то вы должны использовать указатель. Если же происходит разработка своей функции, то лучше использовать ссылки, если это позволяет логика работы функции.
Итоговая таблица
Все вышесказанное можно свести в таблицу:
|
Ситуация |
Указатель |
Ссылка |
|
Может отсутствовать значение? |
Да,
(это nullptr) |
Нет
(ссылка всегда ссылается на значение) |
|
Можно изменять цель, на которую происходит указывание? |
Можно |
Нельзя |
|
Как использовать в прототипах функций |
void func(int *ptr)
{...} |
void func(int &ref)
{...} |
|
Как передавать в функцию переменную |
func(&var)
- здесь адрес переменной приводится к указателю |
func(var) |
|
Использование внутри функции |
if (ptr) *ptr = ... |
ref = ... |
|
Использование доступа к масивам и элементам |
ptr++,
ptr[5],
есть адресная арифметика |
int (&ref)[5] = arr; // Ссылка на массив
int& ref = arr[2]; // Ссылка на элемент 3 |
|
Синтаксис |
Явный (*, ->, &) |
Неявный (как переменная) |
Ссылка — это безопасный, не-NULL указатель, который нельзя перенаправить, с более чистым синтаксисом.
Указатель — более гибкий, но потенциально опасный инструмент, требующий осторожности.