C++中指針和引用的區別(超詳細)

指針和引用主要有以下區別:

  1. 引用必須被初始化,但是不分配存儲空間。指針不聲明時初始化,在初始化的時候需要分配存儲空間。
  2. 引用初始化後不能被改變,指針可以改變所指的對象。
  3. 不存在指向空值的引用,但是存在指向空值的指針

注意:引用作爲函數參數時,會引發一定的問題,因爲讓引用作參數,目的就是想改變這個引用所指向地址的內容,而函數調用時傳入的是實參,看不出函數的參數是正常變量,還是引用,因此可能引發錯誤。所以使用時一定要小心謹慎。

 

 

從概念上講。指針從本質上講就是存放變量地址的一個變量,在邏輯上是獨立的,它可以被改變,包括其所指向的地址的改變和其指向的地址中所存放的數據的改變。

而引用是一個別名,它在邏輯上不是獨立的,它的存在具有依附性,所以引用必須在一開始就被初始化,而且其引用的對象在其整個生命週期中是不能被改變的(自始至終只能依附於同一個變量)。

C++中,指針和引用經常用於函數的參數傳遞,然而,指針傳遞參數和引用傳遞參數是有本質上的不同的:

  • 指針傳遞參數本質上是 值傳遞的方式,它所傳遞的是一個地址值。值傳遞過程中,被調函數的形式參數作爲被調函數的局部變量處理,即在中開闢了內存空間以存放由主調函數放進來的 實參的值,從而成爲了實參的一個副本。值傳遞的特點是被調函數對形式參數的任何操作都是作爲局部變量進行,不會影響主調函數的實參變量的值。
  • 而在引用傳遞過程中, 被調函數的形式參數雖然也作爲局部變量在棧中開闢了內存空間,但是這時存放的是由主調函數放進來的實參變量的地址。被調函數對形參的任何操作都被處理成間 接尋址,即通過棧中存放的地址訪問主調函數中的實參變量。正因爲如此,被調函數對形參做的任何操作都影響了主調函數中的實參變量。
  • 引用傳遞和指針傳遞是 不同的,雖然它們都是在被調函數棧空間上的一個局部變量,但是任何對於引用參數的處理都會通過一個間接尋址的方式操作到主調函數中的相關變量。而對於指針 傳遞的參數,如果改變被調函數中的指針地址,它將影響不到主調函數的相關變量。如果想通過指針參數傳遞來改變主調函數中的相關變量,那就得使用指向指針的 指針,或者指針引用。

爲了進一步加深大家對指針和引用的區別,下面我從編譯的角度來闡述它們之間的區別:

程序在編譯時分別將指 針和引用添加到符號表上,符號表上記錄的是變量名及變量所對應地址。指針變量在符號表上對應的地址值爲指針變量的地址值,而引用在符號表上對應的地址值爲 引用對象的地址值。符號表生成後就不會再改,因此指針可以改變其指向的對象(指針變量中的值可以改),而引用對象則不能修改。

最後,總結一下指針和引用的相同點和不同點:

★相同點:

●都是地址的概念;

指針指向一塊內存,它的內容是所指內存的地址;而引用則是某塊內存的別名。

★不同點:

●指針是一個實體,而引用僅是個別名;

●引用只能在定義時被初始化一次,之後不可變;指針可變;引用“從一而終”,指針可以“見異思遷”;

引用沒有const,指針有constconst的指針不可變

●引用不能爲空,指針可以爲空

●“sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身的大小;

●指針和引用的自增(++)運算意義不一樣;

●引用是類型安全的,而指針不是 (引用比指針多了類型檢查)

 

 

 

 

這幾天看重溫了下《高質量C/C++編程指南》和 《More Effective C++》對於裏面的引用和指針覺得寫得很精闢,同時在網上也找了些別人寫的總結,引用過來大家分享下。

    雖然使用引用和指針都可以間接訪問另一個值,但他們之間有兩個重要區別

  • 引用總是指向某個對象,定義引用沒有初始化是錯誤的。
  • 賦值行爲的差異,給引用賦值修改的是該引用所關聯的對象的值,而並不是使引用與另一個對象關聯。引用一經初始化,就始終指向同一個特定對象。


★ 相同點:


    1. 都是地址的概念;

    指針指向一塊內存,它的內容是所指內存的地址;引用是某塊內存的別名。


★ 區別:


    1. 指針是一個實體,而引用僅是個別名;

    2. 引用使用時無需解引用(*),指針需要解引用;

    3. 引用只能在定義時被初始化一次,之後不可變;指針可變;

    引用“從一而終” ^_^

    4. 引用沒有 const,指針有 const,const 的指針不可變;

    5. 引用不能爲空,指針可以爲空;

    6. “sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身(所指向的變量或對象的地址)的大小;

    typeid(T) == typeid(T&) 恆爲真,sizeof(T) == sizeof(T&) 恆爲真,但是當引用作爲成員時,其佔用空間與指針相同(沒找到標準的規定)。

    7. 指針和引用的自增(++)運算意義不一樣;


 ★ 聯繫


    1. 引用在語言內部用指針實現(如何實現?)。

    2. 對一般應用而言,把引用理解爲指針,不會犯嚴重語義錯誤。引用是操作受限了的指針(僅容許取內容操作)。


★《高質量C/C++編程指南》6.6


    引用是C++中的概念,初學者容易把引用和指針混淆一起。一下程序中,n 是m 的一個引用(reference),m是被引用物(referent)。

  1. int m;  
  2. int &n = m;  

    n 相當於m 的別名(綽號),對n 的任何操作就是對m 的操作。例如有人名叫王小毛,他的綽號是“三毛”。說“三毛”怎麼怎麼的,其實就是對王小毛說三道四。所以n 既不是m 的拷貝,也不是指向m 的指針,其實n就是m 它自己。

   

複製代碼
引用的一些規則如下:1)引用被創建的同時必須被初始化(指針則可以在任何時候被初始化)。

    (2)不能有NULL 引用,引用必須與合法的存儲單元關聯(指針則可以是NULL)。

    (3)一旦引用被初始化,就不能改變引用的關係(指針則可以隨時改變所指的對象)。
複製代碼

 

    以下示例程序中,k 被初始化爲i 的引用。語句k = j 並不能將k 修改成爲j 的引用,只是把k 的值改變成爲6.由於k 是i 的引用,所以i 的值也變成了6.

  1. int i = 5;  
  2. int j = 6;  
  3. int &k = i;  
  4. k = j; // k 和i 的值都變成了6;  

    上面的程序看起來象在玩文字遊戲,沒有體現出引用的價值。引用的主要功能是傳遞函數的參數和返回值。C++語言中,函數的參數和返回值的傳遞方式有三種:值傳遞、指針傳遞和引用傳遞

    以下是“值傳遞”的示例程序。由於Func1 函數體內的x是外部變量n 的一份拷貝,改變x 的值不會影響n, 所以n 的值仍然是0.

  1. void Func1(int x)  
  2. {  
  3.     x = x + 10;  
  4. }  
  5. int n = 0;  
  6. Func1(n);  
  7. cout << “n = ” << n << endl;// n = 0  

    以下是“指針傳遞”的示例程序。由於Func2 函數體內的x 是指向外部變量n 的指針,改變該指針的內容將導致n 的值改變,所以n 的值成爲10.

  1. void Func2(int *x)  
  2. {  
  3.     (* x) = (* x) + 10;  
  4. }  
  5. ⋯  
  6. int n = 0;  
  7. Func2(&n);  
  8. cout << “n = ” << n << endl; // n = 10  

    以下是“引用傳遞”的示例程序。由於Func3 函數體內的x 是外部變量n 的引用,x和n 是同一個東西,改變x 等於改變n,所以n 的值成爲10.

  1. void Func3(int &x)  
  2. {  
  3.     x = x + 10;  
  4. }  
  5. //...  
  6. int n = 0;  
  7. Func3(n);  
  8. cout << “n = ” << n << endl; // n = 10  

對比上述三個示例程序,會發現“引用傳遞”的性質象“指針傳遞”,而書寫方式象“值傳遞”。實際上“引用”可以做的任何事情“指針”也都能夠做,爲什麼還要“引用”

    這東西?

    答案是“用適當的工具做恰如其分的工作”。

    指針能夠毫無約束地操作內存中的如何東西,儘管指針功能強大,但是非常危險。

    就象一把刀,它可以用來砍樹、裁紙、修指甲、理髮等等,誰敢這樣用?

    如果的確只需要借用一下某個對象的“別名”,那麼就用“引用”,而不要用“指針”,以免發生意外。比如說,某人需要一份證明,本來在文件上蓋上公章的印子就行了,如果把取公章的鑰匙交給他,那麼他就獲得了不該有的權利。

  

★條款一:指針與引用的區別


  指針與引用看上去完全不同(指針用操作符’*’’->’,引用使用操作符’.’),但是它們似乎有相同的功能。指針與引用都是讓你間接引用其他對象。你如何決定在什麼時候使用指針,在什麼時候使用引用呢?

  首先,要認識到在任何情況下都不能用指向空值的引用。一個引用必須總是指向 某些對象。因此如果你使用一個變量並讓它指向一個對象,但是該變量在某些時候也可能不指向任何對象,這時你應該把變量聲明爲指針,因爲這樣你可以賦空值給 該變量。相反,如果變量肯定指向一個對象,例如你的設計不允許變量爲空,這時你就可以把變量聲明爲引用。

  但是,請等一下,你懷疑地問,這樣的代碼會產生什麼樣的後果?

  1. char *pc = 0; // 設置指針爲空值  
  2. char& rc = *pc; // 讓引用指向空值  

  這是非常有害的,毫無疑問。結果將 是不確定的(編譯器能產生一些輸出,導致任何事情都有可能發生),應該躲開寫出這樣代碼的人除非他們同意改正錯誤。如果你擔心這樣的代碼會出現在你的軟件 裏,那麼你最好完全避免使用引用,要不然就去讓更優秀的程序員去做。我們以後將忽略一個引用指向空值的可能性。

  因爲引用肯定會指向一個對象,在C裏,引用應被初始化。

  1. string& rs; // 錯誤,引用必須被初始化  
  2. strings("xyzzy");  
  3. string&rs = s; // 正確,rs指向s  
  4.   
  5. 指針沒有這樣的限制。  
  6. string*ps; // 未初始化的指針  
  7.          // 合法但危險  

  不存在指向空值的引用這個事實意味着使用引用的代碼效率比使用指針的要高。因爲在使用引用之前不需要測試它的合法性。

  1. void printDouble(const double& rd)  
  2. {  
  3.  cout<< rd; // 不需要測試rd,它  
  4. }       // 肯定指向一個double值  
  5.   
  6. 相反,指針則應該總是被測試,防止其爲空:  
  7. void printDouble(const double *pd)  
  8. {  
  9.  if (pd)  
  10.  {// 檢查是否爲NULL  
  11.   cout<< *pd;  
  12.  }  
  13. }  

  指針與引用的另一個重要的不同是指針可以被重新賦值以指向另一個不同的對象。但是引用則總是指向在初始化時被指定的對象,以後不能改變。

  1. strings1("Nancy");  
  2. strings2("Clancy");  
  3.  string& rs = s1; // rs 引用 s1  
  4. string *ps= &s1; // ps 指向 s1  
  5. rs = s2; // rs 仍舊引用s1  
  6.        // 但是s1的值現在是"Clancy"  
  7.  ps = &s2; // ps 現在指向 s2;// s1 沒有改變  

總的來說,在以下情況下你應該使用 指針: 一是你考慮到存在不指向任何對象的可能(在這種情況下,你能夠設置指針爲空); 二是你需要能夠在不同的時刻指向不同的對象(在這種情況下,你能改變 指針的指向)。如果總是指向一個對象並且一旦指向一個對象後就不會改變指向,那麼你應該使用引用。 還有一種情況,就是當你重載某個操作符時,你應該使用引用。最普通的例子是操作符[]。這個操作符典型的用法是返回一個目標對象,其能被賦值。

 


  1. vector<int>v(10); //建立整形向量(vector),大小爲10  
  2.                  //向量是一個在標準C庫中的一個模板(見條款35)   
  3. v[5] = 10; // 這個被賦值的目標對象就是操作符[]返回的值  

  如果操作符[]返回一個指針,那麼後一個語句就得這樣寫:

  1. *v[5] = 10;  

  但是這樣會使得v看上去象是一個向量指針。因此你會選擇讓操作符返回一個引用。(這有一個有趣的例外,參見條款30

當你知道你必須指向一個對象並且不想改變其指向時,或者在重載操作符併爲防止不必要的語義誤解時,你不應該使用指針。而在除此之外的其他情況下,則應使用指針

 

 

C++ const引用詳解

 

 

(1)       在實際的程序中,引用主要被用做函數的形式參數--通常將類對象傳遞給一個函數.引用必須初始化但是用對象的地址初始化引用是錯誤的,我們可以定義一個指針引用。

 

1 int ival = 1092;
2 int &re = ival;   //ok
3 int &re2 = &ival;   //錯誤
4 int *pi = &ival;
5 int *&pi2 = pi;   //ok

 

(2)       一旦引用已經定義,它就不能再指向其他的對象.這就是爲什麼它要被初始化的原因。

 

(3)       const引用可以用不同類型的對象初始化(只要能從一種類型轉換到另一種類型即可),也可以是不可尋址的值,如文字常量。例如

 

1 double dval = 3.14159;
2 //下3行僅對const引用纔是合法的
3 const int &ir = 1024;
4 const int &ir2 = dval;
5 const double &dr = dval + 1.0;

上面,同樣的初始化對於非const引用是不合法的,將導致編譯錯誤。原因有些微妙,需要適當做些解釋。

引用在內部存放的是一個對象的地址,它是該對象的別名。對於不可尋址的值,如文字常量,以及不同類型的對象,編譯器爲了實現引用,必須生成一個臨時對象,引用實際上指向該對象,但用戶不能訪問它。

 

例如:

1 double dval = 23;
2 const int &ri = dval; 

編譯器將其轉換爲:

1 int tmp = dval; // double -> int
2 const int &ri = tmp; 

同理:上面代碼

 

1 double dval = 3.14159;
2 //下3行僅對const引用纔是合法的
3 const int &ir = 1024;
4 const int &ir2 = dval;
5 const double &dr = dval + 1.0; 

內部轉化爲:

 

複製代碼
 1 double dval = 3.14159;
 2 //不可尋址,文字常量
 3 int tmp1 = 1024;
 4 const int &ir = tmp1;
 5 
 6 //不同類型
 7 int tmp2 = dval;//double -> int
 8 const int &ir2 = tmp2;
 9 
10 //另一種情況,不可尋址
11 double tmp3 = dval + 1.0;
12 const double &dr = tmp3;
複製代碼

 

(4)       不允許非const引用指向需要臨時對象的對象或值,即,編譯器產生臨時變量的時候引用必須爲const!!!!切記!!

 

複製代碼
 1 int iv = 100;
 2 int *&pir = &iv;//錯誤,非const引用對需要臨時對象的引用
 3 int *const &pir = &iv;//ok
 4 const int ival = 1024;
 5 int *&pi_ref = &ival;    //錯誤,非const引用是非法的
 6 
 7 const int *&pi_ref = &ival;   //錯誤,需要臨時變量,且引用的是指針,而pi_ref是一個非常量指針
 8 
 9 const int * const &pi_ref = &ival;  //正確
10 
11 //補充
12 const int *p = &ival;
13 const int *&pi_ref = p;  //正確 
複製代碼

 

(5)       ********對於const int *const & pi_ref = &iva; 具體的分析如下:*********

1.不允許非const引用指向需要臨時對象的對象或值

 

int a = 2;
int &ref1 = a;// OK.有過渡變量。
const int &ref2 = 2;// OK.編譯器產生臨時變量,需要const引用 

2.地址值是不可尋址的值

int * const &ref3 = &a;   // OK; 

    3.於是,用const對象的地址來初始化一個指向指針的引用

 

const int b = 23;
const int *p = &b;
const int *& ref4 = p;
const int *const & ref5 = &b;   //OK 

const引用的語義到底是什麼?

最後,我們可能仍然不明白const引用的這個const的語義是什麼

 

const引用表示,試圖通過此引用去(間接)改變其引用的對象的值時,編譯器會報錯!

 

這並意味着,此引用所引用的對象也因此變成const類型了。我們仍然可以改變其指向對象的值,只是不通過引用

 

下面是一個簡單的例子:

 

複製代碼
 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main()
 5  {
 6      int val = 1024;
 7      const int &ir = val;     
 8      val++;
 9 
10      //ir++;
11     cout << val << " " << ir << endl;
12 
13      return 0;
14 }
複製代碼

 

其中第10行,如果我們通過ir來改變val的值,編譯時會出錯。但是我們仍然可以通過val直接改變其值(9)

 

總結:const引用只是表明,保證不會通過此引用間接的改變被引用的對象!

 另外,const既可以放到類型前又可以放到類型後面,放類型後比較容易理解:

string const *t1;
const string *t1;
typedef string* pstring;string s;
const pstring cstr1 = &s;就出錯了
//但是放在類型後面不會出錯:
pstring const cstr2 = &s;

 

參考:http://blog.csdn.net/wangqiulin123456/article/details/8464418

        http://blog.csdn.net/k2eats/article/details/2541790?reload

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章