C++入門解惑(2)——初探指標(上) (轉)

amyz發表於2007-11-16
C++入門解惑(2)——初探指標(上) (轉)[@more@]

  似乎從古老的C時代起,指標就開始成為群眾心目中的難點;在如今的C++中,面向、模板等技術的複雜使得過去C中程式導向基礎部分的學習難度淡化了,但指標這部分內容依然佔據在“難點區”的位置。究其原因,可能是相當部分C/C++都是從Basic這樣比較“高階”的語言轉移過來的,甚或從零開始學習而之前基本沒有太多的。而指標則屬於C/C++中最“低階”的部分之一,又是重頭戲,花樣比較多,對底層毫不瞭解的初學者們在過這道檻時可能會暈一暈,這也在所難免。這節我們就嘗試從一些基本的相對底層的原理講起,唔,但願能對各位看官有所幫助。

0.先談談變數
0.Talk about Variable

  啊,變數,那倒是個熟悉親切的概念。在絕大多數的高階語言中,變數是字母和一些允許的符號(數字、下劃線等)組成的識別符號,它可以為我們記錄有用的資料,並且能夠參與運算、重新賦值……嗯,真是老朋友了。不過如果你要學C++,對這位老朋友你還要有更深刻的瞭解。先從變數的宣告說起,例如,我們寫:

int main()
{
  int a;
 // ...
  return 0;
}

 顯然,在主的首行我們宣告並定義了一個整型變數a,(確切地說是)看到這一行,會做兩件事:1.分配一小塊可用於容納整型數值的專門為變數a使用;2.暗中記住這塊記憶體的首地址,以後你的程式只要是使用a的地方,系統就會透過那個地址值來找到那塊記憶體,再按你的要求進行相應的操作……噫,要知道,記憶體分為許許多多的儲存單元,就像城市裡的各家各戶,每個單元(家戶)都有一個獨一無二的稱為地址值的數字(門牌號)與其它單元區別開,要訪問某個單元(查戶口,呵呵),就要透過地址值(門牌號)來進行(走訪)。
  噫,到目前為止這些操作都是系統在暗地裡為我們代勞了,所以我們才得以輕輕鬆鬆地撰寫出簡潔明瞭的程式。不過如果我們好奇心大發要看個究竟,C++也是允許的。例如,我們要檢視a的地址值,則可以使用取值運算子&,只要在變數前加上&,便可以得到它的地址:

#include

using namespace std;

int main()
{
  int a;
  a = 3;
 cout << "a = " << a << 'n';  // 第一個與變數相關聯的資訊:變數的值
  cout << "Address of a: " << &a << endl;  // 第二個與變數相關聯的資訊:變數的
 //  地址值
  return 0;
}

下面是我的機子上剛剛輸出的結果:

a = 3
Address of a: 0x241ff5c

 對a = 3的輸出各位應該沒有什麼問題,但Address of a,則不同的機器,甚至同一機器不同的時間都會不一樣,這很好理解:畢竟記憶體佈局總是時時改變的,系統給你分配一塊空間,能用就是啦,不要太挑剔哦。還有,“0x241ff5c”是不是有些怪怪的?這是因為對地址的輸出預設採用16進位制,所以看起來比較玄奧,其實骨子裡不過一整數罷了,你要看不順眼用十進位制輸出當然也行,涉及到輸出流的格式操縱符,這裡就不多說啦(自己查書^_^)。
 現在,只要給對任何一個變數(或者物件,本節之後均統稱變數),我們都可以透過&運算子瞭解它具體的儲存地址。那麼,反過來,假如我們知道一個變數的地址,如何反過來對它進行變數式的操作呢?答案是使用“*”運算子,和“&”運算子一樣,它也是加在一個地址值之前,結果我們就獲得了該地址所對應的變數,這個過程通常稱為解引用(dereference),“*”便是解引用運算子:

#include

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;  // 同樣,用*得回的變數可以和原變數一樣進行常規操作
 cout << "Now, a = " << a << endl;  // 驗證a是不是真的改變
  return 0;
}

  這個搞笑的程式輸出結果如下(同樣,地址值只是可能的版本):

a = 3
Address of a: 0x241ff5c
Get back a: 3
Now, a = 5

  呵呵,之所以說它搞笑,是因為我們先用取址算符對a進行取址,然後又用反引用算符從地址值得回a,實際開發中不大可能出現如此直接的曲線救國。不過這個搞笑程式倒是挺真切地讓我們明白取值運算子&與反引用運算子*是一對截然相反的兄弟,前者由變數得到其地址,而後者則由地址得到其對應變數。如果兩者同時施用,即程式中的*(&a),則我們兜了一個圈最後還是得回了a本身。

1.指 針
1.Pointer

  現在我們又知道了C++中的新一種數值型別:地址值,它與整型、浮點型、雙精度型、字元型等一樣是程式組成運作的重要型別,只是大多數時候都被隱藏在程式語言背後而已。我們還知道,整型、浮點型等數值都對應有整型變數/常量、浮點型變數/常量來對它們進行儲存,那麼地址值是否可以用相應變數/常量進行儲存呢?答案是肯定的。C/C++中用於儲存地址值的變數稱為指標變數/常量,簡稱為指標。這是一個頗為形象的說法:指標儲存的地址值通常是“指”向某個特定的儲存空間,有了指標的引導,我們就可以訪問相應的量;而且指標變數中儲存的地址是可以透過變數賦值改變的,這就使得指標具有相當大的靈活性;此外,動態記憶體分配也離不開指標……這些都是以後的內容,嗯,慢慢來,我們還是先研究一下如何宣告指標變數。
  對於整形變數,我們宣告時使用關鍵字int進行修飾,如
int a;
  宣告瞭一個名為a的整型變數,由於a是由int修飾的,所以a就有int型變數所具有的屬性:比如它支援加法、減法、乘法、整除(而不是除法)、賦值等操作。那麼假如我加上幾個字元,變成:
int a, *p;
  那又是什麼意思呢?按照上面的思路,給你五秒鐘的時間思考:1,2,3,4,5!OK,你大可以照著說,我們宣告瞭兩個整型的東東:a和*p,它們都支援加法、減法、乘法、整除(而不是除法)、賦值等操作……*p與a應當是等同的,它們都理應可以代表一個整型變數。噫,很好,這是我們完全基於自己的理解而推斷出來的。現在我們再來研究一下:既然*p可以代表一個整型變數,那麼去掉*號,p應該意味著什麼?
  還記得前面所說的&與*的神奇關係麼?它們起著截然相反的功能:對變數加&得到其地址,對地址加*得到其對應變數。現在把前面這句話反向表述,就變成:對地址去掉前面的&則得回其對應變數(如&a變回a),對變數去掉*則得回其地址(如前面所說的*(&a)完全相當於變數a,去掉*變為&a則得到a的地址)。
 好,我們知道*p可以代表整型變數,因此去掉*的p,就代表了*p的地址,也就是說,p表示一個地址!
  哈,現在我們總算可以理解為什麼宣告一個指標用的是*號而不是&號或者其它什麼東東了。現在有一個問題:宣告(定義)變數會出現相應的記憶體分配,例如系統看到int a;會為a分配地塊記憶體,如此看來,對於語句int a, *p;似乎同時會為a,*p各分配一塊儲存整數的記憶體。
  噢,比較不幸,這回我們的邏輯沒有發揮理所當然的作用,對於C/C++,int a, *p將使系統為a分配一塊整型值記憶體(毫無疑問),但對於*p,由於*是一個運算子,僅起標示作用,所以系統關注的僅僅是識別符號p,它將為p分配記憶體,而不是*p。嗯,再重複一遍吧:*只是幫助系統瞭解p的地位而出現的,任何宣告(定義),系統最終為其分配記憶體的只是識別符號本身,所以最終p會得到屬於它的記憶體。
  p得到的是一塊怎樣的記憶體呢?既然*p可以代表int型變數,因而剛剛我們分析出p就代表了標示某個int型變數的地址值,把它們連起來說,就是:p得到的記憶體就用於儲存標示某個int型變數的地址值——p是一個儲存整型地址的變數——噢,p正是我們前面所設想的指標變數!呵呵,費盡心力,終於得到你了!


 嗯,一般的C++教程介紹指標的時候都是直接介紹其宣告,而我們基本上是透過邏輯推理,結合第0節的基礎一步一步匯出宣告方法的,這樣的學習我想或許更有助於我們在比較細緻的層次上把握語言實質。本章表述上較詳細,文字較多(本人功夫有限,呵呵),但思路應該還不算太複雜,只有幾個推導鏈,希望初涉此道的學友們有空多讀幾遍,把它讀透讀薄吧。然後……放鬆一下,喝喝茶,聽聽什麼的(其實我也累了^_^ ),下回我們再繼續講指標的宣告原理和一些應用。在下才疏,與各位一樣都只是C++的初學者與愛好者,雖然無知,但喜歡思考一些雜散的問題,如果有什麼錯誤之處還望眾朋友指出,在下感激不盡。如果對本系列有什麼建議與意見,也非常歡迎提出。總之,希望我們能夠共同進步。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-982789/,如需轉載,請註明出處,否則將追究法律責任。

相關文章