一勞永逸:關於C/C++中指針、數組與函數複合定義形式的直觀解釋

 [標題] char *(*(**(*(*(*x[5])(int,float))[][12])(double))(short,long))[][173] ?! 今天又捧起久違的K&R C拜讀了一遍。其實有點東西在6年前就想寫,藉着今天這個機會,終於把它寫出來了。 初看一眼標題中的變量定義感覺是不是很抓狂?:)一直以來,C語言中關於指針、數據和函數的複合定義都是一個難點,其實,理解它也是有規律可循的。然而,即便是國內在講解指針方面久負盛名的“譚本”也沒有將這一規律說清楚,K&R C雖然提到了一點,卻始終沒有捅破這層窗戶紙,也許是K&R覺得以“直觀方式”解釋太陽春白雪了點吧:)在Blog上面說說這種不值一提的dd倒正合適。 其實,理解C語言中複合定義的關鍵在於對變量聲明語句中各修飾符結合律的把握,我們可以將它們的結合規律簡單歸納如下: (1) 函數修飾符 ( ) 從左至右 (2) 數組修飾符 [ ] 從左至右 (3) 指針修飾符 * 從右至左 其中,(1)與(2)的修飾優先級是相同的,而(3)比前兩者的優先級都低,而且是寫在左邊的。下面我們給出3個直觀的例子來說明如何藉助結合律來理解複合變量聲明,爲了簡單點,函數修飾符一律使用無形參的簽名形式。 示例1. char (*(*x[3])())[5] 這是什麼意思?別急,跟着走一遭咱就知道是什麼了。根據結合律,我們可以依次寫出與x結合的修飾符: x -1-> [3] -2-> * -3-> () -4-> * -5-> [5] -6-> char 然後我們再來從左至右地對上述過程進行解釋: 1說明:x是一個一維數組,數組中元素個數爲3 2說明:上述數組中每一個元素都是一個指針 3說明:這些指針都是函數的指針,該函數的簽名爲( ) 4說明:上面的函數所返回的值是一個指針 5說明:上面的指針所指向的是一個一維數組,其元素個數爲5 6說明:上面的數組中的每一個元素均是一個字符 不知大家在上面的規範化步驟描述中看出端倪來了沒有?:)這個聲明的含義是:x是一個由3個指向函數A的指針所組成的一維數組,函數A返回指向一個元素個數爲5的字符數組的指針。其實,以結合律來解析複合聲明的方式是一種“由近及遠”的方式:首先嚐試着去說清楚離變量“近”的修飾符的含義,然後再對“遠處”的修飾符進行依次說明,從抽象到具體,從頂到底,層層細化。 實際上,我比較反感這種一步到位的複合方式,它不僅把變量定義和類型聲明混爲一談,而且也不能直觀地體現出類型的含義,更糟糕的是,這不符合典型的“積木化”的程序思維,我更傾向於採用typedef,以一種“由遠及近”的方式來逐步定義變量的形態,即先定義若干基本類型,然後再在其基礎上將其擴充成複雜類型,最後利用複雜類型定義變量。例如,上述的例子,如果要我來定義,我覺得如此定義比較恰當: typedef char ArrayOfChar[5]; typedef ArrayOfChar* PointerOfArrayOfChar; typedef PointerOfArrayOfChar (*PointerOfFunc)() typedef PointerOfFunc ArrayOfPointerOfFunc[3] ArrayOfPointerOfFunc pfa; 這種“堆積木”的方式實際上和那個複合聲明是等價的,其看似繁冗,但對於程序員而言卻很直觀,所以平心而論,我比較推薦這種積木化聲明方式,而不推薦以複合聲明直接一步到位。 示例2. char (**x[3])()[5] 根據結合律,將上述聲明改寫如下: x -1-> [3] -2-> * -3-> * -4-> () -5-> [5] -6-> char 1說明:x是一個數組,這個數組包括3個元素 2說明:每個元素均爲一個指針 3說明:上面的指針又指向另一個指針 4說明:上面的第二個指針是一個函數的指針 5說明:上面的函數返回的是一個數組,這個數組包括5個元素?? (錯誤!) 從上述推導過程可以發現,當我們到達第5步時,其語義提到了“一個函數返回了一個數組”,這在C語言中實際上是錯誤的定義,即,( )與[ ]相鄰是非法的,因此,編譯器將拒絕接受這一關於x變量的聲明。同樣的,在推導過程中[ ]與( )相鄰也是不合法的,什麼叫做“一個數組,這個數組裏面的每一個元素都是一個函數(而不是一個指針)”?在這種情況下,編譯器也會100%報錯。 示例3. char p[5][7]、char (*q)[7]、char *r[5] 和 char **s 不知p、q、r、s這四個變量類型是否兼容?根據結合律,有: p -> [5] -> ([7] -> char) const q -> * -> ([7] -> char) r -> [5] -> { * -> char} const s -> * -> { * -> char} 不難發現,無需經過類型強制轉換即可將p賦值給q、將r賦給s,而其他的賦值方式均是錯誤的。爲什麼?首先,p和r是兩個數組,不是指針,因此不能修改其值;其次,不妨讓我們來對p與q(或者r與s)在其括號內的類型部分分別進行sizeof運算,可以發現,二者的結果是一樣的,即:p、q(或者r、s)指針變量具備一致的增量尋址行爲,所以二者才兼容。 看完了上述解釋,想必最唬人的指針複合定義恐怕也難不倒你了。試試下面的挑戰如何? 1. 解釋一下x變量的含義:char *(*(**(*(*(*x[5])(int,float))[][12])(double))(short,long))[][173]; 2. 在32位環境下,假設void* p=(void *)(x+1),x=0x1234;則p的16進制值爲多少?sizeof(x)等於多少? From:http://neoragex2002.cnblogs.com/archive/2005/11/06/269974.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章