В чем разница между ссылкой и указателем в C++?

Ответ

Ссылка (T&) — это псевдоним существующего объекта. Указатель (T*) — это переменная, хранящая адрес памяти.

Основные различия:

  1. Инициализация и переназначение:

    • Ссылка должна быть инициализирована сразу при объявлении и не может быть изменена для ссылки на другой объект после инициализации.
    • Указатель может быть объявлен без инициализации (но это опасно), может быть nullptr, и его значение (адрес) можно менять.
  2. Синтаксис и безопасность:

    • Со ссылкой работают как с обычной переменной, не требуется оператор взятия адреса (&) или разыменования (*). Ссылка не может быть «висячей» (dangling) на этапе компиляции, если инициализирована корректно.
    • Указатель требует явного разыменования (*) для доступа к значению и оператора взятия адреса (&) для получения адреса. Может стать nullptr или указывать на невалидную память.
  3. Арифметика: Указатели поддерживают арифметику (инкремент, декремент), ссылки — нет.

Пример, иллюстрирующий различия:

int main() {
    int a = 10, b = 20;

    // ССЫЛКА
    int& ref = a;   // OK: ref теперь псевдоним для 'a'
    ref = 15;       // Теперь a = 15
    // int& ref2;   // ОШИБКА: ссылка должна быть инициализирована
    // ref = b;     // НЕ меняет объект ссылки! Это присваивание значения b переменной a.

    // УКАЗАТЕЛЬ
    int* ptr = nullptr; // OK: указатель может быть нулевым
    ptr = &a;           // ptr теперь хранит адрес a
    *ptr = 25;          // Теперь a = 25 (разыменование)
    ptr = &b;           // OK: ptr теперь указывает на b
    *ptr = 30;          // Теперь b = 30

    // Арифметика указателей (на массиве)
    int arr[3] = {1, 2, 3};
    int* p = arr;       // p указывает на arr[0]
    ++p;                // p теперь указывает на arr[1]
    // int& r = arr;    // Ошибка: нельзя инициализировать ссылку массивом
    int (&r_arr)[3] = arr; // Но можно ссылку на массив
    return 0;
}

Когда что использовать:

  • Ссылки — когда нужен гарантированно валидный псевдоним, параметры/возвращаемые значения функций, работа с объектами без копирования.
  • Указатели — когда нужна возможность переназначения, опциональность (nullptr), работа с динамической памятью или массивами, низкоуровневое управление.

Ответ 18+ 🔞

Э, слушай, давай разжёвывать эту тему, как будто тебе пять лет, но ты уже в IT. Ссылки и указатели — это как два способа позвать одного и того же мужика на улице.

Представь, у тебя есть ящик с пивом. Ссылка — это ты наклеил на него стикер «Моё, не трожь». Ты можешь брать пиво через этот стикер, но переклеить его на ящик с водкой — нихуя. Стикер намертво приклеен к первому ящику. Указатель — это записка в твоём кармане с адресом, где этот ящик стоит. Записку можно порвать, выкинуть, переписать новый адрес — полная свобода, но и полная жесть, если адрес окажется левым.

Короче, основные пиздели:

  1. Инициализация и переприсваивание — тут разница пиздец.

    • Ссылка (T&) — это как брак. Сразу и навсегда. Объявил — тут же инициализируй, иначе компилятор тебе в сраку чих-пых. И сменить объект после этого — никак. Ты привязан.
    • *Указатель (`T)** — это как гражданский брак. Можешь объявить, а инициализировать потом. Можешь быть один (nullptr`). Можешь сегодня указывать на одну тёлку, завтра — на другую. Свобода, блядь. И бардак, соответственно.
  2. Синтаксис и безопасность — тут ссылка рулит.

    • Ссылка — с ней работаешь, как с обычной переменной. Никаких звёздочек, никаких амперсандов. Удобно, безопасно. Висячей ссылки не получится, если только ты сам себе не прострелишь ногу.
    • Указатель — тут надо постоянно думать головой. Хочешь адрес — ставь &. Хочешь значение — ставь *. Забудешь — получишь либо адрес вместо числа, либо segmentation fault в лучшем случае. Может стать nullptr, может указывать в космос. Доверия ебать ноль.
  3. Арифметика — это фишка только указателей. Ссылки на это не способны. Указатель можно ++, можно --, можно прыгать по массиву. Ссылка — она тупо прилипла к одному объекту и всё.

Вот смотри, живой пример, чтоб вообще всё встало на свои места:

int main() {
    int a = 10, b = 20;

    // ССЫЛКА
    int& ref = a;   // Всё, ref и a — теперь одно лицо. Брак заключён.
    ref = 15;       // Теперь a = 15. Меняем через псевдоним.
    // int& ref2;   // ОШИБКА! Компилятор орет: "Мужик, ты чё, ссылку объявил, а к чему привязать — не сказал! Иди нахуй!"
    // ref = b;     // ВНИМАНИЕ! Это НЕ переназначение! Это мы значением b (20) заменили значение a. ref как был привязан к a, так и остался. a теперь = 20.

    // УКАЗАТЕЛЬ
    int* ptr = nullptr; // Записка пустая. Можно, но опасно.
    ptr = &a;           // Записали в бумажку адрес переменной a.
    *ptr = 25;          // Пошли по адресу и положили туда 25. Теперь a = 25.
    ptr = &b;           // Выкинули старую записку, написали новую — адрес b.
    *ptr = 30;          // Пошли по новому адресу, положили 30. Теперь b = 30.

    // Арифметика указателей (вот где они охуенны)
    int arr[3] = {1, 2, 3};
    int* p = arr;       // p смотрит на первый элемент (arr[0])
    ++p;                // Перепрыгнули на следующий (arr[1]). Просто? Просто.
    // int& r = arr;    // Ошибка! Нельзя ссылку на весь массив вот так просто.
    int (&r_arr)[3] = arr; // А вот так — можно, но синтаксис, блядь, ёперный театр.
    return 0;
}

Так когда что юзать?

  • Ссылки — когда ты уверен в объекте, как в себе. Передаёшь параметры в функцию, возвращаешь что-то без копирования, хочешь безопасный псевдоним. Удобно и надёжно.
  • Указатели — когда нужна гибкость. Динамическая память (new/delete), опциональные параметры (может быть nullptr), низкоуровневые пляски, работа с массивами. Мощно, но можно и ногу отстрелить.

Вот и вся магия. Ссылка — это надёжный, но моногамный брак. Указатель — это свободные отношения, где можно и адрес потерять, и в не ту память залезть. Выбирай по обстановке.