C 語言返璞歸真: 指針篇(4)

續 > C 語言返璞歸真: 指針篇(3)


前言

前面一篇說到了複雜指針的第一種指針——二重指針, 本篇博文接着來聊聊更加複雜的指針。


指針數組和數組指針


首先需要明確的是指針數組是數組,而數組指針是指針。 之所以把它們放在一起, 是因爲它們在形式上非常相似, 但實際上它們的用法和意義完全不同。 譬如定義一個指針數組: int *a[10], 表示一個有 10 個 int* 類型指針的數組; 定義一個數組指針: int (*pa)[10], 可以表示一個指向 int 類型、長度爲 10 的一維數組的指針。 

注意: 上面提到的都是我們比較常見的指針數組和數組指針, 實際上還有許多指針數組和數組指針, 譬如: int **a[10]、 int (*pa)[10][10] 等等, 但是這些出現的頻率實在太低, 所以一般說到指針數組和數組指針時都特指上述兩類比較常見的。

指針數組和數組指針在內存中的表示

1.指針數組


從圖上看到內存中開闢了兩塊空間, 一塊用於存儲二重指針 pa, 一塊空間用於存儲指針數組。 並且二重指針 pa 指向了指針數組名 a。指針數組本質上和一維數組沒有區別, 唯一區別的是它們存儲的東西不同, 指針數組存儲的元素是指針。 圖中, 我特意在指針數組的下面標明瞭數組元素的假定地址, 這裏使用的模型是 LP64, 所以該指針數組裏面的元素都是 8 byte 大小的指針。 注意指針的大小與指針類型無關。 


在指針篇(3)一文中, 我們提到了二重指針可以指向一重指針。 那麼爲什麼二重指針可以指向指針數組名呢? 我們知道一維數組的數組名在作右值時可以隱式轉換爲指針類型, 等效於數組首元素首地址。 同理指針數組名在作右值時也可以隱式轉換爲指針類型, 等效於數組首元素首地址。所以「pa = a 」等效於「pa = &a[0] 」。 指針數組的元素都是指針, 所以pa = a 是成立的。

2. 數組指針

顧名思義, 數組指針是指針, 而且是專門用於指向數組的指針。 它可以指向一維數組、 二維數組甚至是三維數組。 但指向一維數組和三維數組時意義不大。 下面說明一下數組指針指向二維數組的內存分佈情況:

仔細觀察你會發現, 實際上二維數組和一維數組在存儲方式上並沒有不同。 上面的二維數組 a[2][10] 與 a[20] 在內存中的存儲方式完全相同,是線性存儲, 而不是二維方式存儲。所以其實一維數組完全可以替代二維乃至於更高維的數組。 不過對於編程者來說, 二維數組在很多時候更加容易處理, 也可以增加程序的可讀性。 

再觀察上面的圖, 會發現二維數組的 20 個元素被分爲兩組, 一組是 a[0], 另外一組是 a[1]。 因爲二維數組的這種特性類似於數學中的行列式(矩陣), 所以經常二維數組的第一維稱爲數組的行, 第二維稱爲數組的列。 這種稱法更有助於理解二維數組。 在定義時需要初始化一個二維數組時, 也建議寫成行列式(矩陣)的形式, 比如:
這種分行初始化的寫法更加不容易出錯, 也利於編程者閱讀代碼。 

既然二維數組和一維數組在存儲方式沒有什麼區別, 那麼一重指針也完全可以指向二維數組, 只要指向二維數組的首元素即可。 代碼如下:
int a[2][10] = 
{
	{ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,},
	{10, 11, 12, 13, 14, 15, 16, 17, 18, 19,},
};

int *pa = NULL;
pa = &a[0][0];

換作數組指針呢? 數組指針指向二維數組的第一維, 同時它限定了指向的數組的第二維大小。 譬如上述的 int (*pa)[10]只能指向第二維大小是 10 的二維數組。 代碼如下:
int a[2][10] = 
{
	{ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,},
	{10, 11, 12, 13, 14, 15, 16, 17, 18, 19,},
};

int (*pa)[10] = NULL;
pa = a;

區分指針數組和數組指針的技巧

很多人弄不清楚指針數組和數組指針這兩者到底哪個是指針, 哪個是數組。 所以我們首先說說如何在字面上區分這兩者。 其實在中文表達當中, 通常會把名詞(主語)放在後面, 修飾詞(定語)放在前面。 所以指針數組是數組在後, 指針在前, 重點表示的是數組。 反之, 數組指針它重點要表達的是一個指針。


那麼又如何區分具體的 C 語言代碼呢?這裏涉及到一個運算符優先級的問題。 C 語言中有一個運算符優先級表, 大多數情況下不需要用到這個優先級表。 不過作爲 C 語言的一個知識點, 也有必要會看懂優先級表和記住幾個比較重要運算符的優先級。 關於這一點會在下一篇博文中講解。 這裏我們只涉及到「*」(解引用運算符)、「()」、「[]」這三個運算符。


在優先級表中, 上述的幾個運算符的優先級順序爲:「[]」>  「()」 > 「*」。 所以當「*」和「[]」同時作用於 a 的時候, a 先與「[]」結合構成一個數組, 然後再與「*」結合, 結果構成一個指針數組, 其實指針數組也可以寫成 int *(a[10]),不過這裏的括號沒有起到任何作用, 因爲「[]」的優先級高於「*」; 同理數組指針中「()」就起到了一個隔離的作用, 這時候「[]」就不直接作用於 a, 所以 a 要先與「*」結合構成指針,然後再與「[]」結合。


2017 年 7 月 13 日

Kilento


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章