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


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

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


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

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

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

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

int &ri = i;

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

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

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

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

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


2574


источник


Ответы:


  1. Указатель можно переназначить:

    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 &r = x;
    
  2. Указатель имеет собственный адрес и размер памяти в стеке (4 байта на x86), тогда как ссылка имеет один и тот же адрес памяти (с исходной переменной), но также занимает некоторое место в стеке. Поскольку ссылка имеет тот же адрес, что и исходная переменная, безопасно думать о ссылке как о другом имени для той же переменной. Примечание. То, на что указывает указатель, может быть в стеке или в куче. То же ссылка. Моя претензия в этом утверждении заключается не в том, что указатель должен указывать на стек. Указатель - это просто переменная, содержащая адрес памяти. Эта переменная находится в стеке. Поскольку ссылка имеет свое собственное пространство в стеке, а так как адрес совпадает с переменной, которую он ссылается. Еще стек против кучи , Это означает, что существует реальный адрес ссылки, который компилятор вам не скажет.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. У вас могут быть указатели указателей на указатели, предлагающие дополнительные уровни косвенности. В то время как ссылки предлагают только один уровень косвенности.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. Указатель может быть назначен nullptrнапрямую, тогда как ссылка не может. Если вы достаточно стараетесь, и знаете, как это сделать, вы можете сделать адрес ссылки nullptr, Аналогично, если вы достаточно стараетесь, вы можете иметь ссылку на указатель, а затем эта ссылка может содержать nullptr,

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
    
  5. Указатели могут перебирать массив, вы можете использовать ++перейти к следующему элементу, на который указывает указатель, и + 4перейти к 5-му элементу. Это независимо от того, какой размер указывает объект, на который указывает указатель.

  6. Указатель должен быть разыменован *для доступа к местоположению памяти, на которое он указывает, тогда как ссылку можно использовать напрямую. Указатель на класс / struct использует ->для доступа к своим членам, тогда как ссылка использует .,

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

  8. Ссылки не могут быть заполнены в массив, тогда как указатели могут быть (указано пользователем @litb)

  9. Константные ссылки могут быть привязаны к временным. Указатели не могут (не без какой-либо косвенности):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    Это делает const&более безопасным для использования в списках аргументов и т. д.


1344



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

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

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

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

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

Рассмотрим следующее утверждение из Вопросы по C ++ :

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

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

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

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

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


294



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

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он не должен компилироваться. Вы не можете привязывать неконстантную ссылку на временный объект, и вы не можете взять его адрес в этом отношении.


145



Contrary to popular opinion, it is possible to have a reference that is NULL.

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

Granted, it is much harder to do with a reference - but if you manage it, you'll tear your hair out trying to find it. References are not inherently safe in C++!

Technically this is an invalid reference, not a null reference. C++ doesn't support null references as a concept as you might find in other languages. There are other kinds of invalid references as well. Any invalid reference raises the spectre of undefined behavior, just as using an invalid pointer would.

The actual error is in the dereferencing of the NULL pointer, prior to the assignment to a reference. But I'm not aware of any compilers that will generate any errors on that condition - the error propagates to a point further along in the code. That's what makes this problem so insidious. Most of the time, if you dereference a NULL pointer, you crash right at that spot and it doesn't take much debugging to figure it out.

My example above is short and contrived. Here's a more real-world example.

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);

I want to reiterate that the only way to get a null reference is through malformed code, and once you have it you're getting undefined behavior. It never makes sense to check for a null reference; for example you can try if(&bar==NULL)... but the compiler might optimize the statement out of existence! A valid reference can never be NULL so from the compiler's view the comparison is always false, and it is free to eliminate the if clause as dead code - this is the essence of undefined behavior.

The proper way to stay out of trouble is to avoid dereferencing a NULL pointer to create a reference. Here's an automated way to accomplish this.

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));

For an older look at this problem from someone with better writing skills, see Null References from Jim Hyslop and Herb Sutter.

For another example of the dangers of dereferencing a null pointer see Exposing undefined behavior when trying to port code to another platform by Raymond Chen.


100



You forgot the most important part:

member-access with pointers uses ->
member-access with references uses .

foo.bar is clearly superior to foo->bar in the same way that vi is clearly superior to Emacs :-)


93



Apart from syntactic sugar, a reference is a const pointer (not pointer to a const). You must establish what it refers to when you declare the reference variable, and you cannot change it later.

Update: now that I think about it some more, there is an important difference.

A const pointer's target can be replaced by taking its address and using a const cast.

A reference's target cannot be replaced in any way short of UB.

This should permit the compiler to do more optimization on a reference.


91



Actually, a reference is not really like a pointer.

A compiler keeps "references" to variables, associating a name with a memory address; that's its job to translate any variable name to a memory address when compiling.

When you create a reference, you only tell the compiler that you assign another name to the pointer variable; that's why references cannot "point to null", because a variable cannot be, and not be.

Pointers are variables; they contain the address of some other variable, or can be null. The important thing is that a pointer has a value, while a reference only has a variable that it is referencing.

Now some explanation of real code:

int a = 0;
int& b = a;

Here you are not creating another variable that points to a; you are just adding another name to the memory content holding the value of a. This memory now has two names, a and b, and it can be addressed using either name.

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

int a;
increment(a);

When calling a function, the compiler usually generates memory spaces for the arguments to be copied to. The function signature defines the spaces that should be created and gives the name that should be used for these spaces. Declaring a parameter as a reference just tells the compiler to use the input variable memory space instead of allocating a new memory space during the method call. It may seem strange to say that your function will be directly manipulating a variable declared in the calling scope, but remember that when executing compiled code, there is no more scope; there is just plain flat memory, and your function code could manipulate any variables.

Now there may be some cases where your compiler may not be able to know the reference when compiling, like when using an extern variable. So a reference may or may not be implemented as a pointer in the underlying code. But in the examples I gave you, it will most likely not be implemented with a pointer.


55



References are very similar to pointers, but they are specifically crafted to be helpful to optimizing compilers.

  • References are designed such that it is substantially easier for the compiler to trace which reference aliases which variables. Two major features are very important: no "reference arithmetic" and no reassigning of references. These allow the compiler to figure out which references alias which variables at compile time.
  • References are allowed to refer to variables which do not have memory addresses, such as those the compiler chooses to put into registers. If you take the address of a local variable, it is very hard for the compiler to put it in a register.

As an example:

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];
    }
}

An optimizing compiler may realize that we are accessing a[0] and a[1] quite a bunch. It would love to optimize the algorithm to:

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
}

To make such an optimization, it needs to prove that nothing can change array[1] during the call. This is rather easy to do. i is never less than 2, so array[i] can never refer to array[1]. maybeModify() is given a0 as a reference (aliasing array[0]). Because there is no "reference" arithmetic, the compiler just has to prove that maybeModify never gets the address of x, and it has proven that nothing changes array[1].

It also has to prove that there are no ways a future call could read/write a[0] while we have a temporary register copy of it in a0. This is often trivial to prove, because in many cases it is obvious that the reference is never stored in a permanent structure like a class instance.

Now do the same thing with pointers

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];
    }
}

The behavior is the same; only now it is much harder to prove that maybeModify does not ever modify array[1], because we already gave it a pointer; the cat is out of the bag. Now it has to do the much more difficult proof: a static analysis of maybeModify to prove it never writes to &x + 1. It also has to prove that it never saves off a pointer that can refer to array[0], which is just as tricky.

Modern compilers are getting better and better at static analysis, but it is always nice to help them out and use references.

Of course, barring such clever optimizations, compilers will indeed turn references into pointers when needed.


49



A reference can never be NULL.


31



While both references and pointers are used to indirectly access another value, there are two important differences between references and pointers. The first is that a reference always refers to an object: It is an error to define a reference without initializing it. The behavior of assignment is the second important difference: Assigning to a reference changes the object to which the reference is bound; it does not rebind the reference to another object. Once initialized, a reference always refers to the same underlying object.

Consider these two program fragments. In the first, we assign one pointer to another:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

After the assignment, ival, the object addressed by pi remains unchanged. The assignment changes the value of pi, making it point to a different object. Now consider a similar program that assigns two references:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

This assignment changes ival, the value referenced by ri, and not the reference itself. After the assignment, the two references still refer to their original objects, and the value of those objects is now the same as well.


30



There is a semantic difference that may appear esoteric if you are not familiar with studying computer languages in an abstract or even academic fashion.

At the highest-level, the idea of references is that they are transparent "aliases". Your computer may use an address to make them work, but you're not supposed to worry about that: you're supposed to think of them as "just another name" for an existing object and the syntax reflects that. They are stricter than pointers so your compiler can more reliably warn you when you about to create a dangling reference, than when you are about to create a dangling pointer.

Beyond that, there are of course some practical differences between pointers and references. The syntax to use them is obviously different, and you cannot "re-seat" references, have references to nothingness, or have pointers to references.


24



A reference is an alias for another variable whereas a pointer holds the memory address of a variable. References are generally used as function parameters so that the passed object is not the copy but the object itself.

    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. 

20