C++入門解惑(2)——初探指針(上)
發(fā)布時(shí)間:2008-08-06 閱讀數(shù): 次 來源:網(wǎng)樂原科技
似乎從古老的C時(shí)代起,指針就開始成為群眾心目中的難點(diǎn);在如今的C++中,面向?qū)ο?、模板等技術(shù)的復(fù)雜使得過去C中面向過程基礎(chǔ)部分的學(xué)習(xí)難度淡化了,但指針這部分內(nèi)容依然占據(jù)在“難點(diǎn)區(qū)”的位置。究其原因,可能是相當(dāng)部分C/C++都是從Basic這樣比較“高級(jí)”的語言轉(zhuǎn)移過來的,甚或從零開始學(xué)習(xí)而之前基本沒有太多的編程經(jīng)驗(yàn)。而指針則屬于C/C++中最“低級(jí)”的部分之一,又是重頭戲,花樣比較多,對(duì)底層毫不了解的初學(xué)者們?cè)谶^這道檻時(shí)可能會(huì)暈一暈,這也在所難免。這節(jié)我們就嘗試從一些基本的相對(duì)底層的原理講起,唔,但愿能對(duì)各位看官有所幫助。
0.先談?wù)勛兞?br>
0.Talk about Variable
啊,變量,那倒是個(gè)熟悉親切的概念。在絕大多數(shù)的高級(jí)語言中,變量是字母和一些允許的符號(hào)(數(shù)字、下劃線等)組成的標(biāo)識(shí)符,它可以為我們記錄有用的數(shù)據(jù),并且能夠參與運(yùn)算、重新賦值……嗯,真是老朋友了。不過如果你要學(xué)C++,對(duì)這位老朋友你還要有更深刻的了解。先從變量的聲明說起,例如,我們寫:
int main()
{
int a;
// ...
return 0;
}
顯然,在主程序的首行我們聲明并定義了一個(gè)整型變量a,系統(tǒng)(確切地說是編譯器)看到這一行,會(huì)做兩件事:1.分配一小塊可用于容納整型數(shù)值的內(nèi)存專門為存儲(chǔ)變量a使用;2.暗中記住這塊內(nèi)存的首地址,以后你的程序只要是使用a的地方,系統(tǒng)就會(huì)通過那個(gè)地址值來找到那塊內(nèi)存,再按你的要求進(jìn)行相應(yīng)的操作……噫,要知道,內(nèi)存分為許許多多的存儲(chǔ)單元,就像城市里的各家各戶,每個(gè)單元(家戶)都有一個(gè)獨(dú)一無二的稱為地址值的數(shù)字(門牌號(hào))與其它單元區(qū)別開,要訪問某個(gè)單元(查戶口,呵呵),就要通過地址值(門牌號(hào))來進(jìn)行索引(走訪)。
噫,到目前為止這些操作都是系統(tǒng)在暗地里為我們代勞了,所以我們才得以輕輕松松地撰寫出簡(jiǎn)潔明了的程序。不過如果我們好奇心大發(fā)要看個(gè)究竟,C++也是允許的。例如,我們要查看a的地址值,則可以使用取值運(yùn)算符&,只要在變量前加上&,便可以得到它的地址:
#include <iostream>
using namespace std;
int main()
{
int a;
a = 3;
cout << "a = " << a << '\n'; // 第一個(gè)與變量相關(guān)聯(lián)的信息:變量的值
cout << "Address of a: " << &a << endl; // 第二個(gè)與變量相關(guān)聯(lián)的信息:變量的
// 地址值
return 0;
}
下面是我的機(jī)子上剛剛輸出的結(jié)果:
a = 3
Address of a: 0x241ff5c
對(duì)a = 3的輸出各位應(yīng)該沒有什么問題,但Address of a,則不同的機(jī)器,甚至同一機(jī)器不同的時(shí)間都會(huì)不一樣,這很好理解:畢竟內(nèi)存布局總是時(shí)時(shí)改變的,系統(tǒng)給你分配一塊空間,能用就是啦,不要太挑剔哦。還有,“0x241ff5c”是不是有些怪怪的?這是因?yàn)閷?duì)地址的輸出默認(rèn)采用16進(jìn)制,所以看起來比較玄奧,其實(shí)骨子里不過一整數(shù)罷了,你要看不順眼用十進(jìn)制輸出當(dāng)然也行,涉及到輸出流的格式操縱符,這里就不多說啦(自己查書^_^)。
現(xiàn)在,只要給對(duì)任何一個(gè)變量(或者對(duì)象,本節(jié)之后均統(tǒng)稱變量),我們都可以通過&運(yùn)算符了解它具體的存儲(chǔ)地址。那么,反過來,假如我們知道一個(gè)變量的地址,如何反過來對(duì)它進(jìn)行變量式的操作呢?答案是使用“*”運(yùn)算符,和“&”運(yùn)算符一樣,它也是加在一個(gè)地址值之前,結(jié)果我們就獲得了該地址所對(duì)應(yīng)的變量,這個(gè)過程通常稱為解引用(dereference),“*”便是解引用運(yùn)算符:
#include <iostream>
using namespace std;
int main()
{
int a;
a = 3;
cout << "a = " << a << '\n'; // 和前面一樣……
cout << "Address of a: " << &a << '\n'; // 和前面也一樣……
cout << "Get back a: ";
cout << *(&a) << '\n'; // 先用&a得到a的地址,再用*將由地址得回變量a
*(&a) = 5; // 同樣,用*得回的變量可以和原變量一樣進(jìn)行常規(guī)操作
cout << "Now, a = " << a << endl; // 驗(yàn)證a是不是真的改變
return 0;
}
這個(gè)搞笑的程序輸出結(jié)果如下(同樣,地址值只是可能的版本):
a = 3
Address of a: 0x241ff5c
Get back a: 3
Now, a = 5
呵呵,之所以說它搞笑,是因?yàn)槲覀兿扔萌≈匪惴麑?duì)a進(jìn)行取址,然后又用反引用算符從地址值得回a,實(shí)際開發(fā)中不大可能出現(xiàn)如此直接的曲線救國。不過這個(gè)搞笑程序倒是挺真切地讓我們明白取值運(yùn)算符&與反引用運(yùn)算符*是一對(duì)截然相反的兄弟,前者由變量得到其地址,而后者則由地址得到其對(duì)應(yīng)變量。如果兩者同時(shí)施用,即程序中的*(&a),則我們兜了一個(gè)圈最后還是得回了a本身。
1.指 針
1.Pointer
現(xiàn)在我們又知道了C++中的新一種數(shù)值類型:地址值,它與整型、浮點(diǎn)型、雙精度型、字符型等一樣是程序組成運(yùn)作的重要類型,只是大多數(shù)時(shí)候都被隱藏在程序語言背后而已。我們還知道,整型、浮點(diǎn)型等數(shù)值都對(duì)應(yīng)有整型變量/常量、浮點(diǎn)型變量/常量來對(duì)它們進(jìn)行存儲(chǔ),那么地址值是否可以用相應(yīng)變量/常量進(jìn)行存儲(chǔ)呢?答案是肯定的。C/C++中用于存儲(chǔ)地址值的變量稱為指針變量/常量,簡(jiǎn)稱為指針。這是一個(gè)頗為形象的說法:指針存儲(chǔ)的地址值通常是“指”向某個(gè)特定的存儲(chǔ)空間,有了指針的引導(dǎo),我們就可以訪問相應(yīng)的量;而且指針變量中存儲(chǔ)的地址是可以通過變量賦值改變的,這就使得指針具有相當(dāng)大的靈活性;此外,動(dòng)態(tài)內(nèi)存分配也離不開指針……這些都是以后的內(nèi)容,嗯,慢慢來,我們還是先研究一下如何聲明指針變量。
對(duì)于整形變量,我們聲明時(shí)使用關(guān)鍵字int進(jìn)行修飾,如
int a;
聲明了一個(gè)名為a的整型變量,由于a是由int修飾的,所以a就有int型變量所具有的屬性:比如它支持加法、減法、乘法、整除(而不是除法)、賦值等操作。那么假如我加上幾個(gè)字符,變成:
int a, *p;
那又是什么意思呢?按照上面的思路,給你五秒鐘的時(shí)間思考:1,2,3,4,5!OK,你大可以照著說,我們聲明了兩個(gè)整型的東東:a和*p,它們都支持加法、減法、乘法、整除(而不是除法)、賦值等操作……*p與a應(yīng)當(dāng)是等同的,它們都理應(yīng)可以代表一個(gè)整型變量。噫,很好,這是我們完全基于自己的理解而推斷出來的?,F(xiàn)在我們?cè)賮硌芯恳幌拢杭热?p可以代表一個(gè)整型變量,那么去掉*號(hào),p應(yīng)該意味著什么?
還記得前面所說的&與*的神奇關(guān)系么?它們起著截然相反的功能:對(duì)變量加&得到其地址,對(duì)地址加*得到其對(duì)應(yīng)變量?,F(xiàn)在把前面這句話反向表述,就變成:對(duì)地址去掉前面的&則得回其對(duì)應(yīng)變量(如&a變回a),對(duì)變量去掉*則得回其地址(如前面所說的*(&a)完全相當(dāng)于變量a,去掉*變?yōu)?amp;a則得到a的地址)。
好,我們知道*p可以代表整型變量,因此去掉*的p,就代表了*p的地址,也就是說,p表示一個(gè)地址!
哈,現(xiàn)在我們總算可以理解為什么聲明一個(gè)指針用的是*號(hào)而不是&號(hào)或者其它什么東東了?,F(xiàn)在有一個(gè)問題:聲明(定義)變量會(huì)出現(xiàn)相應(yīng)的內(nèi)存分配,例如系統(tǒng)看到int a;會(huì)為a分配地塊內(nèi)存,如此看來,對(duì)于語句int a, *p;似乎同時(shí)會(huì)為a,*p各分配一塊存儲(chǔ)整數(shù)的內(nèi)存。
噢,比較不幸,這回我們的邏輯沒有發(fā)揮理所當(dāng)然的作用,對(duì)于C/C++,int a, *p將使系統(tǒng)為a分配一塊整型值內(nèi)存(毫無疑問),但對(duì)于*p,由于*是一個(gè)運(yùn)算符,僅起標(biāo)示作用,所以系統(tǒng)關(guān)注的僅僅是標(biāo)識(shí)符p,它將為p分配內(nèi)存,而不是*p。嗯,再重復(fù)一遍吧:*只是幫助系統(tǒng)了解p的地位而出現(xiàn)的,任何聲明(定義),系統(tǒng)最終為其分配內(nèi)存的只是標(biāo)識(shí)符本身,所以最終p會(huì)得到屬于它的內(nèi)存。
p得到的是一塊怎樣的內(nèi)存呢?既然*p可以代表int型變量,因而剛剛我們分析出p就代表了標(biāo)示某個(gè)int型變量的地址值,把它們連起來說,就是:p得到的內(nèi)存就用于存儲(chǔ)標(biāo)示某個(gè)int型變量的地址值——p是一個(gè)存儲(chǔ)整型地址的變量——噢,p正是我們前面所設(shè)想的指針變量!呵呵,費(fèi)盡心力,終于得到你了!
嗯,一般的C++教程介紹指針的時(shí)候都是直接介紹其聲明,而我們基本上是通過邏輯推理,結(jié)合第0節(jié)的基礎(chǔ)一步一步導(dǎo)出聲明方法的,這樣的學(xué)習(xí)我想或許更有助于我們?cè)诒容^細(xì)致的層次上把握語言實(shí)質(zhì)。本章表述上較詳細(xì),文字較多(本人功夫有限,呵呵),但思路應(yīng)該還不算太復(fù)雜,只有幾個(gè)推導(dǎo)鏈,希望初涉此道的學(xué)友們有空多讀幾遍,把它讀透讀薄吧。然后……放松一下,喝喝茶,聽聽音樂什么的(其實(shí)我也累了^_^ ),下回我們?cè)倮^續(xù)講指針的聲明原理和一些應(yīng)用。在下才疏,與各位一樣都只是C++的初學(xué)者與愛好者,雖然無知,但喜歡思考一些雜散的問題,如果有什么錯(cuò)誤之處還望眾朋友指出,在下感激不盡。如果對(duì)本系列有什么建議與意見,也非常歡迎提出??傊?,希望我們能夠共同進(jìn)步。