詳解C++中指標(*)、取地址(&)、解引用(*)與引用(&)的區別 (完整程式碼)

PengPengBlog發表於2017-05-03

一、初步瞭解——指標與取地址

先看程式:

  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int num = 7;  
  6.     int *p = &num;  
  7.     printf("%d 的地址是 %p\n", num, p);  
  8.     return 0;  
  9. }  
上面int *p定義了一個指向int型別指標p(我們使用*符號把p宣告為指標),並初始化p使其指向int型別的變數num,這裡&num中的&是取地址操作符,當&作用於一個物件上時,它返回了該物件的地址。

所以這裡指標p指向了num所對應的地址。(我測試時輸出了0028FF1C)


二、如何使用指標?——解引用與指標賦值

  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int num = 7;  
  6.     int *p = &num;  
  7.     printf("數值%d所在的地址是 %p\n", num, p);  
  8.     printf("指標p所指向的地址為 %p , 該地址上所儲存的值為%d\n", p, *p);  
  9.     *p = 100;  
  10.     printf("指標p所指向的地址為 %p , 該地址上所儲存的值為%d\n", p, num);  
  11.     return 0;  
  12. }  

注意這裡*操作符為解引用操作符,它返回指標p所指的物件(左值)。

我們可以對*p賦值(對左值賦值),從而改變p所指的地址上所儲存的值,從而改變此地址所儲存的變數num的值。(num的值變為100)

當然,我們也可以給指標p賦值,使其指向另外一個地址(這樣就改變了在解引用時獲取的左值):

  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int num = 7, another = -5;  
  6.     int *p = &num;  
  7.     p = &another;  
  8.     printf("%d\n", *p);//-5  
  9.     return 0;  
  10. }  

三、引用

從某種意義上來說,引用完全有別於上面說介紹的內容:

  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int val = 7, val2 = 999;  
  6.     int &refval = val, &refval2 = val2; ///引用必須要初始化,使其繫結到一個變數上  
  7.     ///修改引用的值將改變其所繫結的變數的值  
  8.     refval = -12;  
  9.     printf("%d %d\n", val, refval);//-12,refval的值和val一樣  
  10.       
  11.     ///將引用b賦值給引用a將改變引用a所繫結的變數的值,  
  12.     ///引用一但初始化(繫結),將始終繫結到同一個特定物件上,無法繫結到另一個物件上  
  13.     refval = refval2;  
  14.     printf("%d %d\n", val, refval);//999  
  15.     return 0;  
  16. }  

但是可以用int *&refp = p;的方式將一個指標引用和一個指標繫結起來。

PS:如果還是不理解,可以把引用認為是一個變數的別名,就和#define ...,typedef ...很像。


使用引用有何優點?

在傳參的時候,使用指標傳參,編譯器需要給指標另行分配儲存單元,儲存一個該指標的副本,在函式中對這個副本進行操作;使用引用傳參,編譯器就不需要分配儲存空間和儲存副本了,函式將直接對實參進行操作。所以使用引用使得程式的效率更高。


四、補充

a)指向指標的指標

  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int val = 7;  
  6.     int *p = &val;  
  7.     int **p2 = &p;///**宣告一個指向指標的指標  
  8.     printf("val的值為%d %d",*p,**p2);///**p2為兩次解引用,可看做*(*p2)  
  9.     return 0;  
  10. }  

由於指標也要佔用記憶體空間存放其值,所以我們也可以定義一個指向指標的指標。


b)指標與陣列

指標和迭代器非常像,我們可以說指標就是陣列的迭代器。

陣列具有較好的可讀性,指標具有更強的靈活性。一般,對某些多維陣列中非連續的元素的隨機訪問用下標表示比較方便,當按遞增(減)順序訪問陣列時,使用指標快捷而且方便。

在某些情況下使用指標更好,與陣列相比,它可以1. 節省空間(指標只佔用4位元組空間) 2. 省去了呼叫memset()的時間

  1. #include<cstdio>  
  2.   
  3. int val[100];  
  4.   
  5. int main(void)  
  6. {  
  7.     for (int i = 0; i < 100; ++i)  
  8.         val[i] = i;  
  9.     int *p = val;///近似理解為 int *p = &val[0];  
  10.     ///隱式指標轉換:陣列自動轉換為指向第一個元素的指標  
  11.     printf("%d\n", *p); ///指標p指向val的第一個元素,即val[0]  
  12.       
  13.     int t = 100;  
  14.     while (t--)  
  15.         ///可以直接對指標進行加減運算,就和迭代器一樣  
  16.         printf("%d\n", *(p++));///輸出0~99  
  17.           
  18.     ///指標可以做差:  
  19.     int *p2 = &val[10], *p3 = &val[20];  
  20.     printf("%d\n", p3 - p2); //10  
  21.     printf("%d\n", p2 - p3); //-10  
  22.       
  23.     ///還可以比比較大小:  
  24.     printf("%d\n", p2 < p3 ? p3 - p2 : p2 - p3); //10  
  25.     return 0;  
  26. }  

PS:不將陣列轉換為指標的例外情況為有:&陣列,sizeof(陣列),以及用陣列對陣列的引用進行初始化時。

由於sizeof的計算髮生在編譯時刻,所以它可以被當作常量表示式使用:

  1. char arr[sizeof(int) * 10]; // ok  


對於多維陣列,只要把指向指標的指標理解好了就行:

  1. #include<cstdio>  
  2.   
  3. int val[100][100];  
  4.   
  5. int main(void)  
  6. {  
  7.     val[2][1] = 666;  
  8.     ///如何用指標取出val[2][1]?  
  9.     printf("%d", *(*(val + 2) + 1));  
  10.     return 0;  
  11. }  


c)指標與字串

請看這篇文章中的“字串賦值”部分。


d)危險!探測陣列外面的世界

“輸出流?”

  1. #include<cstdio>  
  2.   
  3. int INCORRECT_getIntlen(int *num)  
  4. {  
  5.     int *p = num;  
  6.     printf("%x %p\n", *p, p);  
  7.     while (*p++)///不合理的判斷條件  
  8.         printf("%x %p\n", *p, p);  
  9.     return (p - num);  
  10. }  
  11.   
  12. int main(void)  
  13. {  
  14.     int num[20];  
  15.     int *p[20];///不建立指標陣列造成的最終結果也不同  
  16.     for (int i = 0; i < 20; ++i)  
  17.     {  
  18.         num[i] = i + 1;  
  19.         p[i] = &num[i];  
  20.     }  
  21.     for (int i = 0; i < 20; ++i)  
  22.     {  
  23.         //printf("%x\n", num[i]);  
  24.         printf("%x\t%p\n", num[i], p[i]);  
  25.     }///兩個printf造成的最終結果不同  
  26.     ///陣列越界造成的後果是未知的  
  27.     puts("");  
  28.     printf("%d\n", INCORRECT_getIntlen(num));  
  29.       
  30.     ///推薦下面這種做法  
  31.     printf("%d\n"sizeof(num) / sizeof(int));  
  32.     return 0;  
  33. }  

PS:指標的長度。在32位的編譯環境中,sizeof(p)的結果都是4,也就是一個地址編碼的長度。


e)陣列作為函式引數傳遞

雖然很多時候陣列名被呼叫時會被轉換成指標,但是不可否認的是它的確是特殊型別,並且在若干地方某些操作並不會發生,如使用&,sizeof,以及初始化賦值給引用時。

在定義的時候:
int function(int *a);
int function(int *a[]);
int function(int *a[10]);
這三種定義,會被編譯器解析成int function(int *a);也就是說從編譯器開始,已經成為指標了,在函式體內部沒有辦法知道陣列的長度。

這時C++中的vector就派上用場了。

  1. #include<cstdio>  
  2. #include<vector>  
  3. using namespace std;  
  4.   
  5. int getVectorSize(vector<int> v)  
  6. {  
  7.     return v.size();  
  8. }  
  9.   
  10. int main(void)  
  11. {  
  12.     vector<int> v;  
  13.     v.push_back(1);  
  14.     v.push_back(2);  
  15.     printf("%d\n",getVectorSize(v));  
  16.     return 0;  
  17. }  

但若你不想用vector,有以下三種方法:

1. 仿照字串,加一個結束標記符(比如null)

2. 傳遞指向陣列的第一個元素的指標和最後一個元素的下一個位置的指標(就像我們在排序時呼叫的sort(a,a+n)一樣)

3. 將第二個形參定義為陣列的大小(size_t size),傳遞的值可以為:sizeof(a)/sizeof(*a)

相關文章