Каковы различия между переменной-указателем и ссылочной переменной в C++?

Я знаю, что ссылки являются синтаксическим сахаром, поэтому код легче читать и писать.

Но каковы различия?


Резюме из ответов и ссылок ниже:

  1. Указатель может быть переназначен любое количество раз, в то время как ссылка не может быть переназначена после привязки.
  2. Указатели могут никуда не указывать (NULL), тогда как ссылка всегда ссылается на объект.
  3. Вы не можете взять адрес ссылки, как вы можете с указателями.
  4. Там нет "ссылочной арифметики" (но вы можете взять адрес объекта, на который указывает ссылка, и сделать арифметику указателя на нем, как в &obj + 5).

Чтобы уточнить неправильное представление:

Стандарт C++ очень осторожен, чтобы не указывать, как компилятор может реализовывать ссылки, но каждый компилятор C++ реализует ссылки как указатели. То есть декларация, такая как:

int &ri = i;

если он не оптимизирован полностью, выделяет тот же объем памяти, что и указатель, и размещает адрес i в это хранилище.

Таким образом, указатель и ссылка используют одинаковый объем памяти.

Как общее правило,

  • Используйте ссылки в параметрах функций и возвращаемых типах для предоставления полезных и самодокументируемых интерфейсов.
  • Используйте указатели для реализации алгоритмов и структур данных.

Интересно читать:

48 ответов

Я хотел бы поделиться своей недавней статьей о массивах, указателях и ссылках здесь, которая берет за основу отправную точку для Страуструпа в своей книге "Путешествие по C++". Я полагаю, что многие примеры работающего кода, дампы памяти, диаграммы и объяснения показывают много полезной информации, чтобы справиться с этими вопросами и дать ответы на первоначальный вопрос. Это не исчерпывающая процедура, но то, что она показывает, показывает глубже:

Массивы, указатели и ссылки под капотом.

Некоторые ключевые моменты статьи

указатели

  • Переменные-указатели объявляются с использованием оператора объявления унарного суффикса *
  • Указательным объектам присваивается значение адреса, например, путем присвоения объекту массива, адреса объекта с использованием оператора & унарного префикса или присвоения значению другого объекта указателя
  • Указатель может быть переназначен любое количество раз, указывая на разные объекты
  • Указатель - это переменная, которая содержит назначенный адрес. Он занимает память в памяти, равную размеру адреса для архитектуры целевой машины
  • Указатель может быть математически обработан, например, с помощью операторов приращения или сложения. Следовательно, можно перебирать указатель и т. Д.
  • Чтобы получить или установить содержимое объекта, на который указывает указатель, нужно использовать унарный префиксный оператор *, чтобы разыменовать его

Рекомендации

  • Ссылки должны быть инициализированы, когда они объявлены.
  • Ссылки объявляются с использованием оператора объявления унарного суффикса &.
  • При инициализации ссылки используется имя объекта, на который они будут ссылаться напрямую, без необходимости использовать унарный префиксный оператор &
  • После инициализации ссылки не могут быть указаны на что-то еще с помощью присваивания или арифметической манипуляции
  • Нет необходимости разыменовывать ссылку, чтобы получить или установить содержимое объекта, на который она ссылается
  • Операции присваивания для ссылки манипулируют содержимым объекта, на который она указывает (после инициализации), а не самой ссылкой (не изменяется там, где она указывает)
  • Арифметические операции над ссылкой манипулируют содержимым объекта, на который она указывает, а не самой ссылкой (не изменяется там, где она указывает)
  • Практически во всех реализациях ссылка фактически сохраняется как адрес в памяти упомянутого объекта. Следовательно, он занимает память в памяти, равную размеру адреса для целевой машины, точно так же, как объект-указатель

Несмотря на то, что указатели и ссылки реализованы во многом одинаково "под капотом", компилятор обрабатывает их по-разному, что приводит ко всем различиям, описанным выше.

Простыми словами, мы можем сказать, что ссылка - это альтернативное имя переменной, тогда как указатель - это переменная, которая содержит адрес другой переменной. например

int a = 20;
int &r = a;
r = 40;  /* now the value of a is changed to 40 */

int b =20;
int *ptr;
ptr = &b;  /*assigns address of b to ptr not the value */

Разница между указателем и ссылкой

Указатель может быть инициализирован в 0, а ссылка - нет. Фактически, ссылка должна также ссылаться на объект, но указатель может быть нулевым указателем:

int* p = 0;

Но мы не можем иметь int& p = 0; а также int& p=5 ;,

Фактически, чтобы сделать это правильно, мы должны сначала объявить и определить объект, затем мы можем сделать ссылку на этот объект, поэтому правильная реализация предыдущего кода будет такой:

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

Другим важным моментом является то, что мы можем сделать объявление указателя без инициализации, однако ничего такого нельзя сделать в случае ссылки, которая всегда должна делать ссылку на переменную или объект. Однако такое использование указателя рискованно, поэтому обычно мы проверяем, действительно ли указатель указывает на что-то или нет. В случае ссылки такая проверка не требуется, поскольку мы уже знаем, что ссылка на объект во время объявления является обязательной.

Другое отличие состоит в том, что указатель может указывать на другой объект, однако ссылка всегда ссылается на один и тот же объект, давайте рассмотрим этот пример:

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

Еще один момент: когда у нас есть шаблон, такой как шаблон STL, такой шаблон класса всегда будет возвращать ссылку, а не указатель, чтобы облегчить чтение или присвоение нового значения с помощью оператора []:

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="

Разница заключается в том, что непостоянная переменная-указатель (не путать с указателем на постоянную) может быть изменена в течение некоторого времени во время выполнения программы, требует использования семантики указателя (&,*), а ссылки могут быть установлены после инициализации только (поэтому вы можете установить их только в списке инициализатора конструктора, но никак иначе) и использовать обычную семантику доступа к значениям. В основном, ссылки были введены для поддержки перегрузки операторов, как я читал в одной очень старой книге. Как кто-то заявил в этой теме - указатель может быть установлен в 0 или любое другое значение, которое вы хотите. 0(NULL, nullptr) означает, что указатель инициализируется ничем. Ошибка разыменования нулевого указателя. Но на самом деле указатель может содержать значение, которое не указывает на какое-то правильное расположение в памяти. Ссылки, в свою очередь, стараются не позволять пользователю инициализировать ссылку на что-то, на что нельзя ссылаться из-за того, что вы всегда предоставляете ему правильное значение. Несмотря на то, что существует множество способов инициализации ссылочной переменной в неправильном месте в памяти, лучше не вдаваться в подробности. На уровне машины и указатель, и ссылка работают равномерно - с помощью указателей. Допустим, в основных ссылках приведены синтаксические сахара. Ссылки на значения rvalue отличаются от этого - они, естественно, являются объектами стека / кучи.

Думайте об указателе как о визитной карточке:

  • Это дает вам возможность связаться с кем-то
  • может быть пустым
  • Может содержать неверную или устаревшую информацию
  • Вы не уверены, что кто-то, упомянутый на нем, вообще жив.
  • Вы не можете говорить напрямую с картой, вы можете использовать ее только для вызова кого-либо
  • Может таких карт много существует

Думайте об обращении как об активном разговоре с кем-то:

  • Вы почти уверены, что кто-то, с кем вы связались, жив
  • Вы можете говорить напрямую, никаких дополнительных звонков не требуется
  • Вы уверены, что разговариваете не с пустым местом и не с мусором
  • Вы не можете быть уверены, что вы единственный, кто в данный момент разговаривает с этим объектом

Вы можете использовать разницу между ссылками и указателями, если вы следуете соглашению для аргументов, передаваемых в функцию. Ссылки Const предназначены для данных, передаваемых в функцию, а указатели - для данных, передаваемых из функции. В других языках вы можете явно указать это с помощью таких ключевых слов, как in а также out, В C++ вы можете объявить (по соглашению) эквивалент. Например,

void DoSomething(const Foo& thisIsAnInput, Foo* thisIsAnOutput)
{
   if (thisIsAnOuput)
      *thisIsAnOutput = thisIsAnInput;
}

Использование ссылок в качестве входных данных и указателей в качестве выходных данных является частью руководства по стилю Google.

Я всегда решаю по этому правилу из C++ Core Guidelines:

Предпочитайте T*, а не T&, когда "без аргумента" является допустимым параметром

У меня есть аналогия для ссылок и указателей, думаю, что ссылки - это еще одно имя объекта, а указатели - адрес объекта.

// receives an alias of an int, an address of an int and an int value
public void my_function(int& a,int* b,int c){
    int d = 1; // declares an integer named d
    int &e = d; // declares that e is an alias of d
    // using either d or e will yield the same result as d and e name the same object
    int *f = e; // invalid, you are trying to place an object in an address
    // imagine writting your name in an address field 
    int *g = f; // writes an address to an address
    g = &d; // &d means get me the address of the object named d you could also
    // use &e as it is an alias of d and write it on g, which is an address so it's ok
}

Помимо всех ответов здесь,

Вы можете реализовать перегрузку операторов, используя ссылки:

my_point operator+(const my_point& a, const my_point& b)
{
  return { a.x + b.x, a.y + b.y };
}

Использование параметров в качестве значения создаст временные копии исходных аргументов, а использование указателей не вызовет эту функцию из-за арифметики указателей.

Указатель - это переменная, которая содержит адрес памяти другой переменной, где в качестве ссылки используется псевдоним существующей переменной. (другое имя уже существующей переменной)

  1. указатель может быть инициализирован как,

    int b = 15;
    int *q = &b;
    ИЛИ int *q;q = &b; где как ссылка,

    int b=15;int &c=b;

(объявить и инициализировать за один шаг)

  1. Указателю можно присвоить значение null, но нельзя указать ссылку.
  2. С указателями могут выполняться различные арифметические операции, тогда как такой вещи, как справочная арифметика, не существует.
  3. Указатель можно переназначить, а ссылку нельзя.
  4. Указатель имеет свой собственный адрес памяти и размер в стеке, тогда как ссылка имеет тот же адрес памяти.

Тарын сказал: "Вы не можете взять адрес ссылки, как вы можете с указателями".

На самом деле вы можете.

Я цитирую другой вопрос:

"Часто задаваемые вопросы по C++ говорят лучше всего:

В отличие от указателя, когда ссылка привязана к объекту, она не может быть "переустановлена" для другого объекта. Сама ссылка не является объектом (она не имеет идентификатора; взятие адреса ссылки дает вам адрес референта; помните: ссылка является его референтом)."

"Я знаю, что ссылки - это синтаксический сахар, поэтому код легче читать и писать"

Этот. Ссылка не является другим способом реализации указателя, хотя она охватывает огромный случай использования указателя. Указатель - это тип данных - адрес, который обычно указывает на фактическое значение. Однако он может быть установлен на ноль или несколько мест после адреса, используя адресную арифметику и т. Д. Ссылка - это "синтаксический сахар" для переменной, которая имеет свое собственное значение.

C только проходил по семантике значения. Получение адреса данных, на которые ссылалась переменная, и отправка их в функцию - это способ передать "ссылку". Ссылка сокращает это семантически, "ссылаясь" на исходное местоположение данных. Так:

int x = 1;
int *y = &x;
int &z = x;

Y - это указатель на int, указывающий на место, где хранится x. X и Z относятся к одному и тому же месту хранения (стек или куча).

Многие люди говорили о разнице между ними (указатели и ссылки), как будто они одно и то же с разным использованием. Они не одинаковы вообще.

1) "Указатель может быть переназначен любое количество раз, в то время как ссылка не может быть переназначена после привязки". - указатель - это тип данных адреса, который указывает на данные. Ссылка - это другое название данных. Таким образом, вы можете "переназначить" ссылку. Вы просто не можете переназначить местоположение данных, к которому оно относится. Точно так же, как вы не можете изменить местоположение данных, на которое ссылается "x", вы не можете сделать это на "z".

x = 2;
*y = 2;
z = 2;

Такой же. Это переназначение.

2) "Указатели могут никуда не указывать (NULL), тогда как ссылка всегда ссылается на объект" - опять-таки с путаницей. Ссылка - это просто другое имя для объекта. Нулевой указатель означает (семантически), что он ни на что не ссылается, тогда как ссылка была создана, сказав, что это было другое имя для "x". поскольку

3) "Вы не можете взять адрес ссылки, как вы можете с указателями" - да, вы можете. Опять с растерянностью. Если вы пытаетесь найти адрес указателя, который используется в качестве ссылки, это является проблемой, поскольку ссылки не являются указателями на объект. Они являются объектом. Таким образом, вы можете получить адрес объекта, и вы можете получить адрес указателя. Потому что они оба получают адрес данных (один - расположение объекта в памяти, другой - указатель на расположение объектов в памяти).

int *yz = &z; -- legal
int **yy = &y; -- legal

int *yx = &x; -- legal; notice how this looks like the z example.  x and z are equivalent.

4) "Там нет" ссылочной арифметики "" - опять-таки с путаницей - поскольку в приведенном выше примере z - это ссылка на x, и поэтому оба являются целыми числами, арифметика "reference" означает, например, добавление 1 к значению, на которое ссылается Икс.

x++;
z++;

*y++;  // what people assume is happening behind the scenes, but isn't. it would produce the same results in this example.
*(y++);  // this one adds to the pointer, and then dereferences it.  It makes sense that a pointer datatype (an address) can be incremented.  Just like an int can be incremented. 

вы не можете разыменовать ссылку как указатель, который при разыменовании дает значения в этом месте,

хотя и ссылка, и указатель работают по адресу ...

так

ты можешь сделать это

int * val = 0xDEADBEEF; * val - это что-то по адресу 0xDEADBEEF.

вы не можете этого сделать int& val = 1;

*val не допускается.

Основное значение указателя (*) - "Значение по адресу", что означает, что любой адрес, который вы предоставляете, будет давать значение по этому адресу. Как только вы измените адрес, он даст новое значение, в то время как ссылочная переменная используется для ссылки на какую-либо конкретную переменную и не может быть изменена для ссылки на любую другую переменную в будущем.

В чем разница между операторами "&" и "*" в C & является унарным оператором в C, который возвращает адрес памяти переданного операнда. Это также называется адресом оператора. <> * - это унарный оператор, который возвращает значение объекта, на которое указывает переменная-указатель. Это известно как значение оператора. Он также используется для объявления переменной указателя.

Суммируя,

Указатели: указатель — это переменная, которая содержит адрес памяти другой переменной. Указатель необходимо разыменовать с помощью оператора *, чтобы получить доступ к ячейке памяти, на которую он указывает. - Извлечено из Компьютерщики для Компьютерщиков

Ссылки: ссылочная переменная — это псевдоним, то есть другое имя уже существующей переменной. Ссылка, как и указатель, также реализуется путем хранения адреса объекта. - Извлечено из Компьютерщики для Компьютерщиков

Еще одна картинка для более подробной информации:

много ценной информации выше, но мой совет начинающим программистам на C++.

Избегайте указателей, используйте их только там, где у вас нет другого выбора (потому что вы взаимодействуете с библиотекой, которая принимает только указатель). Я знаю, что это описано во многих книгах и учебных пособиях, но просить новичка написать код для управления необработанными указателями — не лучший способ познакомить нового программиста с языком.

Если вам не нужно иметь возможность изменить объект, на который указываете, используйте ссылку. Если вам нужно изменить то, на что указывает, используйте интеллектуальный указатель.

хотя «технически» вы можете создать ссылку, указывающую на фиктивные данные, для этого необходимо, чтобы вы уже выполнили какую-то неверную манипуляцию с указателем или передали что-то по ссылке, что не используется сразу, но сохраняется и используется позже, когда объект уже был создан. уничтожен. Совершенно приемлемо (и хороший тон) предполагать, что ссылка указывает на правильно сконструированный объект. Вам не следует писать кучу кода, чтобы проверить, указывает ли ссылка на действительный объект.

Указатели имеют дополнительную сложность: они «указывают ни на что» (NULL или nullptr), которые вам, возможно, придется проверить. Указатели усложняют вопрос «кто (и как) отвечает за очистку данных, на которые указывает указатель».

Вы можете произносить ссылки как жесткую ссылку в Linux, а указатели как символическую ссылку в Linux или как ярлык в Windows.

Другие вопросы по тегам