1. 程式人生 > >指標、引用與const指標

指標、引用與const指標

指標與引用


  鄙人的C++是在網易雲課堂上跟著呂鑫老師學的,課程名字叫“C語言|C++|資料結構|MFC|linux呂鑫”。這個課程講的十分詳細,尤其是呂老師對於C++的巨集觀理解讓我多次茅塞頓開。可能大家都會有這種經歷,臨近考試的時候看書一週不如大神通講1小時,先建立巨集觀的脈絡和框架然後去學習細節。以前追宋鴻兵的《巨集觀》裡宋老師也提到了為什麼他可以掌握這麼多知識,那就是因為他不論是歷史也好經濟也好,先建立巨集觀的框架,然後把內容套在框架裡。換句話說,“觀”很重要,帶著“觀”去看待問題猶如按圖索驥,自然會輕鬆很多。扯遠了,還是先感謝呂老師,也同樣感謝CSDN的各位博主。
  課程的連結在這裡,該課程是完全免費的,還附帶資料:
  
https://study.163.com/course/introduction.htm?courseId=712019#/courseDetail?tab=1

  學習的過程中就做了一些筆記,後來在CSDN看的多了就萌生了自己寫技術部落格的想法,於是在某個txt中記錄了很多可以總結的點。結果該txt越來越大,回看竟然都感到陌生了,所以要趕緊在部落格上總結起來,“忘卻的救主快要降臨了吧,我正有寫一點東西的必要了”。


1,綜述

  指標(Pointer)和引用(reference)二者看似不同但又彷彿有相似之處。只有從邏輯和底層徹底搞明白指標和引用,才能思路更清晰地編寫程式碼。言簡意賅兩句乾貨:

  指標本身是個變數,儲存另一個變數的地址

  引用表面是變數別名,底層是個const 指標


2,指標

 2.1 什麼是指標

  定義變數本質是申請空間,譬如:

    int a;

  表示申請一個32位(32/64位系統)的空間,該空間中的二進位制碼錶示數量的規則是整型變數的方式。所以稱a為整形變數,範圍是-231~231-1(總數232,負數最高位是1,佔總數一半,正數和0佔另一半)。
  那現在我在a變數裡面儲存b變數的地址,a變數就成為了一個指標,指向b變數。

    int *a=b;

  因為32/64位系統中定址空間是232,所以a的長度是32位二進位制,這樣才可以不重不漏表示完所有的地址。指標也有變數型別,比如int型指標,double 型指標,這是為了語法的無歧義和程式安全考慮。可能各位會碰到各種繞來繞去的指標題目,這種題目不管疊加了多少指標多少*與&,只要一層層取下來就可以表示清楚了。*a就表示取出a中的數,&a表示取出a的地址。

 2.2 程式碼示例1

  可以看段程式碼應用一下:

int arr[] = {1,2,3,4,5,6,7,8};
int *p=arr;
*(p++)+=123;       
printf("%d,%d\n", *p,*(++p)); 

  第一行,定義陣列arr,元素共八個。
  第二行,定義指標p指向arr陣列。
  此時的*p 還是等於arr陣列的第0個元素,也就是1。
  第三行,p++裡的++是最後才運算,所以先執行*p+=123,也就是arr的第0個元素被賦值為123。此時arr變成{123,2,3,4,5,6,7,8} ,然後是p++,此時*p已經是等於arr的第1個元素了,也就是2。
  第四行,在執行printf時,括號裡的引數是從右往左的順序進行讀取的,也就是說先執行 *(++p),也就是p先加一再指標,指向的是arr第2個元素3,然後在執行*p,還是3最後顯示的內容就是:

  3,3

  注意:指標雖然是一個變數,但它是有型別的特殊變數。指標中儲存的是地址,指標加1不代表該地址加1,比如0x00a2fd68 加1後不是0x00a2fd69,而是根據指標的屬性去加。比如Int型是在地址上加4個位元組,那就是0x00a2fd6B,這是為了方便移動指標。

 2.3 程式碼示例2

void main()
{
	int a[5]={1,2,3,4,5};
	int *ptr=(int *)(&a+1);
	printf(%d,%d”,*(a+1),*(ptr-1));
}

  正確答案是:

      2,5

  這段程式碼牽扯到另一個問題,陣列與指標。第一個2很好出來,因為a為該陣列的首地址,即a為a[0]的地址,那麼a+1,就是a[1]的地址,按照該地址取內容就是a[1]=2。
  第二個有點複雜,關鍵在於,a與&a的值一樣型別不一樣。這也是鄙人困惑的地方之一。在之前的敘述中,鄙人認為指標本身也是一個變數,那麼a既然是一個指標就應該存在於記憶體當中,就應該有地址,a與&a肯定是不一樣的。可以看到下圖:

  事實是,a與&a值是相同的,型別不一樣,前者為陣列元素指標,後者為陣列指標。所以按照2.2中的敘述,前者加1偏移4個位元組,後者加1偏移4*5=20個位元組。所以將&a+1強制型別轉換為*int 賦值給ptr後,ptr為*int型別,減1偏移4個位元組,所以取內容是a[4]=5。
  鄙人覺得這個語法用起來雖然方便,但難以理解a到底在哪,它的地址是什麼。為什麼a和&a一樣的,為什麼不能用通用的指標去理解a。唯一能想到的解釋只能是:a單獨出現時,表示陣列首元素的指標,而&a中的a表示的是陣列這個抽象的東西。還請看到本部落格的各位大神賜教,指出計算機底層是怎麼處理的,類似的問題看哪本書比較好。


3 ,引用

  3.1 什麼是引用

  學過c/c++的肯定都聽過引用就是起別名,或者叫替身,有點像量子糾纏那樣,替身和本體同時變化。當然啦,計算機才沒有那麼大本事,所以引用內部還是在用指標,其實都是一個物件。資料是對實體世界的一次抽象,引用和指標又是對資料的第二次抽象,這樣才會讓資料擺脫儲存的桎梏,讓資料在邏輯上具有很強的操作性。
  先看一下引用怎麼定義吧:

int a = 10;
int &b = a;

  稱b是a的引用,假如b的值改變了,那麼a的值也會改變,即a,b是同體的。那麼這是否與2.1中所說的申請變數是開闢記憶體空間相矛盾呢,其實不矛盾,因為引用是C++的一個障眼法,底層還是指標。請看下面的對比程式碼:

int i=10;                  int i=10;
int &j=i;                  int * const p=&i;
j=-1;                      *p=-1;

  這兩份程式碼的效果是一樣的,實際上在C++內部就是這樣來操作的,不過抽象出了一個引用的概念而已。從int * const p的角度看,理解引用的特性就非常簡單了:

  • 引用型變數必須初始化
  • 引用變數必須掛在一個現有的同類型變數上
  • 引用只能在初始化的時候引用一次,不能再引用其他的變數

  3.2 引用的程式碼示例

  騙你的,沒有程式碼,放個圖片休息一下。


4,const 迷惑

  既然說到了引用,就不得不提const了,當它加入程式碼中,又會產生一些迷惑人的東西。
  首先看這一段程式碼:

const int i=1;
i=2;

  這是錯誤的,因為i被const 修飾了,表示i不能被更改。也就是說,被const修飾表示不能修改,那麼指標呢。

  const int * a; //指向整形常量 的指標,它指向的值不能修改
  int *const b; //指向整形的常量指標 ,它不能在指向別的變數,但指向(變數)的值可以修改。
  const int *const c; //指向整形常量 的常量指標 。它既不能再指向別的常量,指向的值也不能修改。

  這三句話請反覆琢磨三遍,其實無非是在限定倆東西,指標本身與指向的內容,換句話說就是指標本身作為變數能不能改,該變數作為地址找到的記憶體空間的數能不能改。
  但是很容易記混亂吧,技巧是看const的右邊是什麼就限定了什麼。第一個const右邊是int ,那說明是內容不能改。第二個const右邊是b,說明b本身不能改。第三個就是倆都限定了。
  如此看來在3.1中,既然指標不能再指向其他的內容,那麼引用初始化時肯定要掛在一個確定的變數上啊,而且不能改動。
  const 還有一個知識是修飾成員函式時的語法,這個以後再說。