указателя - ссылка на указатель c++




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

Что такое C ++-ссылка ( для программистов C )

Ссылка может рассматриваться как постоянный указатель (не путать с указателем на постоянное значение!) С автоматической косвенностью, т. Е. Компилятор будет применять оператор * для вас.

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

Программистам C могут не нравиться ссылки на C ++, поскольку они больше не будут очевидны при возникновении косвенности или если аргумент передается по значению или указателем, не глядя на сигнатуры функций.

Программистам на C ++ может не нравиться использование указателей, поскольку они считаются небезопасными, хотя ссылки на самом деле не являются более безопасными, чем постоянные указатели, за исключением самых тривиальных случаев - отсутствие удобства автоматической косвенности и перенос другой смысловой коннотации.

Рассмотрим следующий пример из C ++ FAQ :

Несмотря на то, что ссылка часто реализуется с использованием адреса на базовом языке ассемблера, пожалуйста, не думайте о ссылке в качестве смешного указателя на объект. Ссылка - это объект. Это не указатель на объект, а также копия объекта. Это объект.

Но если ссылка действительно была объектом, как могли быть оборванные ссылки? В неуправляемых языках невозможно, чтобы ссылки были «безопаснее», чем указатели, - как правило, это просто не способ надежно присвоить значения через границы границ!

Почему я считаю ссылки на C ++ полезными

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

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

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

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

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

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

Чтобы прояснить заблуждение:

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

int &ri = i;

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

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

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

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

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


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

Указатель может быть инициализирован до 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 "="

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

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

Конечно, это гораздо сложнее сделать с ссылкой, но если вы справитесь с этим, вы будете рвать свои волосы, пытаясь найти его. Ссылки на C ++ не являются безопасными!

Технически это неверная ссылка , а не нулевая ссылка. C ++ не поддерживает нулевые ссылки как концепцию, как вы можете найти на других языках. Существуют и другие недопустимые ссылки. Любая недопустимая ссылка повышает спектр неопределенного поведения , так же, как использование недопустимого указателя.

Фактическая ошибка заключается в разыменовании указателя NULL до назначения ссылки. Но я не знаю каких-либо компиляторов, которые будут генерировать любые ошибки при этом условии - ошибка распространяется до точки далее в коде. Вот почему эта проблема настолько коварна. В большинстве случаев, если вы разыскиваете указатель NULL, вы ругаетесь прямо в этом месте, и это не требует большой отладки, чтобы понять это.

Мой пример выше короткий и надуманный. Вот более реальный пример.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Я хочу повторить, что единственный способ получить нулевую ссылку - это неправильный код, и как только вы его получите, вы получите неопределенное поведение. Никогда не имеет смысла проверять нулевую ссылку; например, вы можете попробовать, if(&bar==NULL)... но компилятор может оптимизировать утверждение из существования! Действительная ссылка никогда не может быть NULL, поэтому из представления компилятора сравнение всегда ложно, и можно свободно исключить предложение if как мертвый код - в этом суть неопределенного поведения.

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

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

Для более старого взгляда на эту проблему от кого-то с лучшими навыками письма, см. Null References от Jim Hyslop и Herb Sutter.

Для другого примера опасности разыменования нулевого указателя см. Раздел «Неопределенное поведение при попытке передать код на другую платформу Раймондом Ченом».


Вы забыли самую важную часть:

член-доступ с использованием указателей ->
членский доступ с использованием ссылок .

foo.bar явно превосходит foo->bar таким же образом, что vi явно превосходит Emacs :-)


Помимо синтаксического сахара, ссылка является указателем const ( не указателем на const ). Вы должны установить, на что оно ссылается, когда вы объявляете ссылочную переменную, и вы не можете изменить ее позже.

Обновление: теперь, когда я думаю об этом еще, есть важное различие.

Целью указателя const можно заменить его адрес и использование команды const.

Цель ссылки не может быть заменена каким-либо образом, чем UB.

Это должно позволить компилятору сделать большую оптимизацию по ссылке.


Ссылка никогда не может быть NULL .


Фактически, ссылка не похожа на указатель.

Компилятор сохраняет «ссылки» на переменные, связывая имя с адресом памяти; это его задача перевести любое имя переменной на адрес памяти при компиляции.

Когда вы создаете ссылку, вы сообщаете компилятору, что вы назначаете другое имя переменной указателя; поэтому ссылки не могут «указывать на null», потому что переменная не может быть, а не быть.

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

Теперь некоторое объяснение реального кода:

int a = 0;
int& b = a;

Здесь вы не создаете другую переменную, которая указывает на a ; вы просто добавляете другое имя в содержимое памяти, содержащее значение a . Эта память теперь имеет два имени: a и b , и ее можно решить с помощью любого имени.

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

При вызове функции компилятор обычно генерирует пространства памяти для аргументов, подлежащих копированию. Функциональная подпись определяет пространства, которые должны быть созданы, и дает имя, которое должно использоваться для этих пространств. Объявление параметра в качестве ссылки просто указывает компилятору использовать пространство с переменной входной переменной вместо выделения нового пространства памяти во время вызова метода. Может показаться странным сказать, что ваша функция будет непосредственно манипулировать переменной, объявленной в области вызова, но помните, что при выполнении скомпилированного кода больше нет области видимости; есть просто плоская память, и ваш код функции может манипулировать любыми переменными.

Теперь могут быть случаи, когда ваш компилятор может не знать ссылку при компиляции, например, при использовании переменной extern. Таким образом, ссылка может быть или не быть реализована как указатель в базовом коде. Но в примерах, которые я вам дал, это скорее всего не будет реализовано с помощью указателя.


Другое отличие состоит в том, что вы можете иметь указатели на тип void (и это означает указатель на что-либо), но ссылки на void запрещены.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

Я не могу сказать, что я действительно доволен этой особой разницей. Я бы предпочел, чтобы это разрешалось со ссылкой на что-либо с адресом и в противном случае такое же поведение для ссылок. Это позволило бы определить некоторые эквиваленты функций библиотеки C, таких как memcpy, используя ссылки.


Кроме того, ссылка, которая является параметром функции, которая является встроенной, может обрабатываться иначе, чем указатель.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

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

Конечно, для функций, которые не указаны, указатель и ссылка генерируют один и тот же код, и всегда лучше передавать значения intrinsics по значению, чем по ссылке, если они не изменяются и не возвращаются функцией.


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

С другой стороны, одно существенное различие между ссылками и указателями заключается в том, что временные адреса, назначенные для ссылок на константу, живут до тех пор, пока ссылка на const не выходит за рамки.

Например:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

будет печатать:

in scope
scope_test done!

Это языковой механизм, который позволяет ScopeGuard работать.


Существует очень важная нетехническая разница между указателями и ссылками: аргумент, передаваемый функции указателем, гораздо более заметен, чем аргумент, переданный функции с помощью неконстантной ссылки. Например:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

Назад в C, вызов, который выглядит, fn(x)может быть передан только по значению, поэтому он определенно не может изменить x; для изменения аргумента необходимо передать указатель fn(&x). Так что если аргументу не предшествовал, &вы знали, что он не будет изменен. (Обратное, &означает изменение, не было правдой, потому что иногда вам приходилось передавать большие структуры только для чтения constуказателем.)

Некоторые утверждают, что это такая полезная функция при чтении кода, что параметры указателя всегда должны использоваться для изменяемых параметров, а не для constссылок, даже если функция никогда не ожидает nullptr. То есть, эти люди утверждают, что такие сигнатуры функций, как fn3()указано выше, не допускаются. Примером этого может служить руководство по стилю C ++ в Google .


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

На самом высоком уровне идея ссылок заключается в том, что они являются прозрачными «алиасами». Ваш компьютер может использовать адрес, чтобы заставить их работать, но вы не должны беспокоиться об этом: вы должны думать о них как о «просто другом имени» для существующего объекта, и этот синтаксис отражает это. Они более строгие, чем указатели, поэтому ваш компилятор может более надежно предупредить вас, когда вы собираетесь создать обвисшую ссылку, чем когда вы собираетесь создать свисающий указатель.

Помимо этого, есть, конечно, некоторые практические различия между указателями и ссылками. Синтаксис для их использования, очевидно, различен, и вы не можете «пересаживать» ссылки, ссылаться на небытие или ссылаться на ссылки.


Я чувствую, что есть еще один момент, который здесь не был рассмотрен.

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

Хотя это может показаться поверхностным, я считаю, что это свойство имеет решающее значение для ряда функций C ++, например:

  • Шаблоны . Поскольку параметры шаблона являются типичными для утки, все синтаксические свойства типа имеют значение, поэтому часто один и тот же шаблон можно использовать как с, так Tи с T&.
    (или std::reference_wrapper<T>которые по-прежнему полагаются на неявный бросок T&)
    Шаблоны, которые охватывают T&и T&&те, и другие.

  • Lvalues . Рассмотрите оператор str[0] = 'X';без ссылок, он будет работать только для c-строк ( char* str). Возврат символа по ссылке позволяет пользовательским классам иметь одну и ту же нотацию.

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

  • Оператор перегружает . С помощью ссылок можно ввести косвенный вызов оператора - скажем, operator+(const T& a, const T& b)сохраняя одну и ту же инфиксную нотацию. Это также работает для регулярных перегруженных функций.

Эти точки дают значительную часть C ++ и стандартной библиотеки, поэтому это довольно важное свойство ссылок.


Возможно, некоторые метафоры помогут; В контексте вашего рабочего стола -

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

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


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

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 

Ссылка не является другим именем, данным некоторой памяти. Это неизменный указатель, который автоматически отключается при использовании. В основном это сводится к:

int& j = i;

Он внутренне становится

int* const j = &i;

Существует одно фундаментальное различие между указателями и ссылками, которые я не видел никого, о которых упоминалось: ссылки позволяют использовать семантику pass-by-reference в аргументах функции. Указатели, хотя сначала это не видно, нет: они предоставляют только семантику pass-by-value. Это было очень хорошо описано в этой статье .

С уважением, & rzej


Эта программа может помочь в понимании ответа на вопрос. Это простая программа ссылки «j» и указатель «ptr», указывающий на переменную «x».

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

Запустите программу и посмотрите на результат, и вы поймете.

Кроме того, поделитесь 10 минутами и посмотрите это видео: https://www.youtube.com/watch?v=rlJrrGV0iOg


Это основано на tutorial . То, что написано, делает его более ясным:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

Просто помните,

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

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

Посмотрите на следующее утверждение,

int Tom(0);
int & alias_Tom = Tom;

alias_Tomможет быть понято как alias of a variable(отличное от того typedef, которое есть alias of a type) Tom. Также хорошо забыть терминологию такого утверждения, чтобы создать ссылку Tom.







c++-faq