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

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