1. 程式人生 > >C++中引用,指標,指標的引用,指標的指標

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

定義一個指標的三種寫法都對: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語言中的通過指標來互換值小函式例子),所以如果想通過被調函式來修改原實參的地址或給重新分配一個物件都是不能完成的,只能使用雙指標或指標引用(下面會進行詳解)

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

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

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

★相同點:

●都是地址的概念;

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

★不同點:

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

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

●引用沒有const,指標有constconst的指標不可變;(具體指沒有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 MYCLASSrefPointerFunc(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,所以不安全。const 指標雖然不能改變指向,但仍然存在空指標,並且有可能產生野指標(即多個指標指向一塊記憶體,free掉一個指標之後,別的指標就成了野指標)。

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

6、特別之處const

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

  • 常量指標VS常量引用

常量指標:指向常量的指標,在指標定義語句的型別前加const,表示指向的物件是常量。

定義指向常量的指標只限制指標的間接訪問操作,而不能規定指標指向的值本身的操作規定性。

常量指標定義"const int* pointer=&a"告訴編譯器,*pointer是常量,不能將*pointer作為左值進行操作。

常量引用:指向常量的引用,在引用定義語句的型別前加const,表示指向的物件是常量。也跟指標一樣不能利用引用對指向的變數進行重新賦值操作。

  • 指標常量VS引用常量

在指標定義語句的指標名前加const,表示指標本身是常量。在定義指標常量時必須初始化!而這是引用天生具來的屬性,不用再引用指標定義語句的引用名前加const。

指標常量定義"int* const pointer=&b"告訴編譯器,pointer是常量,不能作為左值進行操作,但是允許修改間接訪問值,即*pointer可以修改。

  • 常量指標常量VS常量引用常量

常量指標常量:指向常量的指標常量,可以定義一個指向常量的指標常量,它必須在定義時初始化。常量指標常量定義"const int* const pointer=&c"告訴編譯器,pointer和*pointer都是常量,他們都不能作為左值進行操作。

而就不存在所謂的"常量引用常量",因為跟上面講的一樣引用變數就是引用常量。C++不區分變數的const引用和const變數的引用。程式決不能給引用本身重新賦值,使他指向另一個變數,因此引用總是const的。如果對引用應用關鍵字const,起作用就是使其目標稱為const變數。即沒有:Const double const& a=1;只有const double& a=1;

總結:有一個規則可以很好的區分const是修飾指標,還是修飾指標指向的資料——畫一條垂直穿過指標宣告的星號(*),如果const出現線上的左邊,指標指向的資料為常量;如果const出現在右邊,指標本身為常量。而引用本身與天俱來就是常量,即不可以改變指向。

一、引用的概念

引用引入了物件的一個同義詞。定義引用的表示方法與定義指標相似,只是用&代替了*。
例如: Point pt1(10,10);
Point &pt2=pt1; 定義了pt2為pt1的引用。通過這樣的定義,pt1和pt2表示同一物件。
需要特別強調的是引用並不產生物件的副本,僅僅是物件的同義詞。因此,當下面的語句執行後:
pt1.offset(2,2);
pt1和pt2都具有(12,12)的值。
引用必須在定義時馬上被初始化,因為它必須是某個東西的同義詞。你不能先定義一個引用後才
初始化它。例如下面語句是非法的:
Point &pt3;
pt3=pt1;
那麼既然引用只是某個東西的同義詞,它有什麼用途呢?
下面討論引用的兩個主要用途:作為函式引數以及從函式中返回左值。 

二、引用引數

1、傳遞可變引數
傳統的c中,函式在呼叫時引數是通過值來傳遞的,這就是說函式的引數不具備返回值的能力。
所以在傳統的c中,如果需要函式的引數具有返回值的能力,往往是通過指標來實現的。比如,實現
兩整數變數值交換的c程式如下:
void swapint(int *a,int *b)
{
int temp;
temp=*a;
a=*b;
*b=temp;
}

使用引用機制後,以上程式的c++版本為:
void swapint(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
}

呼叫該函式的c++方法為:swapint(x,y); c++自動把x,y的地址作為引數傳遞給swapint函式。

2、給函式傳遞大型物件
當大型物件被傳遞給函式時,使用引用引數可使引數傳遞效率得到提高,因為引用並不產生物件的
副本,也就是引數傳遞時,物件無須複製
。下面的例子定義了一個有限整數集合的類: 
const maxCard=100; 
Class Set 
{
int elems[maxCard]; // 集和中的元素,maxCard 表示集合中元素個數的最大值。 
int card; // 集合中元素的個數。 
public:
Set () {card=0;} //建構函式
friend Set operator * (Set ,Set ) ; //過載運算子號*,用於計算集合的交集 用物件作為傳值引數
// friend Set operator * (Set & ,Set & ) 過載運算子號*,用於計算集合的交集 用物件的引用作為傳值引數 
...
}
先考慮集合交集的實現
Set operator *( Set Set1,Set Set2)
{
Set res;
for(int i=0;i<Set1.card;++i)
for(int j=0;j>Set2.card;++j)
if(Set1.elems[i]==Set2.elems[j])
{
res.elems[res.card++]=Set1.elems[i];
break;
}
return res;
}
由於過載運算子不能對指標單獨操作,我們必須把運算數宣告為 Set 型別而不是 Set * 。
每次使用*做交集運算時,整個集合都被複制,這樣效率很低。我們可以用引用來避免這種情況。
Set operator *( Set &Set1,Set &Set2)
{ Set res;
for(int i=0;i<Set1.card;++i)
for(int j=0;j>Set2.card;++j)
if(Set1.elems[i]==Set2.elems[j])
{
res.elems[res.card++]=Set1.elems[i];
break;
}
return res;
}

三、引用返回值

如果一個函式返回了引用,那麼該函式的呼叫也可以被賦值。這裡有一函式,它擁有兩個引用引數並返回一個雙精度數的引用:
double &max(double &d1,double &d2)
{
return d1>d2?d1:d2;
}
由於max()函式返回一個對雙精度數的引用,那麼我們就可以用max() 來對其中較大的雙精度數加1:
max(x,y)+=1.0;

參考:

char是一個字元型別 C++的內建型別,string是一個字元容器,是模版庫裡面的東西 是一個擴充套件的模版類 
string轉換成char * 可以使用c_str()方法,char *轉換成string 直接賦值就可以了 char*會隱式轉換成string
雙冒號是類名引導,一般出現在一個類的類名後面,用來標識冒號後面的資料是歸屬於哪個類的,如果一個變數或者函式是一個類的靜態成員,可以直接用類名來引導。
如果已經有一個類的物件 需要呼叫物件中的一個成員或者函式,就需要用點了。概要來說,如果要是涉及域指定的就是用::,本質上class和struct都是一個域。

OamString result = BeTracingRecordComFun::executeCmd(expectCmd);
size_t pos1 = result.find("Frame");
 if( pos1 == std::string::npos) { }