C++中引用,指標,指標的引用,指標的指標

來源:互聯網
上載者:User

定義一個指標的三種寫法都對:1. int * p;  2. int* p;  3. int *p; 習慣不同而已
定義一個函數指標的三種寫法都對:1. int *p(); 2. int * p(); 3. int* p();

1、指標傳遞和引用傳遞

在C語言中,如果要實現在函數內部改變外部變數的值的話,就應該傳遞這個變數的指標。如果要通過指標訪問變數,必須使用指標運算子“*”。這樣在原始碼中就會顯得比較彆扭:

void function(int *pval){    *pval=100;    //pval=100;先不考慮此處類型轉換的錯誤,該代碼只能改變堆棧中臨時指標變數的地址,而不能改變指標指向對象的值}int main(){   int x=200;    function(&x);   return 0;}

為了能透明地使用指標來訪問變數,C++中引入了“引用”的概念

void function(int &refval){ refval=100;}int main(){ int x=200;  function(x); //當然,如下調用也可以。但這樣做就失去引入"引用"的原本意義了 int &refx=x; function(refx); return 0;}

這樣一來,只要改一下函式宣告,就可以在原始碼的層級上實現指標訪問和一般訪問的一致性。可以把“引用”想象成一個不需要“*”操作符就可以訪問變數的指標。上面的代碼的C語言形式的虛擬碼:

void function(int *refal){ *refval=100;}int main(){ int x=200; int *refx=&x; function(&x); function(refx); return 0;}

總結:

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

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

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

指標傳遞參數本質上是值傳遞的方式,它所傳遞的是一個地址值。值傳遞過程中,被調函數的形式參數作為被調函數的局部變數處理,即在棧中開闢了記憶體空間以存放由主調函數放進來的實參的值,從而成為了實參的一個副本。值傳遞的特點是被調函數對形式參數的任何操作都是作為局部變數進行,不會影響主調函數的實參變數的值。

而在引用傳遞過程中,被調函數的形式參數雖然也作為局部變數在棧中開闢了記憶體空間,但是這時存放的是由主調函數放進來的實參變數的地址(指標傳遞參數時,指標中存放的也是實參的地址,但是在被調函數內部指標存放的內容可以被改變,即可能改變指向的實參,所以並不安全,而引用則不同,它引用的對象的地址一旦賦予,則不能改變)。被調函數對形參的任何操作都被處理成間接定址,即通過棧中存放的地址訪問主調函數中的實參變數。正因為如此,被調函數對形參做的任何操作都影響了主調函數中的實參變數。

引用傳遞和指標傳遞是不同的,雖然它們都是在被調函數棧空間上的一個局部變數,但是任何對於引用參數的處理都會通過一個間接定址的方式操作到主調函數中的相關變數。而對於指標傳遞的參數,如果改變被調函數中的指標地址,它將影響不到主調函數的相關變數。如果想通過指標參數傳遞來改變主調函數中的相關變數,那就得使用指向指標的指標,或者指標引用。 即指標傳遞只是傳了一個地址copy, 在函數內部改變形參所指向的地址,不能改變原實參指向的地址,僅可以通過修改形參地址的內容,來達到修改實參內容的目的(原C語言中的通過指標來互換值小函數例子),所以如果想通過被調函數來修改原實參的地址或給重新分配一個對象都是不能完成的,只能使用雙指標或指標引用(下面會進行詳解)

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

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

關於C++符號表的進一步總結:http://blog.csdn.net/cclive1601/article/details/8101313

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

★相同點:

●都是地址的概念;

指標指向一塊記憶體,它的內容是所指記憶體的地址;而引用則是某塊記憶體的別名。

★不同點:

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

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

●引用沒有const,指標有const,const的指標不可變;(具體指沒有int& const a這種形式,而const int& a是有     的,  前者指引用本身即別名不可以改變,這是當然的,所以不需要這種形式,後者指引用所指的值不可以改變)

●引用不可為空,指標可以為空白;

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

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

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

 

2、 指標的指標和指標的引用


在下列函式宣告中,為什麼要同時使用*和&符號。以及什麼場合使用這種聲明方式?   
void func1( MYCLASS *&pBuildingElement );    

先來看“int **pp”和“int *&rp”區別。前者是一個指向指標的指標;後者是一個指標的引用。如果這樣看不明白的話,變換一下就清楚了:

typedef int * LPINT;
LPINT *pp;
LPINT &rp; 而指標的指標和指標的引用作為傳遞參數時,如下面的兩個函數在被調用時,編譯器編譯的二進位代碼都將傳遞一個雙重指標,只不過兩者的調用方法不同:

void function1(int **p){ **p=100; *p=NULL;}void function2(int *&ref){ *ref=100; ref=NULL;}

可見,“引用”僅僅是為了給重載操作符提供了方便之門,其本質和指標是沒有區別的。所以只要你碰到*&,就應該想到**。也就是說這個函數修改或可能修改調用者的指標,而調用者象普通變數一樣傳遞這個指標,不使用地址操作符&。 

3、關於指標的引用的詳解 下面用三個函數onePointerFunc,poiPointerFunc, refPointerFunc舉例詳解,三個函數均想要在函數調用完畢後可以指向新的對象。

傳單指標:voidonePointerFunc(MYCLASS *pMyClass) 

     { 
   DoSomething(pMyClass); 
   pMyClass = // 其它對象的指標 
   }  

調用:MYCLASS* p = new MYCLASS;  onePointerFunc(p); 調用onePointerFunc後p沒有指向新的對象:
第二條語句在函數過程中只修改了pMyClass的值。並沒有修改調用者的變數p的值。如果p指向某個位於地址0x008a00的對象,當func1返回時,它仍然指向這個特定的對象。

傳雙指標: voidpoiPointerFunc(MYCLASS** pMyClass); 
   { 
   *pMyClass = new MYCLASS; 
   }   
調用:MYCLASS* p =new MYCLASS;   poiPointerFunc(&p); 調用poiPointerFunc之後,p指向新的對象。

BTW,在COM編程中,到處都會碰到這樣的用法--例如在查詢對象介面的QueryInterface函數中: 
interface ISomeInterface { 
   HRESULT QueryInterface(IID &iid, void** ppvObj); 
   …… 
   }; 
   LPSOMEINTERFACE p=NULL; 
   pOb->QueryInterface(IID_SOMEINTERFACE, &p);   
   此處,p是SOMEINTERFACE類型的指標,所以&p便是指標的指標,在QueryInterface返回的時候,如果調用成功,則變數p包含一個指向新的介面的指標。   

傳指標的引用:voidrefPointerFunc(MYCLASS *&pMyClass); 
   { 
   pMyClass = new MYCLASS; 
   …… 
   }   

其實,它和前面所講得指標的指標例子是一碼事,只是文法有所不同。傳遞的時候不用傳p的地址&p,而是直接傳p本身:   
調用:MYCLASS* p = new MYCLASS; refPointerFunc(p);  調用refPointerFunc之後,p指向新的對象。


MFC在其集合類中用到的*&作為返回修飾符的例子--CObList,它是一個CObjects指標列表。 
class CObList : public CObject { 
   …… 
   // 擷取/修改指定位置的元素 
   CObject*& GetAt(POSITION position); 
   CObject* GetAt(POSITION position) const; 
   };   
這裡有兩個GetAt函數,功能都是擷取給定位置的元素。區別何在呢。 區別在於一個讓你修改列表中的對象,另一個則不行。所以如果你寫成下面這樣:CObject* pObj = mylist.GetAt(pos);  則pObj是列表中某個對象的指標,

如果接著改變pObj的值: pObj = pSomeOtherObj; 這並改變不了在位置pos處的對象地址,而僅僅是改變了變數pObj.


但是,如果寫成下面這樣: CObject*& rpObj = mylist.GetAt(pos);  

現在,rpObj是引用一個列表中的對象的指標,所以當改變rpObj時,也會改變列表中位置pos處的對象地址--換句話說,替代了這個對象。這就是為什麼CObList會有兩個GetAt函數的緣故。一個可以修改指標的值,另一個則不能。注意我在此說的是指標,不是對象本身。這兩個函數都可以修改對象,但只有*&版本可以替代對象


4、指標和引用的解釋

指標-對於一個類型T,T*就是指向T的指標類型,也即一個T*類型的變數能夠儲存一個T對象的地址,而類型T是可以加一些限定詞的,如const、volatile等等。見下圖,所示指標的含義:

引用-引用是一個對象的別名,主要用於函數參數和傳回值類型,符號X&表示X類型的引用。見下圖,所示引用的含義:

 5、指標和引用的區別 首先,引用不可以為空白,但指標可以為空白。前面也說過了引用是對象的別名,引用為空白——對象都不存在,怎麼可能有別名。故定義一個引用的時候,必須初始化。因此如果你有一個變數是用於指向另一個對象,但是它可能為空白,這時你應該使用指標;如果變數總是指向一個對象,i.e.,你的設計不允許變數為空白,這時你應該使用引用。如下圖中,如果定義一個引用變數,不初始化的話連編譯都通不過(編譯時間錯誤):

而聲明指標是可以不指向任何對象,也正是因為這個原因,使用指標之前必須做判空操作,而引用就不必。 其次,引用不可以改變指向,對一個對象"至死不渝";但是指標可以改變指向,而指向其它對象。說明:雖然引用不可以改變指向,但是可以改變初始化對象的內容。例如就++操作而言,對引用的操作直接反應到所指向的對象,而不是改變指向;而對指標的操作,會使指標指向下一個對象,而不是改變所指對象的內容。見下面的代碼:

#include<iostream>using namespace std;int main(int argc,char** argv){    int i=10;    int& ref=i;    ref++;    cout<<"i="<<i<<endl;    cout<<"ref="<<ref<<endl;    int j=20;    ref=j;    ref++;    cout<<"i="<<i<<endl;    cout<<"ref="<<ref<<endl;    cout<<"j="<<j<<endl;    return 0;}

對ref的++操作是直接反應到所指變數之上,對引用變數ref重新賦值"ref=j"(此處要注意ref是可以重新在賦值的,但指向並不會發生變化),並不會改變ref的指向,它仍然指向的是i,而不是j。理所當然,這時對ref進行++操作不會影響到j。而這些換做是指標的話,情況大不相同,請自行實驗。輸出結果如下:

再次,引用的大小是所指向的變數的大小,因為引用只是一個別名而已;指標是指標本身的大小,4個位元組。見下圖所示:

從上面也可以看出:引用比指標使用起來形式上更漂亮,使用引用指向的內容時可以之間用引用變數名,而不像指標一樣要使用*;定義引用的時候也不用像指標一樣使用&取址。 最後,引用比指標更安全。由於不存在Null 參考,並且引用一旦被初始化為指向一個對象,它就不能被改變為另一個對象的引用,因此引用很安全。對於指標來說,它可以隨時指向別的對象,並且可以不被初始化,或為NULL,所以不安全。const 指標雖然不能改變指向,但仍然存在null 指標,並且有可能產生野指標(即多個指標指向一塊記憶體,free掉一個指標之後,別的指標就成了野指標)。

總而言之,言而總之——它們的這些差別都可以歸結為"指標指向一塊記憶體,它的內容是所指記憶體的地址;而引用則是某塊記憶體的別名,引用不改變指向。" 6、特別之處const

在這裡我為什麼要提到const關鍵字呢。因為const對指標和引用的限定是有差別的,下面聽我

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.