Я знаю, что ссылки - это синтаксический сахар, поэтому код легче читать и писать.
Но в чем отличия?
Я думаю, что пункт 2 должен быть таким: «Указатель может иметь значение NULL, а ссылка - нет. Только искаженный код может создать ссылку NULL, и его поведение не определено».
Указатели - это просто еще один тип объекта, и, как любой объект в C++, они могут быть переменной. С другой стороны, ссылки никогда не являются объектами, переменными Только.
Это компилируется без предупреждений: int &x = *(int*)0; на gcc. Ссылка действительно может указывать на NULL.
Я не уверен, что согласен с тем, что «ссылки - это синтаксический сахар». Как бы вы разработали конструкторы копирования на своем языке, если бы у вас не было ссылок?
Вероятно, мы могли бы взять адрес ссылки, например, идиому, принятую при реализации оператора присваивания, то есть T& T::operator=(const T& rhs) { if (this == &rhs) return *this; ... }.
также полезно связь
Я не чувствую себя квалифицированным, чтобы редактировать часть этого вопроса «прояснить заблуждение», но я думаю, что следует отметить, что если int i живет в регистре всю свою жизнь, ссылка вряд ли будет указателем - это может просто использовать псевдоним того же регистра.
ссылка - это псевдоним переменной
Ссылки - это не просто синтаксический сахар: они ВСЕГДА находятся в определенном состоянии. Это обеспечивается компилятором. Указатели не имеют такой гарантии.
Мне нравится, что самое первое предложение - полное заблуждение. Ссылки имеют собственную семантику.
@Calmarius Нет, это неверно. Это ссылка на то, что находится в этой ячейке памяти. Сама ссылка действительна. Однако доступ к тому, что там есть, не определен.
@Calmarius, конечно, чтобы попасть туда, вы разыменовали нулевой указатель. . . который сам по себе не определен. . .
Этот вопрос показывает, что арифметические операции с указателями выполняются по ссылке («&obj + 5»). Не нарушает ли это предположение о псевдониме, когда сайт вызова предполагает, что вызываемая функция не будет выполнять такую арифметику. Таким образом, имеется в виду справочная арифметика UB?
Указатель в основном хранит адрес своего указателя.
int * p = NULL; int & r = * p; ссылка, указывающая на NULL; if (r) {} -> boOm;)
@QiangXu Нет, это адрес целевой ссылки. Прочтите еще раз: ссылка - это псевдоним другого объекта. (За исключением этапа создания) его семантика полностью идентична тому, если вы использовали исходное имя цели.
Я бы не сказал, что ссылки в C++ реализованы как указатели, это две разные абстракции для адрес объекта. Подумайте, как работает оборудование: в оборудовании у вас нет «указателей», у вас есть регистры, память и инструкции, которые работают с регистрами и памятью.
Мне нравится представлять, что переменные - это люди на вечеринке, тогда указатель - это кто-то, кто ходит своим пальцем, обычно нацеленным на кого-то другого, и кого это можно изменить, а в некоторых случаях кто-то другой покидает вечеринку и больше не может быть указанным (вне области действия) или убитым (уничтоженным, это группа Плохо!), в других случаях указатель может указывать на нового человека (значение изменено) или этаж (установлено на 0, nullptr и т. д.). Для сравнения, переменная ссылка - это новый бейджик, который человек может надеть на себя (на который также может указывать указатель!)
a pointer and a reference both occupy the same amount of memory - это неправильно. Указатель на виртуальную функцию может занимать больше памяти, чем ссылка.
Неужели я единственный, кто думает, что указатели легче читать и что они проясняют код больше, чем ссылки?
Очень важный момент: программист должен управлять памятью указателей, а не ссылок.
Указатель может быть нулевым, т.е. принимать значение, которое явно не указывает на какой-либо действительный объект или функцию. С другой стороны, пустая ссылка не может существовать в четко определенной программе, потому что единственный способ создать такую ссылку - это привязать ее к объекту или функции, полученным путем разыменования нулевого указателя, что вызывает неопределенное поведение.





Помимо синтаксического сахара, ссылка - это указатель const (указатель нет на const). Вы должны установить, к чему это относится, когда объявляете ссылочную переменную, и вы не можете изменить ее позже.
Обновление: теперь, когда я думаю об этом еще немного, есть важное различие.
Целевой объект константного указателя можно заменить, взяв его адрес и используя константное приведение.
Цель ссылки не может быть заменена никаким способом, кроме UB.
Это должно позволить компилятору выполнить дополнительную оптимизацию ссылки.
Я думаю, что это лучший ответ на данный момент. Другие говорят о ссылках и указателях, как будто они разные звери, а затем объясняют, чем они отличаются по поведению. Имхо это не облегчает жизнь. Я всегда понимал под ссылками T* const с другим синтаксическим сахаром (что позволяет избавиться от множества * и & из вашего кода).
«Целевой объект константного указателя можно заменить, взяв его адрес и используя константное приведение». Это неопределенное поведение. Подробнее см. stackoverflow.com/questions/25209838/….
Попытка изменить либо референт ссылки, либо значение константного указателя (или любого константного скаляра) является недопустимым равенством. Что вы можете сделать: удалить квалификацию const, добавленную неявным преобразованием: int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci); в порядке.
Здесь разница между UB и буквально невозможна. В C++ нет синтаксиса, который позволил бы вам изменить точки отсчета.
Не невозможно, что сложнее, вы можете просто получить доступ к области памяти указателя, моделирующей эту ссылку, и изменить ее содержимое. Это, безусловно, можно сделать.
@CarloWood точно, Godbolt показывает 0 различий в выводе asm при использовании указателя и при использовании ссылки. Это все еще константный указатель в стеке. Единственное различие, которое он имеет, - на уровне компилятора, то есть компилятор интерпретирует 'b' вместо '* b' как получение указанного значения и '& b' вместо b, и адрес фактического указателя const не может быть взятый
Указатель можно переназначить:
int x = 5;
int y = 6;
int *p;
p = &x;
p = &y;
*p = 10;
assert(x == 5);
assert(y == 10);
Ссылка не может быть повторно привязана и должна быть привязана при инициализации:
int x = 5;
int y = 6;
int &q; // error
int &r = x;
Переменная-указатель имеет свой собственный идентификатор: отдельный видимый адрес памяти, который можно взять с помощью унарного оператора &, и определенный объем пространства, который можно измерить с помощью оператора sizeof. Использование этих операторов для ссылки возвращает значение, соответствующее тому, к чему привязана ссылка; адрес и размер ссылки не видны. Поскольку ссылка таким образом предполагает идентичность исходной переменной, удобно думать о ссылке как о другом имени той же переменной.
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2); // &x == &r
assert(&p != &p2);
Вы можете иметь произвольно вложенные указатели на указатели, предлагающие дополнительные уровни косвенности. Ссылки предлагают только один уровень косвенности.
int x = 0;
int y = 0;
int *p = &x;
int *q = &y;
int **pp = &p;
**pp = 2;
pp = &q; // *pp is now q
**pp = 4;
assert(y == 4);
assert(x == 2);
Указателю может быть назначен nullptr, тогда как ссылка должна быть привязана к существующему объекту. Если вы достаточно постараетесь, вы можете привязать ссылку к nullptr, но это неопределенный, и он не будет вести себя последовательно.
/* the code below is undefined; your compiler may optimise it
* differently, emit warnings, or outright refuse to compile it */
int &r = *static_cast<int *>(nullptr);
// prints "null" under GCC 10
std::cout
<< (&r != nullptr
? "not null" : "null")
<< std::endl;
bool f(int &r) { return &r != nullptr; }
// prints "not null" under GCC 10
std::cout
<< (f(*static_cast<int *>(nullptr))
? "not null" : "null")
<< std::endl;
Однако вы можете иметь ссылку на указатель со значением nullptr.
Указатели могут перебирать массив; вы можете использовать ++ для перехода к следующему элементу, на который указывает указатель, и + 4 для перехода к 5-му элементу. Это не имеет значения, какого размера объект, на который указывает указатель.
Указатель необходимо разыменовать с помощью *, чтобы получить доступ к ячейке памяти, на которую он указывает, тогда как ссылку можно использовать напрямую. Указатель на класс / структуру использует -> для доступа к своим членам, тогда как ссылка использует ..
Ссылки не могут быть помещены в массив, тогда как указатели могут быть (упомянуты пользователем @litb)
Ссылки на константы можно привязать к временным файлам. Указатели не могут (не без косвенного обращения):
const int &x = int(12); // legal C++
int *y = &int(12); // illegal to take the address of a temporary.
Это делает const & более удобным для использования в списках аргументов и т. д.
Ссылка может быть нулевой. В некоторых случаях вы передаете параметры ссылки следующим образом: function (* ptr); Если ptr равен NULL, ссылка будет такой же.
... но разыменование NULL не определено. Например, вы не можете проверить, является ли ссылка NULL (например, & ref == NULL).
В VC++ * ptr выйдет из строя, уверен, что в gC++ он тоже будет segfault
Я считаю, что пункт 2 неверен. См. Этот пример программы: pastebin.com/f5252f8a8 Это означает, что в gcc на моем компьютере ссылка реализована с помощью указателя. Вывод: Размер класса: 8 Значение ref до взлома: 1 Значение ref после взлома: 2 Значение ref после взлома с измененным y: 3
Номер 2 - это нет true. Ссылки - это не просто «другое имя той же переменной». Ссылки могут передаваться в функции, храниться в классах и т. д. Способом, очень похожим на указатели. Они существуют независимо от переменных, на которые они указывают.
Также это: «Указатель имеет свой собственный адрес памяти и размер в стеке». Указатели не нужно размещать в стеке (и большинство из них обычно не размещаются).
Дерек указатель - это такая же переменная, как и любая другая. В системе x86 это 4 байта. Эти 4 байта находятся в стеке. Я не утверждал, что то, на что они указывают, находится в стеке.
Я не думаю, что это хорошо и безопасно, потому что это неправильно :-p Я был бы склонен убрать пункт 2 из другого хорошего ответа.
Брайан, имея в виду пункт 2, знаете ли вы какой-нибудь компилятор, который действительно это делает? Это зависит от реализации, но ссылки обычно реализуются с помощью указателей.
как реализована внутренняя структура ссылки, не имеет значения. Важно то, что адрес ссылки совпадает с адресом самой переменной. Следовательно, они могут использоваться как взаимозаменяемые.
Ник, вы можете использовать тот же аргумент, что и ваша ссылка, чтобы сказать, что указатели реализованы как указатели на указатели, но вы не упомянули об этом. int x = 0; int y = 0; int р = & х; * (int *) (& p) = & y; Потому что это не имеет значения.
Я убедился, что ссылка действительно занимает некоторое место в стеке, я изменил свой № 3, чтобы включить эту информацию. Однако адрес оператора по-прежнему указывает то же самое, что и переменная, на которую он ссылается.
Брайан, стек не имеет значения. Ссылки и указатели не должны занимать место в стеке. Оба они могут быть размещены в куче.
Дерек, пожалуйста, посмотрите мой предыдущий пост, я не думаю, что вы его читали или вы не читали его достаточно внимательно ... «Дерек, указатель - это переменная, как и любая другая. В системе x86 это 4 байта. В стеке 4 байта. Я не утверждал, что то, на что они указывают, находится в стеке "
Брайан, тот факт, что переменная (в данном случае указатель или ссылка) требует места, не означает ли нет места в стеке. Указатели и ссылки могут не только точка в куче, они могут фактически быть выделенный в куче.
согласен, но я просто пытаюсь понять это в этой ситуации: int x = 0; int p = & x ;, p занимает в стеке 4 байта. Я знаю, что можно сказать (новый int) для размещения самого указателя в куче.
вы можете изменить, если хотите быть более точным, но я думаю, что это действительно не нужно.
еще одна важная разница: ссылки не могут быть помещены в массив
Дерек, в стандарте нет упоминания о том, что для ссылки требуется место. независимо от того, что вы делаете со ссылкой, даже если вы берете ее адрес, не может быть никакого дополнительного места, и ссылка может быть просто «настоящим» псевдонимом для другого объекта, на который она ссылается.
@litb: Хотя стандарт может не требовать места, выделенного для ссылки, во многих случаях компилятор будет вынужден зарезервировать место. Рассмотрим ссылку внутри класса, конечно, каждый экземпляр класса может ссылаться на разные внешние объекты, и какой объект, на который они ссылаются, должен каким-то образом удерживаться - в большинстве реализаций как автоматически разыменованный указатель (я действительно не могу думать о какой-либо другой возможной реализации который подходит для всех случаев использования, но опять же, я не эксперт)
Я обнаружил, что глядя на изображения на этой странице: www-numi.fnal.gov/offline_software/srt_public_context/WebDoc s /… при чтении этого сообщения очень легко понять разницу;)
@Brian: Ссылки могут занимать память, как и указатели. Например, если у вас есть переменная-член класса, которая является ссылкой, то для каждого экземпляра класса будет выделена память для этой ссылки. В некоторых случаях компилятор может оптимизировать необходимость выделения памяти для ссылки; и нет хорошего способа получить адрес ссылки в вашем коде (& my_reference просто возвращает адрес упомянутого объекта). В этом отношении я согласен с тем, что ссылки действуют так, как будто они не занимают память, но могут.
Разве то, что 7) добавляет к 2), не является Только, что ссылки могут быть реализованы по-разному в разных языковых реализациях?
@David Rodríguez - dribeas: Я считаю, что в своем предыдущем комментарии вы задумались. Действительно, компилятор может быть вынужден зарезервировать место для ссылки, но во многих случаях в этом нет необходимости, и такие случаи часто легко обнаружить во время компиляции. Когда это произойдет, компилятор не будет использовать место для ссылки. Компиляторам намного сложнее оптимизировать указатели: сначала они должны убедиться, что никакой код никогда не принимает адрес указателя и не может получить его из контекста.
Пуля 1 несколько неверна. В нем говорится, что ссылки должны быть инициализированы (правильные) и что их нельзя переназначить (неверно): ideone.com/hkwoRZ
@kriss: С момента появления лямбда-выражений, которые могут захватывать ссылки по ссылке, возможность оптимизировать хранилище, фактически содержащее адрес референта и связанный анализ, чтобы определить, безопасно ли это, одинакова для указателей и ссылок.
@JohannGerell: Вы ошибаетесь. Ваш пример не переназначает ссылку. Левый операнд присваивания разрешается в объект a, а не в ссылку c.
@BenVoigt: Вы правы! ideone.com/SwRZ6r - спасибо, что научил меня чему-то сегодня, я вполне мог вызвать одну или две ошибки в свои дни из-за этого недоразумения ...
@ Бен Фойгт: Я не уверен, что понимаю ваш комментарий. Что меняет введение лямбда для кода, не использующего лямбда? Также при оптимизации указателей (что действительно может быть сделано в некоторых случаях) вы должны убедиться, что это не пустой указатель. Если вы посмотрите на вывод сборки clang, бывают случаи, когда он вводит такие проверки перед оптимизацией указателя в последующем коде.
@kriss: Вы сказали: «Чтобы компиляторы оптимизировали указатели: они сначала должны убедиться, что ни один код не принимает адрес указателя». Лямбда-выражения с захватом по ссылке, когда ссылка появляется в закрывающем наборе, фактически сохраняют адрес внутренней памяти ссылки, поэтому теперь компилятор, оптимизирующий ссылки, сталкивается с теми же проблемами, что и всегда с указателями.
@BenVoigt: Я до сих пор не понимаю, почему это должно иметь значение, особенно с введением лямбда-выражений, хранение эталонного внешнего хранилища совершенно нормально, потому что в этом смысл ссылки (ссылка - это просто псевдоним некоторого существующего хранилища). Оптимизировать ссылку намного проще, чем это, она просто обрабатывает ее как псевдоним исходной переменной. Это что-то сложное для интерфейсов функций, но простое внутри тела функций или встраивание (потому что ссылки всегда будут иметь псевдонимы одного и того же хранилища, что неверно для указателей).
@kriss: Думаю, вы не поняли мою точку зрения. При захвате лямбда может потребоваться адрес самой ссылки, а НЕ объект, к которому она привязана. Нет другой языковой функции, которая создает ссылку на ссылку.
@BenVoigt: Полагаю, мне придется в этом покопаться, потому что «получение адреса самой ссылки» для меня все еще бессмысленное предложение (потому что ссылка, имеющая адрес или нет, должна быть деталью реализации). Я понимаю, что лямбда C++ эквивалентна вызову функции с закрывающим контекстом (все, что вы можете делать с лямбдами, можно сделать с помощью объектов-функторов). Проблемы (скрытые указатели) для оптимизаторов уже возникают, как только вы передаете параметр функции или классу по ссылке, и я ожидаю таких же проблем с лямбдами. Но я не понимаю, зачем было больше проблем.
Указатель можно сделать не переназначаемым: int x; int * const p = & x; Обратите внимание, что const после символа звездочки, это не то же самое, как если бы оно было перед звездочкой. Теперь вы не можете записать что-либо вроде p = null или другого переназначения, но вы все равно можете написать * p = 5. Константный указатель на константные данные будет иметь вид const int * const p = & x; или int const * const p = & x; (эти два эквивалентны).
Интересно, что точка 9 - это одна из областей, в которых C++ отличается от C - в C вы может берете адрес временного объекта: int *y = &(int){12};.
re: 2. Переменная-указатель получает 4 байта (на некоторых машинах) в стеке, а также ссылочную переменную. Эта часть вашего предложения неверна.
@Leushenko в том, что код C (int){12} является составным литералом, а не временным
По поводу 4.: это ошибка компиляции только потому, что вы ее не разыменовали. Это не защита от nullptr, это просто потому, что нет специальной нулевой ссылки, которую нельзя использовать в качестве инициализатора без приведения типа, в то время как есть nullptr, который можно использовать без приведения типа. Легко написать nullreference_t, который будет делать то же самое.
Указатели работают как последний параметр перед функцией elipsis / varargs (int printf( char const * format, ... )). Ссылки не, с довольно неожиданными результатами, если вы попробуете ...
Хороший ответ. Кроме того, думаю, я 1500-й голосующий :)
Указатель нельзя переназначить, если он соответствует требованиям const. Таким образом, в указателях можно получить преимущество ссылки без повторного назначения.
Итак, в основном ссылки - это постоянные указатели, которые представляют переменную / объект, как это сделал бы *ptr?
Ссылка никогда не может быть NULL.
См. Ответ Марка Рэнсома в качестве контрпримера. Это наиболее часто утверждаемый миф о ссылках, но это миф. Единственная гарантия, которая у вас есть по стандарту, заключается в том, что у вас сразу появляется UB, когда у вас есть ссылка NULL. Но это все равно, что сказать: «Эта машина безопасна, она никогда не съедет с дороги (мы не несем ответственности за то, что может случиться, если вы все равно свернете с дороги. Она может просто взорваться)».
@cmaster: В действующей программе, ссылка не может быть нулевой. Но указатель может. Это не миф, это факт.
@Mehrdad Да, действующие программы остаются в пути. Но нет никакого барьера трафика, который бы заставлял вашу программу выполнять это на самом деле. На больших участках дороги фактически отсутствует разметка. Так что съехать с дороги ночью очень легко. И для отладки таких ошибок очень важно, чтобы вы знать это могло произойти: нулевая ссылка может распространяться до того, как выйдет из строя ваша программа, как это может сделать нулевой указатель. И когда это происходит, у вас есть код вроде void Foo::bar() { virtual_baz(); }, который дает сбой. Если вы не знаете, что ссылки могут быть нулевыми, вы не сможете отследить нулевое значение до его источника.
int * p = NULL; int & r = * p; ссылка, указывающая на NULL; if (r) {} -> boOm;) -
@sree int &r=*p; - это неопределенное поведение. В этот момент у вас нет «ссылки, указывающей на NULL», у вас есть программа, которая больше нельзя рассуждать о вообще.
@cdhowie Вау ... @sree не подразумевал, что int *p = NULL; int &r=*p; был хорошей практикой, они просто приводили пример того, как ссылка на самом деле может быть NULL. Фиолетовые драконы UB не появятся до тех пор, пока вы не использовать нулевой ссылки, так же как UB не происходит с нулевыми указателями, пока вы не «разыменовываете» указатель. Но даже если это UB, в обоих случаях вполне ожидаемо, что UB приведет к SIGSEGV или исключению доступа к памяти, если вы не работаете во встроенной системе, и в этом случае вы вполне можете попытаться получить доступ к чему-то, что действительно находится по адресу 0.
@phonetagger Да, в большинстве реализаций вы не увидите сбоя, пока не будет использована ссылка. Однако это не отменяет моего утверждения - согласно стандарту, UB вызывается, когда ссылка размещена. После того, как вы связали недопустимый объект со ссылкой, с этого момента поведение программы не определено. Возможность того, что он будет работать, как задумано, до тех пор, пока ссылка не будет использована, безусловно, является одним из возможных результатов UB. Другой компилятор и / или архитектура могут показывать другие результаты.
@phonetagger Надеюсь, этот пример доводит до ума, что мы вошли в сферу UB. Мы создаем ссылку на недопустимый (нулевой) объект. Программа никогда не дает сбоев, но дает «неправильный» результат, потому что оптимизатор предположил, что UB не произойдет. Обратите внимание, что компиляция с -O0 меняет вывод программы! На более высоких уровнях оптимизации мы даже не можем спросить, является ли адрес ссылки нулевым, потому что оптимизатор знает, что никакая ссылка не может быть нулевой; &x != nullptr на эталоне x - это тавтология!
@phonetagger Обратите внимание, что в связанном примере мы никогда не используем (недействительный) объект-референт! Тем не менее, UB проявляется, как только мы размещаем ссылку.
cdhowie, что на самом деле то же самое с указателями, если вы знаете, что делаете, вы всегда все делаете правильно, указатель совершенно безопасен ... А если вы сделаете ошибку, это не так. Ссылки такие же. И поскольку вам в любом случае нужны указатели в программах, и вам понадобится ссылка в остальной части кода, вы, скорее всего, в какой-то момент назначите указатель на ссылку, сделав разницу между обеими концепциями с точки зрения безопасности.
Вопреки распространенному мнению, ссылка может быть 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.
@Vincent: На самом деле, нет, иногда вторая строка вызывается незаметно (помните, что компиляторы могут реализовывать ссылки как указатели, так что ...) ... Так что сбой происходит, когда вы используете ссылку.
В любом случае, кодировщик разыменовал указатель, не проверяя его. Это источник ошибки. В этот момент, а иногда и после, программа выйдет из строя. Это может происходить каждый раз, когда указатель преобразуется в ссылку. Это означает, что вы можете снизить риск, удалив как можно больше указателей ...
mark имеет допустимый аргумент. аргумент о том, что указатель может быть NULL, и поэтому вы должны проверить, также не является реальным: если вы говорите, что функция требует отличного от NULL, то вызывающий должен это сделать. поэтому, если вызывающий абонент этого не делает, он вызывает неопределенное поведение. так же, как Марк сделал с плохой ссылкой
В зависимости от реализации, оборудования и ОС установка ссылочной переменной на недопустимый адрес может быть исключением. Адрес 0 может быть или недействительным, опять же, в зависимости от оборудования и ОС.
Описание ошибочное. Этот код может создавать или не создавать ссылку, которая имеет значение NULL. Его поведение не определено. Это может создать совершенно действительную ссылку. Он может вообще не создать какую-либо ссылку.
@ Дэвид Шварц, если бы я говорил о том, как все должно работать в соответствии со стандартом, вы были бы правы. Но это нет, о чем я говорю - я говорю о фактическом наблюдаемом поведении с очень популярным компилятором и экстраполяции, основанной на моих знаниях типичных компиляторов и архитектур ЦП, на то, что произойдет с наверное. Если вы считаете, что ссылки лучше указателей, потому что они безопаснее, и не считаете, что ссылки могут быть плохими, вы однажды столкнетесь с простой проблемой, как и я.
Если вы разрешаете неопределенное поведение, тогда все возможно ... у вас может быть 1 < 0, int, содержащий 3.5, функцию, запускающую ракеты. Если что-то возможно, ничего примечательного.
@ M.M, вы полностью упускаете мою точку зрения. Многие люди считают, что ссылки - это безопаснее, чем указатели, и мой опыт показывает, что это не так. Слишком легко получить неопределенное поведение, не осознавая этого. Это сообщение, которое я пытаюсь передать этим ответом.
Разыменование нулевого указателя неверно. Любая программа, которая делает это, даже для инициализации ссылки, неверна. Если вы инициализируете ссылку с помощью указателя, вы всегда должны проверять, что указатель действителен. Даже если это удастся, базовый объект может быть удален в любой момент, оставив ссылку для ссылки на несуществующий объект, верно? То, что вы говорите, - это хорошо. Я думаю, что настоящая проблема здесь в том, что ссылку НЕ нужно проверять на «пустоту», когда вы ее видите, и указатель должен быть, как минимум, утвержден.
Выше было сказано, что универсально согласованный способ надежного отслеживания времени жизни и владения объектами в C++ - это использование RAII. У std есть надежные классы для этой работы со своими собственными компромиссами. Мы могли бы быть похожими на C# и Java (грубое обобщение) и просто передавать все по значению std :: shared_ptr, но это был бы очень раздутый способ передачи параметров, поэтому ссылки и указатели по-прежнему играют роль. В последнее время я склонялся к передаче простых типов POD по значению вместо константной ссылки, потому что на целевых платформах, над которыми я работаю, это более эффективно (без косвенности и параметров в регистрах).
Пример: godbolt.org/g/T4Uzvq .. Первая версия с передачей по значению выполняется в регистрах ЦП, а вторая функция, где параметры передаются по константной ссылке, данные элемента загружаются из памяти. В худшем случае для версии по значению вызывающий должен загрузить значения в регистры. Версия по константной ссылке ВСЕГДА загружает значения и в худшем случае также должна сохранять их в памяти, даже если значения были в регистрах. LTCG и тому подобное могут изменить картину, но в целом компромисс в пользу первой формы.
optional<T&>.
@IInspectable совсем другое дело, речь шла о простых ссылках. Например, в C# я могу перейти на String s = null; и получить нулевую ссылку на строку. Поскольку в C# ссылки можно переназначать, это вполне разумно. В ссылках C++ не могу следует переназначить, чтобы пустая ссылка была бы бесполезной, и язык не пытается ее поддерживать.
optional<T&> would have been just that. Except, it doesn't exist. Mostly due to arbitrarily limiting reference semantics, making them very alien to C++ value semantics (as outlined in это выступление на CppCon 2017).
@Mark Ransom: проблема с этим ответом заключается в том, что во втором абзаце, где вы говорите: «Любая недопустимая ссылка вызывает призрак неопределенного поведения», это звучит так, как будто вы имеете в виду, что r = 1 имеет неопределенное поведение, и что дерево кода будет в порядке, если оно ограничено к первым двум строкам. Фактически, вторая строка, int & r = *p, имеет неопределенное поведение и может вызвать сбой (или сделать что-нибудь еще) даже без r = 1. Глядя на ваши комментарии, я думаю, вы это знаете, но текущий текст вашего ответа может ввести в заблуждение любого, кто этого еще не осознает.
Если вы хотите быть по-настоящему педантичным, вы можете сделать одну вещь со ссылкой, которую нельзя сделать с помощью указателя: продлить время жизни временного объекта. В C++, если вы привязываете константную ссылку к временному объекту, время жизни этого объекта становится временем жизни ссылки.
std::string s1 = "123";
std::string s2 = "456";
std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;
В этом примере s3_copy копирует временный объект, который является результатом конкатенации. Тогда как s3_reference по сути становится временным объектом. На самом деле это ссылка на временный объект, у которого теперь то же время жизни, что и у ссылки.
Если вы попробуете это сделать без const, он не будет скомпилирован. Вы не можете привязать неконстантную ссылку к временному объекту, равно как и не можете взять его адрес.
но какой для этого вариант использования?
Итак, s3_copy создаст временный объект, а затем скопирует его в s3_copy, тогда как s3_reference напрямую использует временное. Затем, чтобы быть действительно педантичным, вам нужно взглянуть на оптимизацию возвращаемого значения, в соответствии с которой компилятору разрешено исключить конструкцию копирования в первом случае.
@digitalSurgeon: Магия там довольно сильная. Время жизни объекта увеличивается за счет привязки const &, и только когда ссылка выходит за пределы области видимости, вызывается деструктор ссылочного типа действительный (по сравнению с ссылочным типом, который может быть базовым). Поскольку это ссылка, нарезка между ними выполняться не будет.
Обновление для C++ 11: последнее предложение должно гласить: «Вы не можете привязать неконстантную ссылку lvalue к временному», потому что вы может привязываете неконстантную ссылку rvalue к временному, и у него такое же поведение, увеличивающее время жизни.
Может ли кто-нибудь дать мне конкретный пример того, как const & может продлить жизнь переменной, на которую он ссылается? Поскольку ссылки должны быть инициализированы, их всегда нужно будет объявлять после переменной, на которую она будет ссылаться, и поэтому я не могу представить себе ситуацию, когда ссылка будет в другой области или в области, которая будет длиться долго. длиннее, чем область видимости переменной.
@BrunoSantos Во-первых, ссылки не "инициализируются", вы не понимаете, как они работают с указателями. На вопрос: подумайте о перегрузке операторов. Когда, например, происходит сумма двух переменных, машина выделяет временную память, чтобы не изменять данные внутри самих переменных, сначала перемещается, а затем суммирует вторую до значения во временной памяти.
@BrunoSantos Вы упустили суть. const & может продлить время жизни временный, а не переменной. Обычно время жизни временного объекта - это просто полное выражение, в котором оно генерируется, если только на него не ссылается const, и в этом случае он получает время жизни ссылочной переменной. (Копирование / перемещение его в именованную переменную технически не является продлением срока службы.)
@AhmadMushtaq: Ключевое использование этого - производные классы. Если наследование не используется, вы также можете использовать семантику значений, которая будет дешевой или бесплатной из-за конструкции RVO / move. Но если у вас Animal x = fast ? getHare() : getTortoise(), то x столкнется с классической проблемой нарезки, тогда как Animal& x = ... будет работать правильно.
Вот ваш "вариант использования": void foo(const std::string&); foo(s1 + s2);
Вы забыли самую важную часть:
доступ к члену с указателями использует ->
доступ к члену со ссылками использует .
foo.bar превосходит foo->bar на четко точно так же, как vi превосходит четко над Emacs :-)
@Orion Edwards> членский доступ с использованием указателей ->> членский доступ со ссылками использует. Это не на 100% правда. У вас может быть ссылка на указатель. В этом случае вы можете получить доступ к членам указателя без ссылки, используя -> struct Node {Node * next; }; Узел * первый; // p - ссылка на указатель void foo (Node * & p) {p-> next = first; } Узел * bar = новый узел; foo (бар); - OP: Вы знакомы с концепциями rvalues и lvalues?
Умные указатели имеют и то, и другое. (методы класса интеллектуального указателя) и -> (методы базового типа).
Оператор @ user6105 Орион Эдвардс на самом деле верен на 100%. "члены доступа [указателя], на который разыграна ссылка" Указатель не имеет членов. Объект, на который ссылается указатель, имеет члены, и доступ к ним - это именно то, что -> предоставляет для ссылок на указатели, так же как и с самим указателем.
почему . и -> как-то связаны с vi и emacs :)
@artM - это была шутка, и, вероятно, не имеет смысла для людей, для которых английский не является родным. Мои извинения. Объяснение того, лучше ли vi, чем emacs, является полностью субъективным. Некоторые люди думают, что vi намного лучше, а другие думают прямо противоположное. Точно так же я думаю, что использование . лучше, чем использование ->, но, как и vi против emacs, это полностью субъективно, и вы ничего не можете доказать.
Синтаксис . упрощает перенос вашего C++ на C#, язык, который вы должны были использовать.
Первая половина может быть правдой, но вряд ли «самая важная». Другой - просто мнение.
@ user3840170, это шутка :-) В это трудно поверить, но в первые дни переполнения стека (обратите внимание, что ответ датирован сентябрем 2008 года) у людей было чувство юмора, и на этом сайте было приятно потусоваться на. Если честно, трудно поверить, что какой-то педант до сих пор не пометил этот ответ как удаленный
Я использую ссылки, если они мне не нужны:
Нулевые указатели могут использоваться как дозорное значение, часто дешевый способ избегать перегрузки функций или использования булево.
Вы можете выполнять арифметические операции с указателем.
Например, p += offset;
Вы можете написать &r + offset, где r был объявлен как ссылка
Еще одно интересное использование ссылок - предоставить аргумент по умолчанию пользовательского типа:
class UDT
{
public:
UDT() : val_d(33) {};
UDT(int val) : val_d(val) {};
virtual ~UDT() {};
private:
int val_d;
};
class UDT_Derived : public UDT
{
public:
UDT_Derived() : UDT() {};
virtual ~UDT_Derived() {};
};
class Behavior
{
public:
Behavior(
const UDT &udt = UDT()
) {};
};
int main()
{
Behavior b; // take default
UDT u(88);
Behavior c(u);
UDT_Derived ud;
Behavior d(ud);
return 1;
}
Вариант по умолчанию использует «привязку константной ссылки к временному» аспекту ссылок.
Неважно, сколько места он занимает, потому что вы не можете увидеть никаких побочных эффектов (без выполнения кода) любого места, которое он займет.
С другой стороны, одно из основных различий между ссылками и указателями заключается в том, что временные ссылки, назначенные константным ссылкам, живут до тех пор, пока константная ссылка не выйдет за пределы области видимости.
Например:
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 работать.
Вы не можете взять адрес ссылки, но это не значит, что они физически не занимают места. Без оптимизаций, безусловно, могут.
Несмотря на влияние, фраза «Ссылка в стеке вообще не занимает места» явно неверна.
@Tomalak, ну это тоже зависит от компилятора. Но да, это немного сбивает с толку. Полагаю, было бы менее запутанно просто удалить это.
В любом конкретном случае это может быть, а может и нет. Так что категорическое утверждение «это не так» неверно. Вот что я говорю. :) [Я не могу вспомнить, что говорится в стандарте по этому поводу; правила рекомендательных членов могут передавать общее правило «ссылки могут занимать место», но у меня нет моей копии стандарта здесь, на пляже: D]
На самом деле ссылка не похожа на указатель.
Компилятор хранит «ссылки» на переменные, связывая имя с адресом памяти; его задача - преобразовать любое имя переменной в адрес памяти при компиляции.
Когда вы создаете ссылку, вы только говорите компилятору, что назначаете другое имя переменной-указателю; вот почему ссылки не могут «указывать на нуль», потому что переменная не может быть и не быть.
Указатели - это переменные; они содержат адрес какой-либо другой переменной или могут быть нулевыми. Важно то, что указатель имеет значение, а ссылка имеет только переменную, на которую он ссылается.
Теперь объяснение реального кода:
int a = 0;
int& b = a;
Здесь вы не создаете другую переменную, указывающую на a; вы просто добавляете другое имя к содержимому памяти, содержащему значение a. Эта память теперь имеет два имени, a и b, и к ней можно обращаться, используя любое имя.
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
При вызове функции компилятор обычно генерирует области памяти для копируемых аргументов. Сигнатура функции определяет пространства, которые должны быть созданы, и дает имя, которое следует использовать для этих пространств. Объявление параметра как ссылки просто указывает компилятору использовать пространство памяти входной переменной вместо выделения нового пространства памяти во время вызова метода. Может показаться странным сказать, что ваша функция будет напрямую манипулировать переменной, объявленной в вызывающей области, но помните, что при выполнении скомпилированного кода области больше нет; там просто плоская память, и ваш код функции может манипулировать любыми переменными.
Теперь могут быть случаи, когда ваш компилятор может не знать ссылку при компиляции, например, при использовании внешней переменной. Таким образом, ссылка может быть реализована или не реализована как указатель в базовом коде. Но в приведенных мною примерах это, скорее всего, не будет реализовано с помощью указателя.
Ссылка - это ссылка на l-значение, не обязательно на переменную. Из-за этого он намного ближе к указателю, чем к реальному псевдониму (конструкция времени компиляции). Примеры выражений, на которые можно ссылаться: * p или даже * p ++.
Правильно, я просто указывал на тот факт, что ссылка не всегда может помещать новую переменную в стек так, как это сделает новый указатель.
@VincentRobert: Он будет действовать так же, как указатель ... если функция встроена, и ссылка, и указатель будут оптимизированы. Если есть вызов функции, необходимо передать в функцию адрес объекта.
int * p = NULL; int & r = * p; ссылка, указывающая на NULL; if (r) {} -> boOm;)
Этот акцент на стадии компиляции кажется приятным, пока вы не вспомните, что ссылки могут передаваться во время выполнения, и в этот момент статическое псевдонимание исчезает из окна. (Кроме того, ссылки представляют собой обычно, реализованные как указатели, но стандарт не требует этого метода.)
@sree> неправильно. Этот образец просто не подходит для C++. Некоторые компиляторы предупредят об этом. Некоторые даже просто удалят это заявление (в некоторых случаях это делает clang), делая любое утверждение о том, что вы ошиблись. То, что вы получите, не определено.
ссылка можно рассматривать как постоянный указатель (не путать с указателем на постоянное значение!) С автоматическим косвенным обращением, т.е. компилятор применит для вас оператор *.
Все ссылки должны быть инициализированы ненулевым значением, иначе компиляция не удастся. Невозможно получить адрес ссылки - вместо этого оператор адреса вернет адрес значения, на которое указывает ссылка, - также невозможно выполнить арифметические операции со ссылками.
Программистам на C могут не нравиться ссылки на C++, поскольку они больше не будут очевидны, когда происходит косвенное обращение или если аргумент передается по значению или по указателю, не глядя на сигнатуры функций.
Программистам на C++ может не нравиться использование указателей, поскольку они считаются небезопасными - хотя ссылки на самом деле ничем не безопаснее, чем постоянные указатели, за исключением самых тривиальных случаев - не обладают удобством автоматического косвенного обращения и несут другую семантическую коннотацию.
Рассмотрим следующий оператор из C++ FAQ:
Even though a reference is often implemented using an address in the underlying assembly language, please do not think of a reference as a funny looking pointer to an object. A reference is the object. It is not a pointer to the object, nor a copy of the object. It is the object.
Но если ссылка В самом деле была объектом, как могли быть висячие ссылки? В неуправляемых языках ссылки не могут быть более «безопасными», чем указатели - обычно просто нет способа надежно сопоставить значения через границы области видимости!
Исходя из фона C, ссылки C++ могут выглядеть несколько глупо, но все равно следует использовать их вместо указателей, где это возможно: автоматическое косвенное обращение является удобно, а ссылки становятся особенно полезными при работе с RAII - но не из-за какой-либо предполагаемой безопасности преимущество, а скорее потому, что они делают написание идиоматического кода менее неудобным.
RAII - одна из центральных концепций C++, но она нетривиально взаимодействует с семантикой копирования. Передача объектов по ссылке позволяет избежать этих проблем, поскольку не требуется копирование. Если бы в языке не было ссылок, вам пришлось бы использовать вместо них указатели, которые более громоздки в использовании, тем самым нарушая принцип проектирования языка, согласно которому оптимальное решение должно быть проще, чем альтернативы.
@Christoph: ну, ссылки могут болтаться, только если вы их где-то получили путем разыменования некоторого указателя.
@kriss: Нет, вы также можете получить висящую ссылку, вернув автоматическую переменную по ссылке.
@ Бен Войт: Хорошо, но это действительно вызывает проблемы. Некоторые компиляторы даже возвращают предупреждения, если вы это сделаете.
@kriss: В общем случае компилятор практически не может обнаружить. Рассмотрим функцию-член, которая возвращает ссылку на переменную-член класса: это безопасно и не должно быть запрещено компилятором. Затем вызывающий объект, у которого есть автоматический экземпляр этого класса, вызывает эту функцию-член и возвращает ссылку. Presto: висячая ссылка. И да, это вызовет проблемы, @kriss: это моя точка зрения. Многие люди утверждают, что преимущество ссылок перед указателями состоит в том, что ссылки всегда действительны, но это не так.
@Ben Voigt: вы уверены, что этот сценарий будет работать с неконстантными ссылками? Разве это не то, о чем говорит Мэтт Прайс в своем ответе? Но я согласен, возвращаемые ссылки могут быть висячими, даже неавтоматические объекты могут быть явно удалены.
@kriss: Нет, ссылка на объект с автоматической продолжительностью хранения сильно отличается от временного объекта. В любом случае, я просто приводил контрпример к вашему утверждению, что вы можете получить недопустимую ссылку только путем разыменования недопустимого указателя. Кристоф прав - ссылки ничуть не безопаснее, чем указатели, программа, использующая исключительно ссылки, все же может нарушить безопасность типов.
Ссылки - это не указатель. Это новое имя для существующего объекта.
@catphive: истина, если вы руководствуетесь семантикой языка, неверно, если вы действительно смотрите на реализацию; C++ - гораздо более «волшебный» язык, чем C, и если вы удалите магию из ссылок, вы получите указатель
Да, FAQ по C++ неверен. Ссылка - это собственная сущность, которая просто ссылается на объект или функцию. Даже по «языковой семантике». Но в большинстве ситуаций нельзя провести дистанцию между ссылкой и ее целью.
Другой пример признания ссылки недействительной - это ссылка на элемент контейнера std, например вектор. Когда вы вставляете в него элементы, он может перераспределить его хранилище и сделать ваши ссылки недействительными. Обычно это верно, когда что-то имеет ссылку на то, что ему не принадлежит. Его владелец может делать с ним неожиданные вещи без вашего ведома, и это даст вам надежную ссылку.
const int& ri = int(0);. Perfectly valid code, that compiles, and initializes a reference with an object that has a value of null. Apart from that, you cannot initialize a reference with a value anyway. A reference simply has no value. That should probably be reworded.
@IInspectable const int &ri = (int)0; - правильный код. ri инициализируется для ссылки на временный int, срок жизни которого увеличен. Это как раз тот случай, когда ссылка инициализируется значением, которое, как вы позже заявите, невозможно. Также неверно сказать, что ссылка не имеет значения. Его значение - это значение упомянутого объекта.
@Christoph «Все ссылки должны быть инициализированы ненулевым значением, иначе компиляция завершится неудачно». - ложный оператор. Если не указан инициализатор, компиляция завершится ошибкой; однако можно написать например const int& r = *(int *)nullptr;. Это неопределенное поведение, не требующее диагностики; фактически компилятор не может отклонить его, если не будет доказано, что все пути выполнения достигают этой строки.
@ M.M: Что именно более правильно в использовании вами актерского состава в стиле C? И нет, ссылка не имеет значения. Это псевдоним. Это не то, что существует в памяти или где-то еще. Как таковая она не имеет ценности. Его можно просто использовать будто, как он сделал, для записи в объект, к которому привязана ссылка.
@IInspectable *(int *)nullptr разыменовывает нулевой указатель, int(0) или (int)0 точно такие же, как 0, то есть целое число с нулевым значением.
Хороший ответ, НО: когда вы сказали о C++ "таким образом, нарушается принцип языкового дизайна, согласно которому оптимальное решение должно быть проще, чем альтернативы.", я действительно засмеялся! Это оооочень иронично. Способ C++ часто в 10 раз сложнее и синтаксически нелеп, чем способ C. Количество обручей, через которые люди будут прыгать в C++, чтобы получить больше «типовой безопасности» размером с песчинку, в то же время вываливая ведра и ведра синтаксиса, все еще удивляет меня. При этом мне очень нравится C++ компилятор, и я бы предпочел использовать его в любой день вместо компилятора C.
Не поймите меня неправильно: я верю в простоту и все время использую константные ссылки (но не неконстантные ссылки, поскольку я считаю, что они вызывают путаницу и являются плохой практикой). Я просто обнаружил, что почти весь код C++, который я читал, не выбирает простоту, независимо от того, что он утверждает. Он выбирает как можно больше сложности. Кусочки синтаксиса для небольшой безопасности воспринимается. Я бы сказал, что читаемый человеком код безопаснее, чем код, который заставляет вас прыгать через обручи и дважды сгибаться назад, прежде чем он станет правильным типом для передачи куда-либо. [конец тирады].
Кроме того, ссылка, которая является параметром встроенной функции, может обрабатываться иначе, чем указатель.
void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
int testptr=0;
increment(&testptr);
}
void increftest()
{
int testref=0;
increment(testref);
}
Многие компиляторы при встраивании первой версии указателя фактически вызывают запись в память (адрес мы берем явно). Однако они оставят ссылку в более оптимальном реестре.
Конечно, для функций, которые не встроены, указатель и ссылка генерируют один и тот же код, и всегда лучше передавать встроенные функции по значению, чем по ссылке, если они не изменяются и не возвращаются функцией.
Другое отличие состоит в том, что у вас могут быть указатели на тип void (а это означает указатель на что угодно), но ссылки на void запрещены.
int a;
void * p = &a; // ok
void & p = a; // forbidden
Не могу сказать, что я действительно доволен этой конкретной разницей. Я бы предпочел, чтобы это было разрешено со смысловой ссылкой на что-либо с адресом и в остальном такое же поведение для ссылок. Это позволило бы определить некоторые эквиваленты функций библиотеки C, таких как memcpy, с использованием ссылок.
Хотя и ссылки, и указатели используются для косвенного доступа к другому значению, между ссылками и указателями есть два важных различия. Во-первых, ссылка всегда ссылается на объект: определение ссылки без ее инициализации является ошибкой. Поведение присваивания - второе важное отличие: присвоение ссылке изменяет объект, к которому привязана ссылка; он не переключает ссылку на другой объект. После инициализации ссылка всегда ссылается на один и тот же базовый объект.
Рассмотрим эти два фрагмента программы. В первом мы назначаем один указатель другому:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2
После присвоения ival объект, к которому обращается пи, остается неизменным. Присваивание изменяет значение числа пи, заставляя его указывать на другой объект. Теперь рассмотрим аналогичную программу, которая назначает две ссылки:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
Это присвоение изменяет ival, значение, на которое ссылается ri, а не саму ссылку. После присвоения две ссылки по-прежнему ссылаются на свои исходные объекты, и значение этих объектов теперь тоже остается прежним.
"ссылка всегда ссылается на объект" просто неверно
Есть одно фундаментальное различие между указателями и ссылками, о котором я не видел, чтобы кто-то упоминал: ссылки включают семантику передачи по ссылке в аргументах функции. Указатели, хотя сначала они не видны, их нет: они обеспечивают только семантику передачи по значению. Это очень хорошо описано в Эта статья.
С уважением, & Ржей
Ссылки и указатели - это ручки. Оба они дают вам семантику, в которой ваш объект передается по ссылке, но ручка копируется. Нет разницы. (Есть и другие способы иметь дескрипторы, например, ключ для поиска в словаре)
Я тоже так думал. Но посмотрите связанную статью, в которой описано, почему это не так.
@Andrzj: Это просто очень длинная версия единственного предложения в моем комментарии: Ручка скопирована.
Мне нужно больше объяснений по этому поводу: «Ручка скопирована». Я понимаю некоторую основную идею, но я думаю, что физически ссылка и указатель указывают на местоположение переменной в памяти. Это похоже на то, что псевдоним хранит переменную значения и обновляет ее при изменении значения переменной или что-то еще? Я новичок, и, пожалуйста, не помечайте это как глупый вопрос.
@Andrzej Ложь. В обоих случаях происходит передача по значению. Ссылка передается по значению, а указатель передается по значению. Утверждение об обратном сбивает с толку новичков.
Очевидно, что передача по ссылке - действительно хорошая идея. Я предпочитаю видеть из самого вызова функции, что эта функция может изменять, а что нет.
Ссылка - это псевдоним для другой переменной, тогда как указатель содержит адрес памяти переменной. Ссылки обычно используются как параметры функции, поэтому переданный объект является не копией, а самим объектом.
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;
Это не то, что говорится в стандарте C++, и от компилятора не требуется реализовывать ссылки так, как описано в вашем ответе.
@jogojapan: Любой способ, допустимый для компилятора C++ для реализации ссылки, также является допустимым для него способом реализации указателя const. Эта гибкость не доказывает, что существует разница между ссылкой и указателем.
@BenVoigt Может быть и правда, что любая допустимая реализация одного также является допустимой реализацией другого, но это не следует очевидным образом из определений этих двух концепций. Хороший ответ начался бы с определений и продемонстрировал бы, почему утверждение о том, что эти два понятия в конечном итоге одинаковы, верно. Этот ответ кажется своего рода комментарием к некоторым другим ответам.
Ссылка является другое имя, данное объекту. Компилятору разрешено иметь любую реализацию, если вы не заметите разницы, это известно как правило «как если бы». Здесь важно то, что вы не заметите разницы. Если вы обнаружите, что указатель не имеет памяти, компилятор ошибается. Если вы обнаружите, что ссылка не имеет хранилища, компилятор все еще соответствует требованиям.
Эта программа может помочь понять ответ на вопрос. Это простая программа со ссылкой «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://thewikihow.com/video_rlJrrGV0iOg
Ссылки очень похожи на указатели, но они специально созданы, чтобы быть полезными для оптимизации компиляторов.
В качестве примера:
void maybeModify(int& x); // may modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// This function is designed to do something particularly troublesome
// for optimizers. It will constantly call maybeModify on array[0] while
// adding array[1] to array[2]..array[size-1]. There's no real reason to
// do this, other than to demonstrate the power of references.
for (int i = 2; i < (int)size; i++) {
maybeModify(array[0]);
array[i] += array[1];
}
}
Оптимизирующий компилятор может понять, что мы получаем доступ к целому ряду [0] и [1]. Было бы неплохо оптимизировать алгоритм, чтобы:
void hurtTheCompilersOptimizer(short size, int array[])
{
// Do the same thing as above, but instead of accessing array[1]
// all the time, access it once and store the result in a register,
// which is much faster to do arithmetic with.
register int a0 = a[0];
register int a1 = a[1]; // access a[1] once
for (int i = 2; i < (int)size; i++) {
maybeModify(a0); // Give maybeModify a reference to a register
array[i] += a1; // Use the saved register value over and over
}
a[0] = a0; // Store the modified a[0] back into the array
}
Для такой оптимизации нужно доказать, что во время вызова ничего не может изменить array [1]. Сделать это довольно просто. i никогда не меньше 2, поэтому array [i] никогда не может ссылаться на array [1]. MaybeModify () дается a0 в качестве ссылки (массив псевдонимов [0]). Поскольку не существует «ссылочной» арифметики, компилятор просто должен доказать, что возможноModify никогда не получает адрес x, и он доказал, что ничего не меняет array [1].
Он также должен доказать, что нет никаких способов, которыми будущий вызов мог бы читать / записывать [0], пока у нас есть его временная копия в регистре в a0. Часто это легко доказать, потому что во многих случаях очевидно, что ссылка никогда не сохраняется в постоянной структуре, такой как экземпляр класса.
Теперь проделаем то же самое с указателями.
void maybeModify(int* x); // May modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// Same operation, only now with pointers, making the
// optimization trickier.
for (int i = 2; i < (int)size; i++) {
maybeModify(&(array[0]));
array[i] += array[1];
}
}
Поведение такое же; только теперь намного сложнее доказать, что возможноModify никогда не изменяет array [1], потому что мы уже дали ему указатель; кошка вылезла из мешка. Теперь ему нужно сделать гораздо более сложное доказательство: статический анализ MaybeModify, чтобы доказать, что он никогда не записывает в & x + 1. Он также должен доказать, что он никогда не сохраняет указатель, который может ссылаться на array [0], что просто как сложно.
Современные компиляторы становятся все лучше и лучше в статическом анализе, но всегда приятно помочь им и использовать ссылки.
Конечно, исключая такую умную оптимизацию, компиляторы действительно превращают ссылки в указатели, когда это необходимо.
Обновлено: через пять лет после публикации этого ответа я обнаружил фактическую техническую разницу, когда ссылки отличаются, чем просто другой способ взглянуть на одну и ту же концепцию адресации. Ссылки могут изменять срок службы временных объектов так, как указатели не могут.
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
Обычно временные объекты, такие как объект, созданный вызовом createF(5), уничтожаются в конце выражения. Однако, привязав этот объект к ссылке, ref, C++ продлит срок жизни этого временного объекта до тех пор, пока ref не выйдет за пределы области видимости.
Правда, тело должно быть видно. Однако определить, что maybeModify не принимает адреса чего-либо, относящегося к x, существенно проще, чем доказать, что не выполняется связка арифметических действий с указателями.
Я считаю, что оптимизатор уже выполняет проверку «не выполняется множество арифметических действий с указателями» по множеству других причин.
«Ссылки очень похожи на указатели» - семантически, в соответствующих контекстах - но с точки зрения сгенерированного кода, только в некоторых реализациях, а не через какое-либо определение / требование. Я знаю, что вы указали на это, и я не возражаю ни с одним из ваших постов в практическом плане, но у нас уже слишком много проблем с людьми, которые слишком много читают в сокращенных описаниях, таких как «ссылки похожи / обычно реализуются как указатели» .
У меня такое ощущение, что кто-то ошибочно пометил как устаревший комментарий в соответствии с void maybeModify(int& x) { 1[&x]++; }, который обсуждается в других комментариях выше.
Это основано на руководство. То, что написано, проясняет:
>>> 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.
И если класс имеет ссылочную переменную, он должен быть инициализирован либо с помощью nullptr, либо с помощью допустимого объекта в списке инициализации.
Формулировка в этом ответе слишком запутанна, чтобы иметь реальную пользу. Кроме того, @Misgevolution, вы серьезно рекомендуете читателям инициализировать ссылку с помощью nullptr? Вы на самом деле читали какую-либо другую часть этой беседы или ...?
Мое плохое, извините за ту глупость, которую я сказал. К тому времени я, должно быть, был лишен сна. 'инициализировать с помощью nullptr' совершенно неверно.
Рискуя ввести в заблуждение, я хочу добавить некоторый ввод, я уверен, что это в основном зависит от того, как компилятор реализует ссылки, но в случае gcc идея о том, что ссылка может указывать только на переменную в стеке на самом деле не правильно, возьмите это, например:
#include <iostream>
int main(int argc, char** argv) {
// Create a string on the heap
std::string *str_ptr = new std::string("THIS IS A STRING");
// Dereference the string on the heap, and assign it to the reference
std::string &str_ref = *str_ptr;
// Not even a compiler warning! At least with gcc
// Now lets try to print it's value!
std::cout << str_ref << std::endl;
// It works! Now lets print and compare actual memory addresses
std::cout << str_ptr << " : " << &str_ref << std::endl;
// Exactly the same, now remember to free the memory on the heap
delete str_ptr;
}
Что выводит это:
THIS IS A STRING
0xbb2070 : 0xbb2070
Если вы заметили, что даже адреса памяти точно такие же, это означает, что ссылка успешно указывает на переменную в куче! Теперь, если вы действительно хотите развлечься, это тоже сработает:
int main(int argc, char** argv) {
// In the actual new declaration let immediately de-reference and assign it to the reference
std::string &str_ref = *(new std::string("THIS IS A STRING"));
// Once again, it works! (at least in gcc)
std::cout << str_ref;
// Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
delete &str_ref;
/*And, it works, because we are taking the memory address that the reference is
storing, and deleting it, which is all a pointer is doing, just we have to specify
the address with '&' whereas a pointer does that implicitly, this is sort of like
calling delete &(*str_ptr); (which also compiles and runs fine).*/
}
Что выводит это:
THIS IS A STRING
Следовательно, ссылка ЯВЛЯЕТСЯ указателем под капотом, они оба просто хранят адрес памяти, где адрес, на который указывает, не имеет значения, как вы думаете, что произойдет, если я вызову std :: cout << str_ref; ПОСЛЕ вызова delete & str_ref? Что ж, очевидно, что он компилируется нормально, но вызывает ошибку сегментации во время выполнения, потому что он больше не указывает на допустимую переменную, у нас, по сути, есть неработающая ссылка, которая все еще существует (пока она не выпадает из области видимости), но бесполезна.
Другими словами, ссылка - это не что иное, как указатель, в котором механизм указателя абстрагирован, что делает его более безопасным и простым в использовании (без случайной математики указателя, без смешения '.' И '->' и т. д.), Если вы не пробуйте глупостей вроде моих примеров выше;)
Теперь несмотря на о том, как компилятор обрабатывает ссылки, всегда будет иметь какой-то указатель под капотом, потому что ссылка должен ссылается на конкретную переменную по определенному адресу памяти, чтобы она работала должным образом, этого не избежать (следовательно, термин "ссылка").
Единственное важное правило, которое важно помнить для ссылок, заключается в том, что они должны быть определены во время объявления (за исключением ссылки в заголовке, в этом случае она должна быть определена в конструкторе после того, как объект, в котором он содержится, будет построено слишком поздно, чтобы определять его).
Помните, мои приведенные выше примеры - это просто примеры, демонстрирующие, что такое ссылка, вы никогда не захотите использовать ссылку таким образом! Для правильного использования ссылки здесь уже есть множество ответов, которые попадают в точку
Существует семантическая разница, которая может показаться эзотерической, если вы не знакомы с абстрактным или даже академическим изучением компьютерных языков.
На самом высоком уровне идея ссылок заключается в том, что они являются прозрачными «псевдонимами». Ваш компьютер может использовать адрес, чтобы заставить их работать, но вы не должны беспокоиться об этом: вы должны думать о них как о «просто другом имени» для существующего объекта, и синтаксис отражает это. Они строже, чем указатели, поэтому ваш компилятор может более надежно предупредить вас, когда вы собираетесь создать висящую ссылку, чем когда вы собираетесь создать висячий указатель.
Помимо этого, конечно, есть некоторые практические различия между указателями и ссылками. Синтаксис для их использования, очевидно, отличается, и вы не можете «повторно разместить» ссылки, иметь ссылки на небытие или иметь указатели на ссылки.
Может быть, помогут какие-нибудь метафоры; В контексте экранного пространства вашего рабочего стола -
Ссылка на указатель возможна в C++, но обратное невозможно, значит, указатель на ссылку невозможен. Ссылка на указатель обеспечивает более чистый синтаксис для изменения указателя. Взгляните на этот пример:
#include<iostream>
using namespace std;
void swap(char * &str1, char * &str2)
{
char *temp = str1;
str1 = str2;
str2 = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap(str1, str2);
cout<<"str1 is "<<str1<<endl;
cout<<"str2 is "<<str2<<endl;
return 0;
}
И рассмотрим C-версию вышеуказанной программы. В C вы должны использовать указатель на указатель (множественное косвенное обращение), что приводит к путанице, и программа может выглядеть сложной.
#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
char *temp = *str1_ptr;
*str1_ptr = *str2_ptr;
*str2_ptr = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap1(&str1, &str2);
printf("str1 is %s, str2 is %s", str1, str2);
return 0;
}
Посетите следующую страницу для получения дополнительной информации о ссылке на указатель:
Как я уже сказал, указатель на ссылку невозможен. Попробуйте следующую программу:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
Разница в том, что неконстантная переменная-указатель (не путать с указателем на константу) может быть изменена в какой-то момент во время выполнения программы, требует использования семантики указателя (&, *) операторов, в то время как ссылки могут быть установлены при инициализации. только (поэтому вы можете установить их только в списке инициализаторов конструктора, но не как-то еще) и использовать обычную семантику доступа к значениям. В основном ссылки были введены для поддержки перегрузки операторов, как я читал в одной очень старой книге. Как кто-то заявил в этом потоке, указатель может быть установлен на 0 или любое другое значение, которое вы хотите. 0 (NULL, nullptr) означает, что указатель не инициализирован ничем. Разыменовать нулевой указатель является ошибкой. Но на самом деле указатель может содержать значение, которое не указывает на какое-то правильное место в памяти. Ссылки, в свою очередь, стараются не позволить пользователю инициализировать ссылку на то, на что нельзя ссылаться, потому что вы всегда предоставляете ей rvalue правильного типа. Хотя существует множество способов инициализировать ссылочную переменную в неправильном месте памяти, лучше не вдаваться в подробности. На машинном уровне и указатель, и ссылка работают единообразно - через указатели. Скажем, существенные ссылки содержат синтаксический сахар. Ссылки rvalue отличаются от этого - они, естественно, являются объектами стека / кучи.
Указатель может быть инициализирован 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 " = "
У нас все еще может быть const int& i = 0.
В этом случае ссылка будет использоваться только при чтении, мы не можем изменить эту ссылку на константу даже с помощью «const_cast», потому что «const_cast» принимает только указатель, а не ссылку.
const_cast неплохо работает со ссылками: coliru.stacked-crooked.com/a/eebb454ab2cfd570
вы делаете приведение для ссылки, а не для ссылки, попробуйте это; const int & я =; const_cast <int> (я); Я пытаюсь отбросить постоянство ссылки, чтобы сделать возможной запись и присвоение нового значения ссылке, но это невозможно. пожалуйста, сосредоточьтесь !!
Я чувствую, что есть еще один момент, который здесь не затронут.
В отличие от указателей, ссылки - это синтаксически эквивалентный к объекту, на который они ссылаются, т.е. любая операция, которая может быть применена к объекту, работает для ссылки и с точно таким же синтаксисом (исключение, конечно, инициализация).
Хотя это может показаться поверхностным, я считаю, что это свойство имеет решающее значение для ряда функций 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++ и стандартной библиотеки, так что это довольно важное свойство ссылок.
«неявное приведение» приведение - это синтаксическая конструкция, она существует в грамматике; приведение всегда явное
Я всегда выбираю правило это из C++ Core Guidelines:
Prefer T* over T& when "no argument" is a valid option
Использование перегруженных функций, которые не принимают указатели, вместо разрешения nullptr или использование терминальных объектов, возможно, является гораздо лучшим решением вместо разрешения nullptr в качестве аргументов.
@Clearer Это, вероятно, чище, но иногда вам просто нужно быстро передать указатели, и могут быть случаи, когда вам все равно, является указатель нулевым или нет.
Между указателями и ссылками существует очень важное нетехническое различие: аргумент, переданный функции с помощью указателя, гораздо более заметен, чем аргумент, переданный функции по неконстантной ссылке. Например:
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() выше, не должны быть разрешены. Рекомендации Google по стилю C++ являются примером этого.
У меня есть аналогия со ссылками и указателями: ссылки можно рассматривать как другое имя объекта, а указатели - как адрес объекта.
// 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
}
Тарин ♦ сказала:
You can't take the address of a reference like you can with pointers.
На самом деле можно.
Цитирую из ответ на другой вопрос:
The C++ FAQ says it best:
Unlike a pointer, once a reference is bound to an object, it can not be "reseated" to another object. The reference itself isn't an object (it has no identity; taking the address of a reference gives you the address of the referent; remember: the reference is its referent).
Цитата, которую вы даете, противоречит вашей точке зрения. Здесь совершенно ясно сказано, что у самой ссылки нет адреса.
Имея дело с выражением, называющим ссылку, вы имеете дело с референтом.
Вы можете использовать разницу между ссылками и указателями, если вы следуете соглашению об аргументах, передаваемых функции. Ссылки на константы предназначены для данных, передаваемых в функцию, а указатели - для данных, передаваемых из функции. На других языках это можно явно обозначить такими ключевыми словами, как in и out. В C++ вы можете объявить (по соглашению) эквивалент. Например,
void DoSomething(const Foo& thisIsAnInput, Foo* thisIsAnOutput)
{
if (thisIsAnOuput)
*thisIsAnOutput = thisIsAnInput;
}
Использование ссылок в качестве входов и указателей в качестве выходов является частью Руководство по стилю Google.
В указателях и ссылках нет ничего изначально похожего на ввод или вывод. Важное отличие - наличие или отсутствие const.
Я согласен, здесь нет ничего похожего на ввод или вывод. То, что я говорил, было просто условностью (которую я не изобретал). Соглашение является частью Руководство по стилю Google. У указателей в качестве вывода есть одно преимущество: они могут иметь значение NULL, если вам не нужен вывод.
Чем это отличается от моего ответа 8 месяцев назад? Он даже ссылается на то же руководство по стилю!
Помимо всех ответов здесь,
вы можете реализовать перегрузку оператора, используя ссылки:
my_point operator+(const my_point& a, const my_point& b)
{
return { a.x + b.x, a.y + b.y };
}
Использование параметров в качестве значения создаст временные копии исходных аргументов, а использование указателей не вызовет эту функцию из-за арифметики указателей.
Дело не в том, что он не будет вызван: такой оператор с двумя параметрами-указателями не может быть даже объявлять; согласно черновик стандарт "Операторная функция должна быть либо нестатической функцией-членом, либо не являющейся функцией-членом, имеющей хотя бы один параметр, тип которого является классом, ссылкой на класс, перечислением или ссылкой на перечисление." ([over.oper] / 6)
Несмотря на то, что указатели и ссылки реализуются во многом одинаково «изнутри», компилятор обрабатывает их по-разному, что приводит ко всем различиям, описанным выше.
Недавняя статья, которую я написал, содержит гораздо больше деталей, чем я могу показать здесь, и должна быть очень полезной для этого вопроса, особенно о том, как что-то происходит в памяти:
Подробная статья о массивах, указателях и ссылках
Предлагаю добавить к самому ответу основные моменты из статьи. Ответы только по ссылкам обычно не приветствуются, см. stackoverflow.com/help/deleted-answers
@HolyBlackCat Мне было интересно об этом. Статья длинная и всесторонняя, она развивается от первых принципов до углубленного изучения с большим количеством примеров кода и дампов памяти, а затем заканчивается упражнениями, которые развивают подробные примеры кода и объяснения. Также в нем много диаграмм. Я попытаюсь выяснить, как прямо здесь изложить некоторые ключевые моменты, но прямо сейчас не знаю, как это сделать наилучшим образом. Большое спасибо за ваш вклад. Я сделаю все возможное, прежде чем мой ответ будет удален.
«Я знаю, что ссылки - это синтаксический сахар, поэтому код легче читать и писать»
Этот. Ссылка - это не другой способ реализации указателя, хотя она охватывает огромный случай использования указателя. Указатель - это тип данных - адрес, который обычно указывает на фактическое значение. Однако он может быть установлен в ноль или через пару знаков после адреса с помощью адресной арифметики и т. д. Ссылка - это «синтаксический сахар» для переменной, имеющей собственное значение.
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, и поэтому оба являются целыми числами, арифметика «ссылка» означает, например, добавление 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.
Основное значение указателя (*) - «Значение по адресу», что означает, что какой бы адрес вы ни указали, он будет давать значение по этому адресу. Как только вы измените адрес, он даст новое значение, тогда как ссылочная переменная используется для ссылки на любую конкретную переменную и не может быть изменена для ссылки на любую другую переменную в будущем.
Резюме из ответов и ссылок ниже:
NULL), тогда как ссылка всегда относится к объекту.&obj + 5).Чтобы прояснить заблуждение:
The C++ standard is very careful to avoid dictating how a compiler may implement references, but every C++ compiler implements references as pointers. That is, a declaration such as:
int &ri = i;if it's not optimized away entirely, allocates the same amount of storage as a pointer, and places the address of
iinto that storage.
Таким образом, указатель и ссылка используют один и тот же объем памяти.
Как общее правило,
Интересное чтение:
простыми словами, мы можем сказать, что ссылка - это альтернативное имя переменной, тогда как указатель - это переменная, которая содержит адрес другой переменной. например
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 */
Ссылка - это константный указатель. int * const a = &b такой же, как int& a = b. Вот почему не существует такой вещи, как ссылка на константу, потому что она уже является константой, тогда как ссылка на константу - это const int * const a. Когда вы компилируете с использованием -O0, компилятор помещает адрес b в стек в обеих ситуациях, и как член класса он также будет присутствовать в объекте в стеке / куче, как если бы вы объявили константный указатель. С -Ofast это можно оптимизировать бесплатно.
В отличие от константного указателя, нет способа получить адрес самой ссылки, так как он будет интерпретирован как адрес переменной, на которую он ссылается. Из-за этого ссылка всегда будет оптимизирована, но если программе абсолютно необходим адрес константного указателя на -Ofast, то есть вы печатаете адрес, тогда константный указатель будет помещен в стек.
#include <iostream>
int main() {
int a =1;
int* b = &a;
std::cout << b ;
}
int main() {
int a =1;
int& b = a;
std::cout << &b ;
}
they both have the same assembly output
-Ofast:
main:
sub rsp, 24
mov edi, OFFSET FLAT:_ZSt4cout
lea rsi, [rsp+12]
mov DWORD PTR [rsp+12], 1
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<void const*>(void const*)
xor eax, eax
add rsp, 24
ret
-O0:
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-12], 1
lea rax, [rbp-12]
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov rsi, rax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
mov eax, 0
leave
ret
Как члены объекта они идентичны от -O0 до -Ofast.
#include <iostream>
int b=1;
struct A {int* i=&b; int& j=b;};
A a;
int main() {
std::cout << &a.j << &a.i;
}
The address of b is stored twice in the object.
a:
.quad b
.quad b
mov rax, QWORD PTR a[rip+8] //&a.j
mov esi, OFFSET FLAT:a //&a.i
Локальная ссылка (то есть ссылка не в структуре или классе) не обязательно выделяет память. Вы можете это сказать по разнице в sizeof между sizeof (int &) и sizeof (struct {int & x;}).