常見指標型別入門

Soap發表於2019-05-10

常見指標型別入門

指標是C++中極其重要的概念,如果說某汪在華語樂壇佔據了半壁江山,那麼指標也是支撐C++眾多高階特性的一雙有力的大手。本文簡要說明C++中指標的定義及指標型別的判斷,其中指標型別的判斷往往是初學者最大的障礙,但這也是最需要重點掌握的基礎技能

一、什麼是指標

  1. 指標的定義

    從各種教科書上大同小異的定義來看,指標就是儲存其他變數的地址的變數。要想弄懂這個定義,我們需要先搞清楚兩個概念:變數、變數的地址。

  2. 變數、變數的地址

(1)變數的引入

變數是用於儲存某一數值的“容器”(“容器”這一詞在C++和其他領域中是專業術語,這裡只是用於比喻,不要把這個詞當成對變數的術語,牢記!),我們在計算機中程式設計時,總體來看都是“輸入——計算——輸出”這一套路,所以往往會有大量的資料,如果程式中全部的資料都是使用字面常量的方式來記錄,那麼先不說可實現性,光光是程式的編寫和維護就是一件幾乎不可能的事了,所以從BCPL和B語言之後(準確地說是C語言後),正式有了變數的概念。程式設計師只需要定義好某種型別的變數,就可以儲存對應型別的資料,而不需再再像在無型別語言裡那樣關心資料在記憶體中是如何儲存的了。

(2)邏輯上的記憶體模型

這裡所說的記憶體模型是高階語言程式設計師腦中的記憶體模型,實際上並不存在;記憶體的物理模型不需要高階語言的初學者考慮,故不在此討論
程式執行在記憶體中,資料和指令都載入進記憶體中(然而實際上並不是這麼理想,在之後的文章中我會再介紹作業系統對記憶體的管理),所以為了讓訪問記憶體的裝置能準確地定位到某一處,記憶體的每一處都分配了一個唯一的數值,相當於身份證號,稱作地址。記憶體地址是二進位制的數值。32位機的定址能力就是能在由32位標識的記憶體地址中進行定位,所以記憶體大小被限制在4GB。
地址這一詞與人類社會的門牌號很像,人住在屋子裡,而屋子有一個編號,人看作資料,屋子就是儲存“人”這一型別的變數,而門牌號就是變數的地址。

  1. 指標的作用

    看了以上這麼多的文字,那麼指標的類比概念也很好理解了,不過到此我們需要回答一個問題:為什麼需要指標?通過指標來操作變數還是一個二級的操作,這不會顯得很麻煩嗎?

是的,通過指標我們確實比直接操作變數多做了工作,但恰恰就是指標,也給了程式設計師直接操作記憶體的機會,因為直接操作記憶體比操作變數在時間上要快上很多,這也是為什麼C++程式執行的效率高的原因之一。

  1. 指標的型別

    指標(pointer)的全名其實是指標變數,也就是說指標也是變數。既然是變數,那麼很自然的也就有型別這一問題。C++是強型別的靜態語言,某一種型別的變數就只能儲存其對應型別的資料,指標也不例外。通常,當某一型別指標儲存著其對應型別的地址時,我們說“這個指標指向了那個變數”。


二、指標的型別

正文從這開始。
把指標考慮成變數,所以我們從最基本的變數(以整型為例)的定義開始分析。

  • int p;

p是一個整型變數。

  • int *p;

p是一個指向整型變數的指標。

  • int p[SIZE];

p是一個陣列,陣列中有SIZE個元素,每個元素都是整型。

  • int *p[SIZE];

從這裡開始變得有趣了
對於這種“複雜”型別的定義,有一種方法是從中間開始讀,根據運算子優先順序向兩邊擴充套件。
p是一個陣列(先與定義陣列的下標運算子結合,因為下標運算子[]的優先順序高於解引用運算子*),陣列中有SIZE個元素,然後與*結合,陣列中的元素都是指標,最後看int,即指標指向int。所以,把以上的分析過程連起來,就是,p是一個由SIZE個指向整型的指標構成的陣列。

  • int (*p)[SIZE];

這裡我們會開始簡單看到()運算子的其中一個作用
首先,從p開始尋找優先順序比*高的運算子——是的,()在這裡的意義和數學中的小括號是一樣的,都是用於強行改變運算優先順序的運算子。所以,因為有(),p先與*結合,p是一個指標,然後再與[]結合,p這個指標指向一個有SIZE個元素的陣列,最後,看int,即,p是一個指向由SIZE個整型元素構成的陣列的指標。

  • int p(int);

這裡就是()的另一個作用
這裡的()是函式呼叫運算子,()內寫清了引數列表,所以,p是一個函式,函式有一個整型變數的引數,函式返回值為整型。

  • int p(int, int *, int);

分析過程同上一點,直接給出結果。
p是一個函式,有三個引數,依次為整型、指向整型的指標和整型,返回值為整型。

  • int **p;

C++中沒有**這種的運算子,所以這種寫法是等同於int *(*p);的。
其表示,p是一個指標,指向了一個指標變數,且被p指向的那個指標指向整型。
這種指標稱作二級指標,類似的,還有多級指標,但在實際編寫程式時,二級指標的使用已經比較少了,多級指標更是幾乎(不代表沒有用處)不會使用,所以初學者不必考慮多級指標。

  • int (*p)(int *, int);

p是一個指標,指向了一個函式,這個函式的引數列表為(int *, int),返回值為int
這裡的p,我們就稱之為函式指標,因為它指向了函式。函式指標的概念需要實際練習幾次才能初步掌握。

  • void (*p[SIZE])(int *, int);

這種複雜程度的型別定義是程式中經常見到的,所以一點一點地來分析。
p先與[]結合,說明p是一個陣列;再與左側的*結合,說明陣列裡的元素都是指標;之後與右側的函式呼叫運算子()結合,那些指標指向的函式的引數依次為(int *, int),返回值型別為void。所以,再整理一下剛剛的分析:
p是一個由SIZE個元素構成的陣列,每個元素都是函式指標,其指向的函式的引數依次為指向整型的指標、整型,返回值型別為void
這種型別要求重點掌握。

  • int *(*p(int))[SIZE];

這種型別的可以先跳過,在確認了掌握了上面講述的所有定義後再來嘗試理解。
從p開始分析,先與()結合,說明p是一個函式;然後進入到引數列表中,發現這個函式的引數只一個int;再看p左側的*,說明這個函式的返回值是一個指標;之後到外層(這裡外層的()是為了強制改變優先順序),先與[]結合,說明函式返回的指標指向的是一個陣列;再與左面的*結合,說明指向的陣列中的元素都是指標;最後與int結合,說明陣列中的元素指向整型。
所以,p是一個引數為一個int、且返回一個指向由SIZE個int *構成的陣列的的指標的函式。

    這裡思考,如果去掉最外層的`()`,即改為如下:
    `int **p(int)[SIZE];`
    會是什麼結果?為什麼?歡迎在之後的討論區發表意見。

三、結語

常用的指標型別(或者直接說是型別)定義大多都是如上型別的變種,當然會有更復雜的,不過再複雜的都可以用上述的方法來進行分析。

相關文章