引用
在正式介紹指針之前,先來看看什麼是引用。
int a = 10;
int &ref1 = a;
你可能注意到了,上面的代碼裏有個 &。這就是我們的主角,引用。在變量名之前加上該符號,就可以指出它是個引用。
我們常説的引用,就是把別人的東西拿過來自己用。C++ 的引用也是如此,就是把另外一個對象拿過來用,然後起個名字。也就是説:
// a = 10
ref1 = 11;
// 現在,a = 11
對象就像瓶子,引用就是瓶子上面的標籤。訪問引用時,就是找到標籤所對應的瓶子。
引用必須滿足以下條件:
- 引用指向的是一個對象,而不是值
- 引用類型和它指向的對象匹配
- 引用必須在聲明時初始化
- 引用初始化後不能更改綁定的對象
要注意的是,引用必須在聲明時初始化。下面代碼會產生編譯錯誤:
int &ref2; // Error!
另外要注意的一點是,可以一次聲明多個引用,但都要加上 &。
int &ref1=a, ref2=a;// ref1 是引用,ref2 則是 a 值的拷貝
int &ref1=a, &ref2=a;// 都是引用
實際上,把 & 和類型名稱放一起也是可行的,但是考慮到上面這個一次聲明多個的問題,我還是建議和變量名放一起,否則有歧義。
指針
好好好,現在我們來到了正題。
先把上面的引用忘了,我們到最後再來講指針和引用的差別。
創建指針
int a = 10;
int *p;
p = &a;
這裏又有 * 又有 &,看暈了都。所以我把它拆成了三行,我們一行一行來。
首先,第二行,有個星號。這就是我們的主角,指針。* 表示創建的是指針。這一行聲明瞭一個 int 類型的指針,但是並沒有初始化。
第三行,把指針 p 指向 a 的地址。你肯定注意到這裏有個老熟人 &。當然啦,我讓你先把引用忘了是有原因的,因為這裏的 & 和上面引用那裏的完全不是一個東西。
這裏的 & 叫做 取地址符。它和一個變量一起用可以返回那個變量的地址。各位都知道你的內存很大,位置很多,取地址符就是用於查找變量的位置的。
Warning! 這裏不初始化指針拆成兩行的方法是不推薦的,因為未初始化的指針行為未知。實際請務必初始化!
既然得到了位置,我們自然就知道指針的用法了——“一個指針對應一個對象的位置”。
注意:
- 引用不是對象,沒有地址
- 指針自己是對象,所以可以用指針指向指針。這個後面再説。
ohhhhhhhh 恭喜你,你已經明白了怎麼創建指針,接下來就用一下吧。
用指針
cout << *p;
// a = 10, output: 10
*p = 20;
cout << *p;
// a = 20, output: 20
嗯,現在熟悉的東西又來了。我們在創建指針的時候已經用了星號了,現在訪問時又出現了。
或許你已經猜到了。很遺憾,這裏的星號和前面的含義也截然不同。* 叫解引用符(別看名字,它和引用沒半毛錢關係),用於從某個地址獲取其對應的對象。
啥意思?我們的變量對象在內存裏,& 找到了對象的位置用指針存起來,然後想要用的時候,再用 * 根據位置找到對象。
哎,回到上面的三行代碼。1、4 行輸出了對象,3 行則改變了對象的值。我們可以看到,由於根據位置找到的對象還是 a,所以 a 的值也發生了變化。
int b =30;
p = &b;
我們先前提到指針是對象,所以它本身也可以改變。
你可以用其它對象的地址重新賦值給指針,就像上面一樣。這樣指針就指向其它對象了。
再次恭喜你,你現在已經明白了怎麼用指針了。接下來再介紹點特殊的指針。
在繼續之前……
再強調一下,* & 兩個符號存在多重含義。
*:
- 在聲明變量時,在變量前,聲明它是個指針
- 在使用變量時,在變量前,是通過地址找對象(解引用符)
&:
- 在聲明變量時,在變量前,聲明它是個引用
- 在使用變量時,在變量前,是根據對象找地址
也就是説:
聲明前面是類型,其它時候在尋找。指針配上找對象,引用配上找地址。
空指針
int *p = nullptr;
int *p1 = 0;
我的天哪,這兩個指針並沒有指向某個對象的地址!會不會報錯啊!
其實並不會,它們叫做空指針。顧名思義,就是空的指針。空指針什麼都不指向。就是個指針而已,空的,用不了。通常你沒理由這麼幹,除非你真的暫時不知道該指向什麼,以後再指。這樣你用的時候就可以檢查指針是否有指向東西(是否為空),而不是未初始化指針的未知行為。
if(p){
...
}
if(p1){
...
}
如果指針是空的,那麼它在 if 裏相當於 false。所以可以像上面那樣檢查指針是否為空。
Warning! 未初始化和空指針不是一個東西。未初始化的指針的行為是未知的,不能這樣檢驗。所以確保初始化。
指針的嵌套
前面提到了指針是對象,也就是説指針也有地址,也就是自己的位置。那麼我們就可以套娃了,cpp 允許你嵌套,比如指向指針的指針。
int a = 10;
int *p1 = &a;
int **p2 = &p1;
cout<<*p2<<endl;
cout<<**p2<<endl;
cout<<*p1<<endl;
先想想取地址符和解引用符的作用,想想上面代碼的輸出是什麼。
示例輸出:
0x7ffe065143d4
10
10
嗯,你的輸出第一行肯定和我不一樣,且每次運行的輸出肯定不一樣。
如果你學過點底層知識,一定能看出來第一行是個十六進制數。沒錯它就是個地址。
為什麼會有這樣的結果呢?
通過圖片解析下你就明白了(第一行變量名,第二行變量的值,第三行變量的地址。注意 p2 p1 地址未知,是假設的)

可以看到,*p2 實際上指的是 p1,而它的值則是 a 的地址。而 **p2 才指的是 a 本身。也就是説,解引用一次,就找一次地址對應的對象。要想獲得 a,則必須解引用兩次。
再再再恭喜你一下,你已經完全明白了指針的簡單使用。
指針和引用
通過上面的講解,我們不難得出結論:
- 指針是對象
- 引用不是對象
- 指針、引用可以指向的是對象
- (推論)指針可以指向指針
所以顯然可以推出:
- 引用可以指向指針
- 指針不能指向引用
引用只是給對象貼了標籤(起別名)而已。而指針則是創建了另一個對象來存儲對象的位置。在這個過程中,最重要的是分清 & 和 * 到底是在聲明類型,還是作為取地址和解引用運算符。
嗯,夠清晰,夠明白。
下一篇,我們將進一步探索 const 限定,瞭解什麼是指針常量、指向常量的指針。依舊是奶奶級,拆碎了給你看。