reference用法 - c++參照




C++中指針變量和引用變量之間有什麼區別? (20)

我知道引用是語法糖,因此代碼更容易讀寫。

但有什麼區別?

以下答案和鏈接摘要:

  1. 指針可以重新分配任意次數,而綁定後無法重新分配引用。
  2. 指針可以指向任何地方( NULL ),而引用總是指向一個對象。
  3. 您無法使用指針獲取引用的地址。
  4. 沒有“參考算術”(但是您可以獲取引用所指向的對象的​​地址,並在其上執行指針算法,如&obj + 5 )。

澄清一個誤解:

C ++標準非常謹慎,以避免規定編譯器如何實現引用,但每個C ++編譯器都將引用實現為指針。 也就是說,聲明如下:

int &ri = i;

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

因此,指針和引用都使用相同數量的內存。

作為基本規則,

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

有趣的讀物:


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

引用可以被認為是一個常量指針 (不要與指向常量值的指針混淆!)和自動間接,即編譯器將為您應用*運算符。

必須使用非null值初始化所有引用,否則編譯將失敗。 獲取引用的地址既不可能 - 地址運算符將返回引用值的地址 - 也不可能在引用上進行算術運算。

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

C ++程序員可能不喜歡使用指針,因為它們被認為是不安全的 - 雖然引用並不比常量指針更安全,除了在最微不足道的情況下 - 缺乏自動間接的便利性並帶有不同的語義內涵。

請考慮C ++ FAQ中的以下語句:

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

但如果參考確實是對象,那麼怎麼會有懸空引用呢? 在非託管語言中,引用不可能比指針更“安全” - 通常只是不能跨範圍邊界可靠地對值進行別名!

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

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

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


指針和引用之間的區別

指針可以初始化為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模板的模板時,這種類模板將始終返回引用而不是指針,以便使用operator []輕鬆讀取或分配新值:

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 "="

你忘記了最重要的部分:

使用指針的成員訪問->
使用引用的成員訪問.

foo.bar 明顯優於foo->bar ,就像vi 明顯優於Emacs :-)


冒著混淆的風險,我想拋出一些輸入,我確定它主要取決於編譯器如何實現引用,但是在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 ++中,如果將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情況下嘗試這個,它將無法編譯。 您不能將非const引用綁定到臨時對象,也不能為此處獲取其地址。


如果您不熟悉以抽像或甚至學術方式學習計算機語言,那麼語義上的差異可能會顯得深奧。

在最高級別,引用的想法是它們是透明的“別名”。您的計算機可能會使用一個地址來使它們工作,但您不應該擔心:您應該將它們視為現有對象的“另一個名稱”,並且語法反映了這一點。它們比指針更嚴格,因此當您要創建懸空引用時,編譯器可以更可靠地警告您,而不是在您創建懸空指針時。

除此之外,指針和引用之間當然存在一些實際差異。使用它們的語法明顯不同,你不能“重新定位”引用,引用虛無,或指向引用。


實際上,引用並不像指針。

編譯器保持對變量的“引用”,將名稱與內存地址相關聯; 這是在編譯時將任何變量名轉換為內存地址的工作。

創建引用時,只告訴編譯器為指針變量指定另一個名稱; 這就是為什麼引用不能“指向null”,因為變量不能,也不能。

指針是變量; 它們包含其他變量的地址,或者可以為null。 重要的是指針有一個值,而引用只有一個它正在引用的變量。

現在對實際代碼的一些解釋:

int a = 0;
int& b = a;

在這裡,您不是要創建另一個指向a變量; 你只是在保存a值的內存內容中添加另一個名稱。 此內存現在有兩個名稱, ab ,可以使用任一名稱進行尋址。

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

int a;
increment(a);

調用函數時,編譯器通常會為要復制的參數生成內存空間。 函數簽名定義了應該創建的空格,並給出了應該用於這些空間的名稱。 將參數聲明為引用只是告訴編譯器使用輸入變量內存空間而不是在方法調用期間分配新的內存空間。 說你的函數將直接操作調用範圍中聲明的變量似乎很奇怪,但請記住,在執行編譯代碼時,沒有更多的範圍; 只有普通的平坦內存,你的功能代碼可以操縱任何變量。

現在可能存在編譯器在編譯時可能無法知道引用的情況,例如使用extern變量時。 因此,引用可能會也可能不會被實現為底層代碼中的指針。 但是在我給你的例子中,它很可能不會用指針實現。


引用永遠不能為NULL


引用與指針非常相似,但它們經過精心設計,有助於優化編譯器。

  • 設計引用使得編譯器更容易跟踪哪個引用別名哪個變量。 兩個主要特徵非常重要:沒有“參考算術”,也沒有重新分配參考文獻。 這些允許編譯器在編譯時找出哪些引用別名是哪些變量。
  • 允許引用引用沒有內存地址的變量,例如編譯器選擇放入寄存器的變量。 如果你取一個局部變量的地址,編譯器很難把它放在一個寄存器中。

舉個例子:

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
}

為了進行這樣的優化,需要證明在調用期間沒有任何東西可以改變數組[1]。 這很容易做到。 我永遠不會少於2,所以array [i]永遠不會引用數組[1]。 maybeModify()被賦予a0作為參考(別名數組[0])。 因為沒有“引用”算法,編譯器只需要證明maybeModify永遠不會得到x的地址,並且它已經證明沒有任何改變數組[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];
    }
}

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

現代編譯器在靜態分析方面越來越好,但總是很好地幫助它們並使用引用。

當然,除非進行這種巧妙的優化,否則編譯器確實會在需要時將引用轉換為指針。

編輯:發布這個答案五年後,我發現了一個實際的技術差異,其中引用不同於查看相同尋址概念的不同方式。 引用可以以指針不能的方式修改臨時對象的生命週期。

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

通常,臨時對象(例如通過調用createF(5)創建的對象createF(5)在表達式的末尾被銷毀。 但是,通過將該對象綁定到引用, ref ,C ++將延長該臨時對象的生命週期,直到ref超出範圍。


我使用引用,除非我需要以下任何一個:

  • 空指針可以用作標記值,通常是避免函數重載或使用bool的廉價方法。

  • 你可以對指針進行算術運算。例如,p += offset;


指針和引用之間存在非常重要的非技術差異:通過指針傳遞給函數的參數比通過非const引用傳遞給函數的參數更加明顯。 例如:

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引用,即使函數從不期望a nullptr。也就是說,那些人認為fn3()不應該允許像上面這樣的功能簽名。Google的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);
}

在內聯指針版本1時,許多編譯器實際上會強制寫入內存(我們正在明確地獲取地址)。但是,他們會將參考文獻保留在更優化的寄存器中。

當然,對於沒有內聯的函數,指針和引用生成相同的代碼,如果它們未被函數修改並返回,則通過值傳遞內在函數而不是引用更好。


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

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

const指針的目標可以通過獲取其地址並使用const轉換來替換。

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

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


也許一些比喻會有所幫助; 在桌面屏幕空間的上下文中 -

  • 引用要求您指定實際窗口。
  • 指針需要屏幕上一塊空間的位置,您可以確保它將包含該窗口類型的零個或多個實例。

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

引用不是給予某些內存的另一個名稱。它是一個不可變的指針,在使用時會自動取消引用。基本上它歸結為:

int& j = i;

它在內部成為

int* const j = &i;

引用是另一個變量的別名,而指針保存變量的內存地址。引用通常用作函數參數,以便傳遞的對像不是副本而是對象本身。

    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. 

我總是決定this從C ++核心指導原則:

當“無參數”是有效選項時,首選T *而不是T.


該計劃可能有助於理解問題的答案。這是引用“j”和指向變量“x”的指針“ptr”的簡單程序。

#include<iostream>

using namespace std;

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

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

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

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

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

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

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

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

運行程序,看看輸出,你會明白。

此外,請花10分鐘觀看此視頻:https://www.youtube.com/watch?v=rlJrrGV0iOghttps://www.youtube.com/watch?v=rlJrrGV0iOg


這是基於本tutorial。寫的內容更清楚:

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

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

簡單地記住,

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

更重要的是,因為我們可以參考幾乎任何指針教程,指針是指針算法支持的對象,它使指針類似於數組。

看下面的陳述,

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

alias_Tom可以理解為一個alias of a variable(具有不同的typedef,這是alias of a typeTom。也可以忘記這樣的語句的術語是創建一個引用Tom





c++-faq