[C++] C ++中的指針變量和引用變量之間有什麼區別?


Answers

什麼是C ++參考( 適用於C程序員

一個引用可以被認為是一個常量指針 (不要與指向一個常量值的指針混淆),也就是說編譯器會為你應用*運算符。

所有引用必須用非空值初始化,否則編譯將失敗。 無法獲得引用的地址 - 地址運算符將返回引用值的地址 - 也不可能對引用進行運算。

C程序員可能不喜歡C ++引用,因為當間接發生時,或者如果通過值或指針傳遞參數而不查看函數簽名,它將不再明顯。

C ++程序員可能不喜歡使用指針,因為它們被認為是不安全的 - 儘管除了最瑣碎的情況之外,引用實際上並不比常量指針更安全 - 缺乏自動間接的便利性並帶有不同的語義內涵。

考慮C ++ FAQ中的以下陳述:

儘管通常使用底層彙編語言中的地址來實現引用,但請不要將引用看作指向對象的有趣的指針。 參考對象。 它不是指向對象的指針,也不是對象的副本。 這對象。

但是如果一個參考文獻真的是這個客體,怎麼會有懸而未決的參考? 在非託管語言中,引用不可能比指針更“安全” - 通常不會在範圍邊界上可靠地別名化值的方法!

為什麼我認為C ++引用很有用

從C背景來看,C ++引用可能看起來像一個有點愚蠢的概念,但仍應該在可能的情況下使用它們而不是指針:自動間接方便,並且引用在處理RAII時特別有用 - 但不是因為任何安全感優點,而是因為他們使寫慣用代碼不那麼笨拙。

RAII是C ++的核心概念之一,但它與復制語義的非平凡交互。 通過引用傳遞對象可避免這些問題,因為不涉及復制。 如果引用不存在於語言中,則必須使用指針,這會更麻煩,因此違反了語言設計原則,即最佳實踐解決方案應該比替代方案更容易。

Question

我知道引用是語法糖,所以代碼更易於讀寫。

但有什麼區別?

下面的答案和鏈接摘要:

  1. 指針可以重新分配任意次數,而綁定後不能重新引用引用。
  2. 指針可以指向無處( NULL ),而引用始終指向一個對象。
  3. 你不能像指針那樣使用引用的地址。
  4. 沒有“引用算術”(但是你可以把一個引用指向的對象的​​地址作為&obj + 5指針算術)。

澄清一種誤解:

C ++標準非常小心地避免規定編譯器必須如何實現引用,但每個C ++編譯器都將引用實現為指針。 也就是說,一個聲明如:

int &ri = i;

如果沒有完全優化請將相同數量的存儲分配給指針,並將i的地址放入該存儲中。

所以,一個指針和一個引用都佔用相同數量的內存。

作為基本規則,

  • 在函數參數和返回類型中使用引用來定義有用和自我記錄的接口。
  • 使用指針來實現算法和數據結構。

有趣的閱讀:




I feel like there is yet another point that hasn't been covered here.

Unlike the pointers, references are syntactically equivalent to the object they refer to, ie any operation that can be applied to an object works for a reference, and with the exact same syntax (the exception is of course the initialization).

While this may appear superficial, I believe this property is crucial for a number of C++ features, for example:

  • Templates . Since template parameters are duck-typed, syntactic properties of a type is all that matters, so often the same template can be used with both T and T& .
    (or std::reference_wrapper<T> which still relies on an implicit cast to T& )
    Templates that cover both T& and T&& are even more common.

  • Lvalues . Consider the statement str[0] = 'X'; Without references it would only work for c-strings ( char* str ). Returning the character by reference allows user-defined classes to have the same notation.

  • Copy constructors . Syntactically it makes sense to pass objects to copy constructors, and not pointers to objects. But there is just no way for a copy constructor to take an object by value - it would result in a recursive call to the same copy constructor. This leaves references as the only option here.

  • Operator overloads . With references it is possible to introduce indirection to an operator call - say, operator+(const T& a, const T& b) while retaining the same infix notation. This also works for regular overloaded functions.

These points empower a considerable part of C++ and the standard library so this is quite a major property of references.




Both references and pointers can be used to change local variables of one function inside another function. Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain. Despite above similarities, there are following differences between references and pointers.

References are less powerful than pointers

1)一旦創建了一個引用,就不能再引用另一個對象; 它不能被重新安裝。 這通常是用指針完成的。

2)引用不能為NULL。 指針通常被設置為NULL來表示它們沒有指向任何有效的東西。

3)聲明時必須初始化引用。 指針沒有這樣的限制

Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don't have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn't need pointers.

參考文獻更安全,更易於使用:

1)更安全:由於引用必須被初始化,像野指針這樣的野生引用不太可能存在。 仍然可以提供不涉及有效位置的引用

2)易於使用:引用不需要取消引用操作符來訪問該值。 它們可以像正常變量一樣使用。 只有在申報時才需要'&'運營商。 另外,對象引用的成員可以使用點運算符('。')來訪問,與需要使用箭頭運算符( - >)來訪問成員的指針不同。

Together with the above reasons, there are few places like copy constructor argument where pointer cannot be used. Reference must be used pass the argument in copy constructor. Similarly references must be used for overloading some operators like ++ .




Another difference is that you can have pointers to a void type (and it means pointer to anything) but references to void are forbidden.

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

I can't say I'm really happy with this particular difference. I would much prefer it would be allowed with the meaning reference to anything with an address and otherwise the same behavior for references. It would allow to define some equivalents of C library functions like memcpy using references.




I have an analogy for references and pointers, think of references as another name for an object and pointers as the address of an object.

// 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
}



除了語法糖之外,引用是一個const指針( 不是指向const指針)。 您必須在聲明參考變量時確定其引用的內容,並且以後不能再更改它。

更新:現在我再想一想,有一個重要的區別。

一個const指針的目標可以被取代它的地址並使用const轉換。

參考目標不能以UB以外的任何方式替換。

這應該允許編譯器對引用做更多的優化。




At the risk of adding to confusion, I want to throw in some input, I'm sure it mostly depends on how the compiler implements references, but in the case of gcc the idea that a reference can only point to a variable on the stack is not actually correct, take this for example:

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

Which outputs this:

THIS IS A STRING
0xbb2070 : 0xbb2070

If you notice even the memory addresses are exactly the same, meaning the reference is successfully pointing to a variable on the heap! Now if you really want to get freaky, this also works:

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).*/
}

Which outputs this:

THIS IS A STRING

Therefore a reference IS a pointer under the hood, they both are just storing a memory address, where the address is pointing to is irrelevant, what do you think would happen if I called std::cout << str_ref; AFTER calling delete &str_ref? Well, obviously it compiles fine, but causes a segmentation fault at runtime because it's no longer pointing at a valid variable, we essentially have a broken reference that still exists (until it falls out of scope), but is useless.

In other words, a reference is nothing but a pointer that has the pointer mechanics abstracted away, making it safer and easier to use (no accidental pointer math, no mixing up '.' and '->', etc.), assuming you don't try any nonsense like my examples above ;)

Now regardless of how a compiler handles references, it will always have some kind of pointer under the hood, because a reference must refer to a specific variable at a specific memory address for it to work as expected, there is no getting around this (hence the term 'reference').

The only major rule that's important to remember with references is that they must be defined at the time of declaration (with the exception of a reference in a header, in that case it must be defined in the constructor, after the object it's contained in is constructed it's too late to define it).

Remember, my examples above are just that, examples demonstrating what a reference is, you would never want to use a reference in those ways! For proper usage of a reference there are plenty of answers on here already that hit the nail on the head




A reference to a pointer is possible in C++, but the reverse is not possible means a pointer to a reference isn't possible. A reference to a pointer provides a cleaner syntax to modify the pointer. Look at this example:

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

And consider the C version of the above program. In C you have to use pointer to pointer (multiple indirection), and it leads to confusion and the program may look complicated.

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

Visit the following for more information about reference to pointer:

As I said, a pointer to a reference isn't possible. Try the following program:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}



引用與指針非常相似,但它們專門用於優化編譯器。

  • 引用的設計使編譯器能夠更容易地跟踪哪些引用別名使用哪個變量。 兩個主要特徵非常重要:沒有“參考算術”,也沒有重新分配參考。 這些允許編譯器確定哪些引用在編譯時別名了哪些變量。
  • 允許引用指代沒有內存地址的變量,例如編譯器選擇將其放入寄存器的變量。 如果你使用局部變量的地址,編譯器很難將它放入寄存器。

舉個例子:

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]。 這很容易做到。 我永遠不會少於2,所以array [i]永遠不能引用array [1]。 maybeModify()被賦予a0作為參考(別名數組[0])。 因為沒有“引用”算法,所以編譯器只需證明MaybeModify永遠不會獲得x的地址,並且已經證明沒有什麼會改變array [1]。

它還必須證明,當我們在a0中有一個臨時寄存器副本時,未來的調用將無法讀取/寫入[0]。 這經常被證明是微不足道的,因為在很多情況下,很明顯參考文件永遠不會像類實例那樣存儲在永久結構中。

現在用指針做同樣的事情

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

行為是一樣的; 只是現在證明可能修改數組[1]是很難的,因為我們已經給它一個指針了; 貓在袋子外面。 現在它必須做更加困難的證明:一個對MaybeModify的靜態分析,以證明它永遠不會寫入&x + 1。它還必須證明它永遠不會保存可以引用array [0]的指針,這只是如棘手。

現代編譯器在靜態分析中越來越好,但總是很好的幫助他們並使用引用。

當然,除了這種聰明的優化之外,編譯器確實會在需要時將引用轉換為指針。




與流行觀點相反,可能有一個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子句作為死代碼去除 - 這是未定義行為的本質。

避免麻煩的正確方法是避免取消引用空指針來創建引用。 這是一個自動完成這個任務的方法。

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

對於具有較好寫作技巧的人來說,對這個問題進行較老的觀察,請參閱Jim Hyslop和Herb Sutter的空引用

有關解除引用空指針的危險的另一個示例,請參閱Raymond Chen 在嘗試將代碼移植到另一個平台時暴露未定義的行為




Another interesting use of references is to supply a default argument of a user-defined type:

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

The default flavor uses the 'bind const reference to a temporary' aspect of references.




儘管引用和指針都用於間接訪問另一個值,但引用和指針之間有兩個重要區別。 首先是引用始終引用對象:定義引用而不初始化引用是錯誤的。 賦值的行為是第二個重要區別:賦值給引用會改變引用綁定到的對象; 它不重新綁定對另一個對象的引用。 一旦初始化,引用總是指向相同的基礎對象。

考慮這兩個程序片段。 首先,我們將一個指針分配給另一個:

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

分配後,ival,pi處理的對象保持不變。 該分配會更改pi的值,使其指向不同的對象。 現在考慮一個分配兩個引用的類似程序:

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.




The difference is that non-constant pointer variable(not to be confused with a pointer to constant) may be changed at some time during program execution, requires pointer semantics to be used(&,*) operators, while references can be set upon initialization only(that's why you can set them in constructor initializer list only, but not somehow else) and use ordinary value accessing semantics. Basically references were introduced to allow support for operators overloading as I had read in some very old book. As somebody stated in this thread - pointer can be set to 0 or whatever value you want. 0(NULL, nullptr) means that the pointer is initialized with nothing. It is an error to dereference null pointer. But actually the pointer may contain a value that doesn't point to some correct memory location. References in their turn try not to allow a user to initialize a reference to something that cannot be referenced due to the fact that you always provide rvalue of correct type to it. Although there are a lot of ways to make reference variable be initialized to a wrong memory location - it is better for you not to dig this deep into details. On machine level both pointer and reference work uniformly - via pointers. Let's say in essential references are syntactic sugar. rvalue references are different to this - they are naturally stack/heap objects.




It doesn't matter how much space it takes up since you can't actually see any side effect (without executing code) of whatever space it would take up.

On the other hand, one major difference between references and pointers is that temporaries assigned to const references live until the const reference goes out of scope.

例如:

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!

This is the language mechanism that allows ScopeGuard to work.




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.