整理日: 2015年3月18日程序員
引用(reference)和指針(pointer)是學C++過程當中最使人頭疼的問題,經常不知道何時用哪一個合適,又經常弄混。找到Dan Saks的這篇文章,講的很清楚,強烈推薦,因此翻譯一下供你們參考。安全
如下譯自Dan Saks的文章 References vs. Pointers函數
瞭解引用reference與指針pointer到底有什麼不一樣能夠幫助你決定何時該用reference,何時該用pointer。翻譯
在C++ 中,reference在不少方面與指針(pointer)具備一樣的能力。雖然多數C++程序員對於什麼時候使用reference什麼時候使用pointer 都會有一些直覺,但總仍是會有些時候搞不清楚。若是你想要創建一個關於使用reference使用的清晰有理的概念, 又有必要了解到底reference和pointer有什麼不一樣。指針
深層含義
與pointer 相似,一個reference是一個對象(object),能夠用來間接指向另外一個對象。一個reference的聲明與pointer的聲明的實質語法結構是相同的。不一樣的是,聲明pointer的時候使用星號操做符 * , 而聲明reference的時候使用地址操做符 & 。 例如,咱們有:code
int i = 3;
則有:對象
int *pi = &i;
聲明 pi 爲一個指針類型的對象,而且是一個」指向int整型的指針」,它的初始值爲對象i的地址。而另外一方面:接口
int &ri = i;
聲明 ri爲一個reference類型的對象,而且也是一個指向整型的reference,它指向的是i。 咱們能夠看到pointer和reference的聲明有顯著的不一樣,但這並非決定什麼時候使用哪個的根據。決定的真正依據是當它們被用在表達式中時其顯 示的不一樣決定了使用哪個合適io
Pointer 和reference的最大不一樣是:pointer必須使用一個星號操做符 * 來去掉reference (英文叫作dereference,我不知道這裏怎樣翻譯這個詞合適,姑且就叫「去參考」吧)而reference不須要任何操做符來去參考。 例如, 有了上面例子中的定義, 間接表達式 *pi 將 pi 去參考爲指向i。相反, 表達式ri-不須要任何操做符-自動將ri去參考爲指向i。所以, 使用指針p,咱們須要用賦值語句:編譯
*p = 4;
將i的值變爲4; 而使用reference ri,咱們只須要直接寫:
ri = 4;
就能夠一樣將i的值變爲4 。
這個顯示的不一樣在當你爲函數的參數類型和返回值類型選擇是使用pointer仍是reference的時候就會顯著起來,尤爲是對於重載操做符的函數。
下面使用一個針對列舉類型(enumeration)的++操做符例子來講明上面這點。在C++中, 內置的++操做符對列舉類型無效,例如, 對下面定義:
enum day{ Sunday, Monday, … }; day x;
表達式 ++x 不能編譯。若是想讓它經過編譯,必需要定義一個名爲operator++的函數,接受day爲參數,而且調用 ++x 必須改變x的值。所以, 僅聲明一個函數 operator++ , 以類型day爲參數, 以下:
day operator++(day d);
並不可以獲得想要得效果。 這個函數經過值傳遞參數(pass by value),這就意味着函數內看到的是參數的一個拷貝,而不是參數自己。爲了使函數可以改變其操做數(operand)的值,它必須經過指針或reference來傳遞其操做數。
經過指針傳遞參數(passing by pointer),函數定義以下:
day operator++(day d);
它經過將增長後的值存儲到*d裏面來使函數改變日期(day)的值。可是,這樣你就必須使用像表達式++&x這樣來調用這個操做符,這看起來不太對勁兒。
正確的方法是定義operator++以reference爲參數類型,以下:
day &operator++(day &d) { d = (day)(d + 1); return d; }
使用這個函數, 表達式 ++x 纔有正確的顯示以及正確的操做。
Passing by reference不只僅是寫operator++較好的方法,而是惟一的方法。 C++在這裏並無給咱們選擇的餘地。 像下面的聲明:
day operator++(day d);
是不能 經過編譯的。每一個重載的操做符函數必須或者是一個類的成員, 或者使用類型T、 T & 或 T const & 爲參數類型,這裏T是一個類(class)或列舉(enumeration)類型。 也就是說,每個重載操做符必須以類或列舉類型爲參數類型。指針,即便是指向一個類或列舉類型對象的指針,也不能夠用。C++ 不容許在重載操做符時從新定義內置操做符的含義,包括指針類型。所以,咱們不能夠定義:
int operator++(int i); // 錯誤
由於它試圖對int從新定義操做符 ++ 的含義。 咱們也不能夠定義:
int operator++(int i); // 錯誤
由於它試圖對 int * 從新定義操做符 ++ 的含義。
References vs. const pointers
C++ 中不容許定義」const reference」, 由於一個reference天生就是const。也就是說,一旦將一個reference綁定到一個對象,就沒法再將它從新綁定到另外一個不一樣的對象。在聲 明一個reference以後沒有寫法能夠將它從新綁定到另一個對象。例如:
int &ri = i;
將 ri 綁定到 i 。而後下面的賦值:
ri = j;
並非把 ri 綁定到 j ,而是將 j 中的值賦給 ri 指向的對象,也就是賦給 i 。
簡而言之,一個pointer在它的有生之年能夠指向許多不一樣的對象,而一個reference只可以指向一個對象。有些人認爲這纔是 reference和 pointer最大的不一樣。我並不同意。也許這是reference與pointer的一點不一樣, 但並非reference和const pointer的不一樣。在強調一遍,一旦一個reference與一個對象綁定,就不能再將它改指向另外的東西。既然不能再綁定reference以後再 改變, 一個reference就必須在一出生就被綁定。不然這個reference就永遠不能被綁定到任何東西,也就毫無用處了。
上一段的討論也一樣徹底適用於常量指針(const pointer)。(注意,我這裏說的是常量指針(const pointer), 而不是指向常量的指針 「pointers to const」。) 例如,一個reference聲明必須同時帶有一個初始化賦值,以下所示:
void f() { int &r = i; // … }
省略這個初始化賦值將產生一個編譯錯誤:
void f() { int &r; //錯誤 // … }
一個常量指針的聲明也一樣必須帶有一個初始化賦值,以下所示:
void f() { int *const p = &i; // … }
省略這個初始化賦值一樣會出錯:
void f(){ int *const p; // 錯誤 // … }
在我看來, 不可以對reference二次綁定做爲reference與pointer的不一樣。並不比常量指針和很是量指針的不一樣更爲顯著。
Null references
除了顯示的不一樣,常量指針與reference還有一點很是不一樣,那就是,一個有效的reference必須指向一個對象;而一個指針不須要。一個指針,即便是一個常量指針, 均可以有空值。 一個空指針不指向任何東西。
這點不一樣就暗示當你想要確信一個參數必須指向一個對象的時候,應該使用reference做爲參數類型。 例如,交換函數(swap function),它接受兩個int參數,並將兩個參數的數值對調,以下所示:
int i, j; swap(i, j);
將本來在 i 中的值放到 j 中, 並將本來在 j 中的值放到 i 中。咱們能夠這樣寫這個函數:
void swap(int *v1, int *v2) { int temp = *v1; *v1 = *v2; *v2 = temp; }
這種定義下,函數要像這樣被調用: swap(&i, &j);
這個接口暗示其中一個或兩個參數都有可能爲空(null)。而這個暗示是誤導的。例如,調用
swap(&i, NULL);
的後果極可能是不愉快的。
而像下面這樣定義reference爲參數:
void swap(int &v1, int &v2) { int temp = v1; v1 = v2; v2 = temp; }
清晰的代表了調用swap應該提供兩個對象,它們的值將被交換。 而且這樣定義的另外一個好處是,在調用這個函數的時候,不須要使用那些&符號,看起來更順眼:
swap(i, j);
更安全?
有些人認爲既然reference不可以爲空,那麼它應該比指針更安全。 我認爲reference可能要安全一點,但不會安全不少。雖然一個有效的reference不能爲空,可是無效的能夠呀。實際上,在不少狀況下程序有可 能產生無效的reference,而不僅是空的reference。 例如,你能夠定義一個reference,使它綁定到一個指針指向的對象,以下所示:
int *p; // … int &r = *p;
若是指針p在reference定義時恰好爲空,則這個reference爲空。 從技術上來講,這個錯誤並不在於將reference綁定到一個空值,而是在於對一個空指針去參考。 對一個空指針去參考產生了一個不肯定的操做,也就意味着不少事均可能發生,並且大部分都不是什麼好事。頗有可能當程序將reference r 綁定到p (p所指向的對象)的時候,p實際上沒有被去參考,甚至程序只是將p的值拷貝給實現r的指針。而程序將會繼續執行下去直到錯誤在後面的運行中更爲明顯的表 現出來,產生不可預知的危害。
下面的函數展現了另一種產生無效reference的方法:
int &f() { int i; // … return i; }
這個函數返回一個指向本地變量 i 的reference。然而當函數返回時,本地變量 i 的存儲空間也就消失了。所以這個函數實際返回了一個指向被回收了的空間的reference。這個操做與返回一個指向本地變量的指針的後果相同。有些編譯 器能夠在編譯時發現這個錯誤,但也頗有可能不會發現。
我喜歡reference,也有很好的理由使用它們代替pointer。但若是你指望使用reference來使你的程序健壯性顯著加強,那麼你多半會失望的。