Каковы различия между переменной-указателем и ссылочной переменной в C ++? - PullRequest
2956 голосов
/ 12 сентября 2008

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

Но в чем различия?


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

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

Для уточнения заблуждения:

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

int &ri = i;

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

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

Как правило,

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

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

Ответы [ 35 ]

28 голосов
/ 29 октября 2014

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

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

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

22 голосов
/ 01 января 2013

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

    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. 
18 голосов
/ 13 января 2014

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

>>> 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.

18 голосов
/ 26 февраля 2013

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

int& j = i;

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

int* const j = &i;
18 голосов
/ 13 сентября 2008

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

С другой стороны, одно существенное различие между ссылками и указателями заключается в том, что временные ссылки, присвоенные ссылкам const, остаются в силе до тех пор, пока ссылка на 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 работать.

15 голосов
/ 09 февраля 2015

Ссылка на указатель возможна в 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;
}
15 голосов
/ 12 сентября 2008

Я использую ссылки, если мне не нужен ни один из них:

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

  • Вы можете делать арифметику с указателем. Например, p += offset;

14 голосов
/ 06 февраля 2012

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

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

13 голосов
/ 15 октября 2014

Риск добавления к путанице, я хочу добавить некоторые входные данные, я уверен, что это в основном зависит от того, как компилятор реализует ссылки, но в случае 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? Ну, очевидно, что он хорошо компилируется, но вызывает ошибку сегментации во время выполнения, потому что он больше не указывает на допустимую переменную, у нас, по сути, есть поврежденная ссылка, которая все еще существует (пока она не выпадает из области видимости), но бесполезна. </p>

Другими словами, ссылка - это не что иное, как указатель, у которого абстрагирована механика указателя, что делает его более безопасным и простым в использовании (без случайной математики указателя, без смешения '.' И '->' и т. Д.) при условии, что вы не пробуете чепуху, как мои примеры выше;)

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

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

Помните, мои примеры выше - это просто примеры, демонстрирующие, что такое ссылка, вы никогда не захотите использовать ссылку таким образом! Для правильного использования справочника здесь уже есть множество ответов, которые бьют по голове

12 голосов
/ 29 января 2010

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

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

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

...