本節書摘來自異步社群出版社《好學的c++程式設計》一書中的第2章,第2.3節,作者: 張祖浩 , 沈天晴,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
好學的c++程式設計
變量存儲空間位址的指向
古詩雲:“借問酒家何處有?牧童遙指杏花村。”路人向牧童請問酒家的位址,牧童感到光口頭表述酒家位址不夠清楚,要用一個手指指向酒家,就清楚了。并且路人由此可知,隻要通路牧童所指,就能通路到所指杏花村酒樓的美酒佳肴了。
圖2.4中,“2012是變量a的存儲空間位址。”這個關系是用語言或文字來表述的。那麼,在圖形上如何來表述這種關系呢?受牧童啟發,簡單得很!從位址2012畫一個箭頭指向變量a就行了,如圖2.5所示。這種圖形讓人一眼就看明白:位址2012指向變量a。c++約定好:這表明2012是變量a的位址,這當然也就是變量a存儲空間的位址。

這樣,變量位址就有了指向,是哪個變量的位址,它就指向哪個變量。說到底,是哪個存儲空間的位址,它就指向哪個存儲空間。由此,指向存儲空間的位址就獲得了一個名稱,叫做“指針”。說白了,指針就是變量位址,就是變量存儲空間位址,實際就是變量存儲空間首位元組位址。這些位址不僅有指向,而且有了與所指變量存儲空間同樣的類型。
位址運算符 &
變量位址已如上所述。那麼,在程式中如何能從變量之名獲得該變量之位址呢?這就要靠位址運算符 &。在這裡,符号 & 作為位址運算符使用。
我們将變量名(例如a)置于位址運算符 & 的右側得 &a,則 &a就是變量a的位址。同理,&b就是變量b的位址。對圖2.4中情況而言,&a 就是位址2012,&b 就是位址2016。
要注意,& 用在不同的場合有不同的含義。在這裡,不是在聲明中,& 的右側是一個變量,這時,& 是作為位址運算符使用。不要和别名聲明中的 & 混淆。
指針變量
以上講的變量的位址,例如圖2.5中的位址2012,它是變量a位址,也就是變量a存儲空間的位址。這個位址之值還沒有存儲起來。如果要存儲,當然還要另配給一個存儲空間來存儲它。
若某變量(比如p),系統為其配給的存儲空間專門用來存放某另一變量(比如a)的位址,則此變量(p)就叫做指針變量,也可簡稱為指針。
上述位址2012就可用指針變量存儲空間進行存儲,隻要将該位址賦給該指針變量就行。
例如,圖2.6(a)中,右框表示某變量a的存儲空間,位址為 &a。具體說,&a就是2012。左框表示指針變量p的存儲空間,若将a的位址 &a作為指針值,賦給p,語句為:
則指針變量p的存儲空間中就存儲了變量a的位址 &a了,p的值就是變量a的位址2012。這時我們就說,變量p是變量a的指針,變量p指向了變量a。或者說,a是指針變量p所指的變量,如圖2.6(a)中箭頭所示。變量a和變量p之間的關系也可簡示如圖2.6(b)。
指針變量的值是可以變化的。如果指針p之值不是變量a的位址&a了,而改變為變量b的位址&b。則指針就不指向a,而改為指向b了。例如,看圖2.7(a),經指派語句p=&a;q=&b;使指針p指向了變量a,指針q指向了變量b。如果重作指派p=&b;q=&a;使兩個指針之值作了互換,則其指向也就互換了,如圖2.7(b)所示。
以上講的變量a、b都是int型變量,儲存其位址的變量p、q是int型指針變量。
指針變量的類型
我們知道,指針變量的值總是某個存儲空間位址。所指存儲空間是有類型的。我們把指針所指存儲空間的類型定義為指針變量的類型。這樣一來就可以知道,float型指針是指向float型存儲空間的;char型指針是指向char型存儲空間的。指針是什麼類型,所指存儲空間就是什麼類型。反過來說,所指存儲空間是什麼類型,指針就是什麼類型。反正指針(位址)的類型和所指存儲空間的類型是一緻的。
例如,int型變量i的位址 &i就是int型指針,char型變量c的位址&c就是char型指針。變量位址的類型和變量存儲空間的類型是一緻的,當然也和變量類型是一緻的。
總體來說,變量、變量存儲空間、存儲空間位址、存儲空間首位元組位址以及存放存儲空間位址的指針變量,都有類型,變量是什麼類型,它們就全都是什麼類型。它們的類型和變量類型完全一緻。
單純的一個記憶體單元位址,它不指向某個變量(或存儲空間)談不上什麼類型。沒有類型的指針叫void型指針(如以vp表示)。任何其他類型的指針都可直接對vp進行指派,指派隻是将一個位址值賦給了它,它仍然是void型指針。反過來,若void型指針vp想對别的指針進行指派,則必須先将vp進行強迫類型轉換為相同類型後,然後才可進行指派。
指針變量初次出場也要作聲明,表明指針變量屬何類型。
指針變量聲明過後,系統就配給它存儲空間,用以存儲指針變量之值。不管指針是何類型,指針值總是一個8位十六進制的位址值。用4個位元組大小的存儲空間存儲位址就行。
但指針所指存儲空間的大小,則随指針類型的不同而有所不同。比如,char型指針所指char型存儲空間大小為1個位元組,而double型指針所指double型存儲空間大小則為8個位元組(見表2-1)。
對于圖2.7中的變量a和指針變量p,假設變量a是int類型,可作如下聲明:
第一條語句是對int型變量a的聲明,大家是熟悉的。
第二條語句是對指針變量p作的聲明。在這個聲明中,“int”和“*”一起作為一個整體,描述了p是一個int型指針變量。接着的“=”是用int型變量a的位址&a,給p初始化。記住!記住:誰的位址給指針變量初始化,指針變量就指向誰。這樣,指針p的初值就是&a,指針p指向了變量a,如圖2.8所示。
當然,不在聲明中進行初始化,而在聲明過後另寫一個指派語句也是可以的。例如:
頭兩句對變量a和指針變量p作了聲明。末句對指針p賦以 &a,使p指向了a。記住!記住:誰的位址給指針變量初始化(或指派),指針變量就指向誰。
上述3條語句合并寫成如下一條語句也行:
這裡,雖然“int”和“”不緊靠在一起,但仍在同一個聲明語句中,它倆仍然作為一個整體,描述了p是一個int型指針變量。接着的“=”是對p進行初始化,将變量a的位址 &a作為指針變量p的初值。這裡,如果脫離整體考慮,而認為把 &a 賦給了 p ,那就錯啦。
上述聲明過後,我們可以稱p為“int型指針變量”,也可以稱p為“int 型變量”。其中“int ”作為一個整體,表明了變量p是一個int 型指針。
指針聲明的一般形式為:
同類型的指針,互相間可以指派,例如:
a行語句中,聲明了兩個int型指針變量p和q。b行語句将變量a的位址&a賦給了指針變量p,使p指向了a。c行語句對同類型指針變量q賦以p值,使q值也等于a的位址,因而也指向了a。p和q都指向同一個變量a,如圖2.9所示。
void型指針vp的聲明語句形式是:void *vp;
要注意,“”用在不同的場合有不同的含義。在這裡,“”用在變量的聲明中,它表示這是一個指針變量。
我們知道,所謂對變量進行通路,就是對變量的存儲空間進行通路,就是對變量的存儲空間進行寫值或讀值。
與直呼其名的通路不同。用指針進行通路是循址通路。循址通路不涉及變量之名,而是對指針(位址)所指存儲空間的内容進行通路。
那麼,怎樣才能由指針來獲得指針所指存儲空間的内容呢?這就要靠所指運算符 *。
所指運算符 *
在這裡,運算符 作為所指運算符使用。我們将指針名(比如p)置于所指運算符 的右側,得 p,則p就是指針p所指存儲空間的内容。同理,指針q所指存儲空間内容就是 *q。
要注意,“”用在不同的場合有不同的含義。在這裡,不是在聲明中,“”的右側是一個指針p,這p就表示指針p所指存儲空間的内容。“”作為所指運算符使用。
指針所指和所指變量
對于指針所指存儲空間的内容,本書簡稱之為指針所指。例如p就是指針p所指存儲空間的内容,簡稱 p為指針p的所指。
如果将變量a的位址 &a賦給指針p,即:
則p就指向了變量a,這時,p所指存儲空間的内容就是變量a。指針所指*p和所指變量a二者同是p所指存儲空間的内容,二者是一回事,故得下列等價式:
公式3
由此可得結論:如果指針p指向變量a,則通路指針所指 *p,就是通路所指變量a。記住!記住!
快餐循址派送
張三想吃快餐,隻要告知張三家住宅位址。大堂經理就啟動送餐程式,送餐員就出馬給位址所指賦送快餐。送餐程式之内沒有客戶尊姓大名,送餐員隻管埋頭按位址所指送餐。給位址所指的住宅空間賦送了快餐,實際就是給程式之外的張三賦送了快餐。真是:
這裡的關鍵就在于,必須将張三住宅位址賦入送餐員本子,使送餐員本子中的位址(指針)指向張三。這樣,就能實作通路指針所指就是通路所指張三。給指針所指送餐就是給所指張三送餐。
用指針所指對所指變量進行通路例
【例2-3】參照【例2-2】,說明通路指針所指就是通路所指變量。程式如下:
程式運作結果為:
此例不妨與【例2-2】對照來看。程式中,a行聲明了3個int型變量a、b和c,并都有了初值。b行輸出a、b和c,輸出結果如f行所示。
在c行聲明了3個int型指針變量p1、p2和p3,并且分别用變量a、b和c的位址給予初始化。這樣,就使指針p1、p2和p3分别指向了變量a、b和c。
在d行的一對花括弧内,對指針所指 p1、p2和 *p3都分别作了增值指派,即分别取三者的原值加上一定的值後,再重新分别賦給三者。
e行又輸出a、b和c,輸出結果如g行所示。
奇怪!從f和g行的輸出結果相比來看,a、b和c的值都變大了。可是,從整個程式來看,并未對a、b和c進行增值指派呀!何以會變大呢?
關鍵在于c行的語句,用a、b和c的位址分别對3個指針p1、p2和p3進行了初始化。誰的位址給指針變量初始化(或指派),指針變量就指向誰。這就使3個指針分别指向了變量a、b和c。記得嗎?如果指針指向了某變量,則通路指針所指就是通路所指變量。在d行,雖然是在花括弧内,對指針所指p1、p2和*p3進行增值指派,但是實際就是在花括弧外,對所指變量a、b和c進行增值指派。具體來說,就是有如下的關系:
以後會看到,d行花括弧内的程式可以讓一個函數來完成。到那時,可以看到如下情景:
這恰似按送餐規程内的程式對指針所指進行送餐,實際就是規程之外的張三獲得快餐。在這裡,先提前讓大家領會一下,指針在後面内容中将有精彩表現。
某指針指向某變量的兩個等價式
綜上所述可知,如果在圖形中出現指針的指向關系:t圖檔 14h。則可獲得下列兩個等價式,這兩個等價式在後續内容中常用到:
(1)t圖檔 15&h; (2)*t圖檔 16h。
(1)式表明t的值就是變量h的位址,指針t指向了變量h;
(2)式表明指針所指t就是所指變量h。通路指針所指t就是通路所指變量h。
随機值指針是危險分子
若聲明了一個指針變量p,并未對它進行初始化或指派,則p值(位址值)将是一個随機數,這樣的指針叫做随機值指針。随機值指針說不定是指向哪個存儲空間。所指存儲空間的内容也說不定是什麼内容。若所指存儲空間内容是很重要的資料,這時冒然給指針所指 *p指派,就将使該重要資料丢失,可能造成重大故障。是以說,随機值指針是一個危險分子。
若一時不知用哪個變量的位址對指針進行初始化或指派為好,這時可暫時先用一個空值(null或0)進行初始化或指派,以代替指針存儲空間裡的随機數。例如:
或:<code>`</code>javascript
int *p=0;
double a;
double *u=&a;
double **x=&u;
double *u;
double **x;
u=&a;
x=&u;
double a, u=&a, *x=&u;
資料類型 **變量名;
using namespace std;
int main()
{
char a='a',b='b',t; //a
char u=&a, v=&b; //b
char x=&u, y=&v; //c
a=a b=b //g
a=b b=a //h