1. 程式人生 > >c語言複雜宣告的解析

c語言複雜宣告的解析

摘錄的別人的:

C語言所有複雜的指標宣告,都是由各種宣告巢狀構成的。如何解讀複雜指標宣告呢?右左法則是一個既著名又常用的方法。不過,右左法則其實並不是C標準裡面的內容,它是從C標準的宣告規定中歸納出來的方法。C標準的宣告規則,是用來解決如何建立宣告的,而右左法則是用來解決如何辯識一個宣告的,兩者可以說是相反的。右左法則的英文原文是這樣說的:

The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed. 


這段英文的翻譯如下:

右左法則:首先從最裡面的圓括號看起,然後往右看,再往左看。每當遇到圓括號時,就應該掉轉閱讀方向。一旦解析完圓括號裡面所有的東西,就跳出圓括號。重複這個過程直到整個宣告解析完畢。

        筆者要對這個法則進行一個小小的修正,應該是從未定義的識別符號開始閱讀,而不是從括號讀起,之所以是未定義的識別符號,是因為一個聲明裡面可能有多個識別符號,但未定義的識別符號只會有一個。

        現在通過一些例子來討論右左法則的應用,先從最簡單的開始,逐步加深:

int (*func)(int *p);

首先找到那個未定義的識別符號,就是func,它的外面有一對圓括號,而且左邊是一個*號,這說明func是一個指標,然後跳出這個圓括號,先看右邊,也是一個圓括號,這說明(*func)是一個函式,而func是一個指向這類函式的指標,就是一個函式指標,這類函式具有int*型別的形參,返回值型別是int。

int (*func)(int *p, int (*f)(int*));

func被一對括號包含,且左邊有一個*號,說明func是一個指標,跳出括號,右邊也有個括號,那麼func是一個指向函式的指標,這類函式具有int *和int (*)(int*)這樣的形參,返回值為int型別。再來看一看func的形參int (*f)(int*),類似前面的解釋,f也是一個函式指標,指向的函式具有int*型別的形參,返回值為int。

int (*func[5])(int *p);

func右邊是一個[]運算子,說明func是一個具有5個元素的陣列,func的左邊有一個*,說明func的元素是指標,要注意這裡的*不是修飾func的,而是修飾func[5]的,原因是[]運算子優先順序比*高,func先跟[]結合,因此*修飾的是func[5]。跳出這個括號,看右邊,也是一對圓括號,說明func陣列的元素是函式型別的指標,它所指向的函式具有int*型別的形參,返回值型別為int。


int (*(*func)[5])(int *p);

func被一個圓括號包含,左邊又有一個*,那麼func是一個指標,跳出括號,右邊是一個[]運算子號,說明func是一個指向陣列的指標,現在往左看,左邊有一個*號,說明這個陣列的元素是指標,再跳出括號,右邊又有一個括號,說明這個陣列的元素是指向函式的指標。總結一下,就是:func是一個指向陣列的指標,這個陣列的元素是函式指標,這些指標指向具有int*形參,返回值為int型別的函式。

int (*(*func)(int *p))[5];

func是一個函式指標,這類函式具有int*型別的形參,返回值是指向陣列的指標,所指向的陣列的元素是具有5個int元素的陣列。

要注意有些複雜指標宣告是非法的,例如:

int func(void) [5];

func是一個返回值為具有5個int元素的陣列的函式。但C語言的函式返回值不能為陣列,這是因為如果允許函式返回值為陣列,那麼接收這個陣列的內容的東西,也必須是一個數組,但C語言的陣列名是一個右值,它不能作為左值來接收另一個數組,因此函式返回值不能為陣列。

int func[5](void);

func是一個具有5個元素的陣列,這個陣列的元素都是函式。這也是非法的,因為陣列的元素除了型別必須一樣外,每個元素所佔用的記憶體空間也必須相同,顯然函式是無法達到這個要求的,即使函式的型別一樣,但函式所佔用的空間通常是不相同的。

作為練習,下面列幾個複雜指標宣告給讀者自己來解析,答案放在第十章裡。

int (*(*func)[5][6])[7][8];

int (*(*(*func)(int *))[5])(int *);

int (*(*func[7][8][9])(int*))[5];

        實際當中,需要宣告一個複雜指標時,如果把整個宣告寫成上面所示的形式,對程式可讀性是一大損害。應該用typedef來對宣告逐層分解,增強可讀性,例如對於宣告:

int (*(*func)(int *p))[5];

可以這樣分解:

typedef  int (*PARA)[5];
typedef PARA (*func)(int *);

這樣就容易看得多了。

詳見:http://blog.csdn.net/wangweixaut061/article/details/6549768

C語言所有複雜的指標宣告,都是由各種宣告巢狀構成的。如何解讀複雜指標宣告呢?右左法則是一個既著名又常用的方法。不過,右左法則其實並不是C標準裡面的內容,它是從C標準的宣告規定中歸納出來的方法。C標準的宣告規則,是用來解決如何建立宣告的,而右左法則是用來解決如何辯識一個宣告的,兩者可以說是相反的。右左法則的英文原文是這樣說的:

The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.


這段英文的翻譯如下:

右左法則:首先從最裡面的圓括號看起,然後往右看,再往左看。每當遇到圓括號時,就應該掉轉閱讀方向。一旦解析完圓括號裡面所有的東西,就跳出圓括號。重複這個過程直到整個宣告解析完畢。

        筆者要對這個法則進行一個小小的修正,應該是從未定義的識別符號開始閱讀,而不是從括號讀起,之所以是未定義的識別符號,是因為一個聲明裡面可能有多個識別符號,但未定義的識別符號只會有一個。

        現在通過一些例子來討論右左法則的應用,先從最簡單的開始,逐步加深:

int (*func)(int *p);

首先找到那個未定義的識別符號,就是func,它的外面有一對圓括號,而且左邊是一個*號,這說明func是一個指標,然後跳出這個圓括號,先看右邊,也是一個圓括號,這說明(*func)是一個函式,而func是一個指向這類函式的指標,就是一個函式指標,這類函式具有int*型別的形參,返回值型別是int。

int (*func)(int *p, int (*f)(int*));

func被一對括號包含,且左邊有一個*號,說明func是一個指標,跳出括號,右邊也有個括號,那麼func是一個指向函式的指標,這類函式具有int *和int (*)(int*)這樣的形參,返回值為int型別。再來看一看func的形參int (*f)(int*),類似前面的解釋,f也是一個函式指標,指向的函式具有int*型別的形參,返回值為int。

int (*func[5])(int *p);

func右邊是一個[]運算子,說明func是一個具有5個元素的陣列,func的左邊有一個*,說明func的元素是指標,要注意這裡的*不是修飾func的,而是修飾func[5]的,原因是[]運算子優先順序比*高,func先跟[]結合,因此*修飾的是func[5]。跳出這個括號,看右邊,也是一對圓括號,說明func陣列的元素是函式型別的指標,它所指向的函式具有int*型別的形參,返回值型別為int。


int (*(*func)[5])(int *p);

func被一個圓括號包含,左邊又有一個*,那麼func是一個指標,跳出括號,右邊是一個[]運算子號,說明func是一個指向陣列的指標,現在往左看,左邊有一個*號,說明這個陣列的元素是指標,再跳出括號,右邊又有一個括號,說明這個陣列的元素是指向函式的指標。總結一下,就是:func是一個指向陣列的指標,這個陣列的元素是函式指標,這些指標指向具有int*形參,返回值為int型別的函式。

int (*(*func)(int *p))[5];

func是一個函式指標,這類函式具有int*型別的形參,返回值是指向陣列的指標,所指向的陣列的元素是具有5個int元素的陣列。

要注意有些複雜指標宣告是非法的,例如:

int func(void) [5];

func是一個返回值為具有5個int元素的陣列的函式。但C語言的函式返回值不能為陣列,這是因為如果允許函式返回值為陣列,那麼接收這個陣列的內容的東西,也必須是一個數組,但C語言的陣列名是一個右值,它不能作為左值來接收另一個數組,因此函式返回值不能為陣列。

int func[5](void);

func是一個具有5個元素的陣列,這個陣列的元素都是函式。這也是非法的,因為陣列的元素除了型別必須一樣外,每個元素所佔用的記憶體空間也必須相同,顯然函式是無法達到這個要求的,即使函式的型別一樣,但函式所佔用的空間通常是不相同的。

作為練習,下面列幾個複雜指標宣告給讀者自己來解析,答案放在第十章裡。

int (*(*func)[5][6])[7][8];

int (*(*(*func)(int *))[5])(int *);

int (*(*func[7][8][9])(int*))[5];

        實際當中,需要宣告一個複雜指標時,如果把整個宣告寫成上面所示的形式,對程式可讀性是一大損害。應該用typedef來對宣告逐層分解,增強可讀性,例如對於宣告:

int (*(*func)(int *p))[5];

可以這樣分解:

typedef  int (*PARA)[5];
typedef PARA (*func)(int *);

這樣就容易看得多了。

c言語的複雜型別宣告 

在c言語的複雜型別宣告中,我們用到3個修飾符:*、()、[]。含義如下: 
* 暗示宣告一個指標 
() 暗示宣告一個函式 
[] 暗示宣告一個數組 
c語言允許我們一次使用多個上面所說明的修飾符來申明一個變數,這樣我們可以構造出多種多樣的類別來。 
大家先看下面的例子: 
int board[8][8]; //二維陣列(一個8個元素的陣列,元素內容還是陣列) 
int **p; //指向指標的指標(一個指標,內容還是一個指標) 
int *array[10]; //10個元素的陣列,元素內容是指標。 
int (*p)[10]; //一個指標,指向含有10個元素的陣列。 
int *oof[3][4]; //3個元素的陣列,存放的內容是指標,指標分別指向4個元素的陣列 
int (*oof)[3][4]; //一個指標指向3*4 的陣列。 

看到這裡,可能有人會想,你在說些什麼哦,怎麼看不明白,沒有關係,看完下面的3條法則,你在來看上面的內容就很清晰了。 
1:離名字越近的修飾符優先順序越高 
2:[],()優先順序比*高 
3:用()括起來的表示式的優先順序最高。 

我用這些法則來解釋上面提到的例子,請看 
int *foo[3][4] 

名字:foo 

*、[3] 離名字一樣近,而[4]離的比他們遠,所以*、[3]的優先順序比[4]高。(法則1) 
而 [] 的優先順序比*高 (法則2) 
優先順序關係如下: 
[3] > * > [4] 

所以int *foo[3][4] 是一個3個元素的陣列(第一個修飾符[3]),存放內容是指標(第二個修飾符號*),指標分別指向4個元素的陣列(第三個修飾符[4]) 

int (*foo)[3][4] 
名字:foo 
優先順序關係如下:(括號括起來的表示式優先順序最高) 
* > [3] > [4] 
所以一個指標指向3*4 的陣列。 
下面的內容留給大家去思考: 
int *func()[3]; 
int *f[3](); 
alongzhang 
是這樣的:

儲存類別    型別限定詞    型別    識別符號

這種說明會給人們一種暗示:C語言的宣告是靜止的、死板的,什麼宣告都能夠以這個為基礎,往上一套就OK了。事實真的如此嗎?說句心裡話,筆者也祈禱事實真的如此,這樣世界就簡單多了、清靜多了。但別忘了,這個世界總是讓人事與願違的。實際上,C的宣告的組織形式是以巢狀為基礎的,是用巢狀宣告組織起來的,並非象上面所述那麼死板,儲存類說明符一定得放在限定詞前面嗎?型別說明符一定要緊貼識別符號嗎?不!C標準從來沒有這樣說過!下面來看一看C89對宣告的形式是如何規定的:

宣告:

宣告說明符     初始化宣告符表opt    [opt的意思是option,可選]

其中宣告說明符由以下三項構成:

宣告說明符:

儲存類說明符    宣告說明符opt
型別說明符      宣告說明符opt
型別限定符      宣告說明符opt

在這裡,一個宣告說明符可以包含另一個宣告說明符,這就是宣告的巢狀,這種巢狀貫穿於整個宣告之中,今天我們看來一個非常簡單的宣告,其實就是由多個宣告巢狀組成的,例如:

static const int i=10, j=20, k=30;

變數i前面就是宣告說明符部分,有三個宣告說明符:static const int,static是一個儲存類說明符,它屬於這種形式:

static 宣告說明符

static後面的宣告說明符就是const int,const是一個型別限定符,這也是個巢狀,它是由

const 宣告說明符

組成,最後的int是一個型別說明符,到這裡已經沒有嵌套了,int就是最底的一層。對於儲存類說明符、型別說明符和型別限定符的排列順序,C標準並沒有規定其順序,誰巢狀誰都可以。換言之,上面的宣告可以寫成:

int static const i=10, j=20, k=30;或者const int static i=10, j=20, k=30;

這無所謂,跟原宣告是一樣的。再舉一個有趣的例子:

const int *p;與int const *p;

有些人會對後面一種形式感到困惑,因為他一直以來學習的都是那種死板的形式,因此他無法理解為什麼那個const可以放在int的後面。實際上對於標準來說,這是再正常不過的行為了。

上面舉的例子是變數的宣告,函式的宣告也同樣道理,例如:

static const int func(void);
......

int main(void)
{
          int static const (*p)(void);
          p=func;
          .........
          return 0;
}

const int static func(void)
{
          .......
          return 0;
}

func的函式原型宣告、函式定義跟main內的函式指標p的宣告是一樣的。但是,筆者並非鼓勵大家把宣告說明符寫得亂七八糟,作為一個良好的風格,應該按照已經習慣約定的方式排列說明符,但懂得其中的原理非常重要。

宣告static const int i=10, j=20, k=30;的int後面的部分就是初始化宣告符表,這比較容易理解,這個符表實際上也是巢狀的:

初始化宣告符表:

初始化宣告符
初始化宣告符表, 初始化宣告符


初始化宣告符:

宣告符
宣告符=初值


          宣告符是初始化宣告符的主體,現在來討論一下宣告符是如何規定的:

宣告符:

指標opt    直接宣告符

這裡寫的指標opt指的是那個指標宣告符*,要注意的是,*屬於宣告符,而不是宣告說明符的一部分。

指標opt又包含:

指標:

* 型別限定符表opt
* 型別限定符表opt 指標

在這裡有一個常見的問題,就是const int *p;與int * const p的區別,第一個宣告的const屬於宣告說明符,它跟int一起,是用來說明*p這個宣告符的,因此const修飾的是p所指向的那個物件,這個物件是const的。而第二個宣告的const是宣告符的一部分,它修飾的物件是p本身,因此p是const的。

          上面規定的第二條值得注意,這條規定產生了一種指標與const的複雜形式,例如:

const int * const *** const ** const p;(是不是有種想衝向廁所的衝動?)這是一種複雜的宣告巢狀,如何解讀這種宣告?其實只要掌握了它的規律,無論它有多少個const、多少個*都不難解讀的,這個內容我將在第九章進行解釋。

          剩下的就是直接宣告符和型別限定詞表的內容:

直接宣告符:

識別符號
(宣告符)
直接宣告符[常量表達式opt]
直接宣告符(形式引數型別表)
直接宣告符(識別符號表opt)


型別限定符表:

型別限定符
型別限定符表 型別限定符


          這一章的最後一個內容,是討論一下typedef,typedef用來宣告一個別名,typedef後面的語法,是一個宣告。本來筆者以為這裡不會產生什麼誤解的,但結果卻出乎意料,產生誤解的人不在少數。罪魁禍首又是那些害人的教材。在這些教材中介紹typedef的時候通常會寫出如下形式:

typedef int PARA;

這種形式跟#define int PARA幾乎一樣,如前面幾章所述,這些教材的宗旨是由淺入深,但實際做出來的行為卻是以偏蓋全。的確,這種形式在所有形式中是最簡單的,但卻沒有對typedef進一步解釋,使得不少人用#define的思維來看待typedef,把int與PARA分開來看,int是一部分,PARA是另一部分,但實際上根本就不是這麼一回事。int與PARA是一個整體!就象int i:宣告一樣是一個整體宣告,只不過int i定義了一個變數,而typedef定義了一個別名。這些人由於持有這種錯誤的觀念,就會無法理解如下一些宣告:

typedef int a[10];
typedef void (*p)(void);

他們會以為a[10]是int的別名,(*p)(void)是void的別名,但這樣的別名看起來又似乎不是合法的名字,於是陷入困惑之中。實際上,上面的語句把a宣告為具有10個int元素的陣列的類型別名,p是一種函式指標的類型別名。

雖然在功能上,typedef可以看作一個跟int PARA分離的動作,但語法上typedef屬於儲存類宣告說明符,因此嚴格來說,typedef int PARA整個是一個完整的宣告。

複雜的C宣告一般被認為不是很好的程式設計習慣,當然也就不推薦使用。但是在讀很多前輩遺留的程式碼時,又不得不面對這一問題。知道總比不知道好,我們還是來看看分析複雜C語言宣告的規則吧,用例子分析最直觀。

一、“right-left”規則
看過《C專家程式設計》中的分析規則,用起來並不是很舒服,遂在網上尋找,發現還有一個著名的“right-left”規則。規則經翻譯總結後如下:

“right-left”規則:
0. 規則中符號
*    讀作 “指向...的指標”  
[]     讀作 “...的陣列”  
()    讀作 “返回...的函式”
1. 起始點
找到宣告中的識別符號(Identifier),它就是你分析的起始點,讀作:“$(Identifier)是...”;

2. 右邊
看你的識別符號右邊
a) 如果發現“()”,你將知道這是一個函式宣告,這時你可以說“$(Identifier)是返回...的函式”;
b) 如果發現“[]”,你將知道這是一個數組宣告,這時你可以說“$(Identifier)是...的陣列”;
c) 繼續向右,直到遇到右邊宣告結束或者遇到“)”,繼續下面。
3. 左邊
看你的識別符號左邊
a) 如果碰到的不是我們在0.中定義的符號,則直接說出它;否則按照0.中定義的符號含義說出。繼續向左,直到遇到左邊宣告結束或“(”。
4. 重複2和3的步驟,直到宣告分析完畢。

二、例子詳解
我們從簡單到複雜,循序漸進。
[Example 1] int *p[];
1) 找到識別符號:p,讀作:“p是...”;
2) 向右看:發現一“[]”,然後遇到右邊宣告結尾,讀作:“p是...的陣列”;
3) 向左看:發現一“*”, 讀作:“p是指向...的指標的陣列”;
4) 繼續向左看:沒有發現0.中定義的符號,則分析結束,讀作:“p是指向int型別的指標的陣列”。

[Example 2] int *(*func())();
1) 找到識別符號:func,讀作:“func是...”;
2) 向右看:發現一“()”,然後遇到“)”,讀作:“func是返回...的函式”;
3) 向左看:發現一“*”,然後遇到“(”,讀作:“func是返回指向...的指標的函式”;
4) 向右看:發現一“()”,然後右邊宣告結束,讀作:“func是返回指向返回...的函式的指標的函式”;
5) 向左看:發現一“*”,讀作:“func是返回指向返回指向...的指標的函式的指標的函式”;
6) 向左看:沒有發現.中定義的符號,則分析結束,讀作:“func是返回指向返回指向int型別的指標的函式的指標的函式”。

三、常見不合法的宣告符號組合
包括:
[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array

複雜指標的宣告 2007-10-13 16:20
參考了《C和指標》與《C程式設計語言》寫成了下面的文件,主要是自己看著方便點:)

        首先看幾個簡單的例子
        int f; //一個整型變數
        int *f; //一個指向整型的指標
        不過,請看第 2 個宣告是如何工作的:它把表示式 *f 宣告為一個整數。根據這個事實,你肯定能推斷出 f 是個指向整型的指標。C 宣告的這種解釋方法可以通過下面的宣告得到驗證。
        int* f,g;
        它並沒有宣告兩個指標。儘管它們之間存在空白,但星號是作用於 f 的,只有 f 是一個指標。 g 只是一個普通的整型變數。
    
        下面的例子,你曾見過:
        int f();
        它把 f 宣告為一個函式,它的返回值是一個整數。舊式風格的宣告對函式的引數不提供任何資訊。它只聲明瞭 f 的返回型別。現在我將使用這種舊式風格,這樣例子看上去簡單一些,後面我將再回到完整的原型形式。

        下面是一個例子:
        int *f();     // f 是一個函式,它返回一個指向 int 型別的指標
        要想推斷出它的含義,必須確定表示式 *f() 是如何進行求值的。首先執行的是函式呼叫操作符(), 因為它的優先順序高於間接訪問操作符。
    
        接下來的一個宣告更為有趣:
        int (*f)();     // f 是一個指向函式的指標,該函式返回一個 int 型別的物件
        確定括號的含義是分析這個宣告的一個重要步驟。第 2 對兒括號是函式呼叫操作符,但第 1 對兒括號只是起到聚組的作用。它迫使間接訪問在函式呼叫之前進行,使 f 成為一個函式指標,它所指向的函式返回一個整型值。
    
        “函式指標”? 是的,程式中每個函式都位於記憶體中的某個位置,所以存在指向那個位置的指標是完全可能的。
    
        現在下面的這個宣告應該是比較容易懂了:
        int *(*f)(); 
        它和前一個宣告基本相同, f 也是一個函式指標,它所指向的函式返回一個指向 int 型別的指標。必須對其進行間接訪問操作才能得到一個整型值。
    
        現在讓我們把陣列也考慮進去。
        int f[];
        這個宣告表示 f 是個整型陣列,陣列的長度暫時忽略,因為我們現在關心的是它的型別,而不是它的長度
             【注】如果它們的連結屬性是external或者是作用函式的引數,即使它們宣告時未註明長度,也仍然是合法的。
    
        下面的宣告又如何呢?
        int *f[];
        這個宣告又出現了兩個操作符。下標的優先順序更高,所以 f 是一個數組,它的元素型別是指向整型的指標
    
        下面的這個例子隱藏著一個圈套。不管怎樣,讓我們先推斷它的含義。
        int f()[];
        f 是一個函式,返回一個整型陣列。這裡的圈套在於這個宣告是非法的————函式只能返回標量值,不能返回陣列。
    
        還有一個例子,頗費思量:
        int f[]();
        現在 f 似乎是一個數組,它的元素是返回值為整型的函式。但是,這個宣告也是非法的,因為陣列元素必須具有相同的長度,但不同的函式顯然可能具有不同的長度。

        但是,下面的這個宣告是合法的:
        int (*f[])();
        你必須找到所以的操作符,然後按照正確的次序執行它們。括號內的表示式 *f 首先進行求值,所以 f 是一個元素為某種型別的指標的陣列。 表示式 () 是函式呼叫操作符,所以 f 肯定是一個數組,陣列元素的型別是函式指標,它所指向的函式返回整型值。

        如果你弄明白了上面最後一個宣告,下面這個應該是比較容易的了:
        int *(*f[])();
        它和上面那個宣告的唯一區別就是多了一個間接訪問操作符,所以這個宣告建立了一個指標陣列,指標指向返回整型指標的函式。

        新式風格的例子:
        int (*f)(int,float);
        int *(*g[])(int,float);
        前者把 f 宣告為一個函式指標,它所指向的函式接受兩個引數,分別是一個整型和浮點型,並返回一個整型。後者把 g 宣告為一個指標陣列,它所指向的函式接受兩個引數,分別是整型和浮點型,並返回整型指標。

             【提示】如果你使用的是UNIX系統,並能訪問Internet,你可以獲得一個名叫 cdecl 的程式,它可以在 C 語言的宣告和宣告語義之間進行轉換。它可以解釋一個現存的 C 宣告:
        cdecl> explain int (*(*f)())[10];
              declare f as pointer to function returning pointer to array 10 of int
        或者給你一個宣告語義:
        cdecl> declare x as pointer to array 10 of pointer to function returning int
              int (*(*x)[10])()
        cdecl 的原始碼可以從 comp.sources.unix.newsgroup 存檔檔案第 14 卷中獲得。

如何理解c和c ++的複雜型別宣告 2007-10-13 16:21    如何理解c和c ++的複雜型別宣告曾經碰到過讓你迷惑不解、類似於int * (* (*fp1) (int) ) [10];這樣的變數宣告嗎?本文將由易到難,一步一步教會你如何理解這種複雜的C/C++宣告。 

  我們將從每天都能碰到的較簡單的宣告入手,然後逐步加入const修飾符和typedef,還有函式指標,最後介紹一個能夠讓你準確地理解任何C/C++宣告的“右左法則”。 

  需要強調一下的是,複雜的C/C++宣告並不是好的程式設計風格;我這裡僅僅是教你如何去理解這些宣告。注意:為了保證能夠在同一行上顯示程式碼和相關注釋,本文最好在至少1024x768解析度的顯示器上閱讀。 
讓我們從一個非常簡單的例子開始,如下:

int n;


這個應該被理解為“declare n as an int”(n是一個int型的變數)。接下去來看一下指標變數,如下: 

int *p;


這個應該被理解為“declare p as an int *”(p是一個int *型的變數),或者說p是一個指向一個int型變數的指標。我想在這裡展開討論一下:我覺得在宣告一個指標(或引用)型別的變數時,最好將*(或&)寫在緊靠變數之前,而不是緊跟基本型別之後。這樣可以避免一些理解上的誤區,比如: 
再來看一個指標的指標的例子: 

char **argv;


理論上,對於指標的級數沒有限制,你可以定義一個浮點型別變數的指標的指標的指標的指標,再來看如下的宣告: 

int RollNum[30][4]; 
int (*p)[4]=RollNum; 
int *q[5];
這裡,p被宣告為一個指向一個4元素(int型別)陣列的指標,而q被宣告為一個包含5個元素(int型別的指標)的陣列。另外,我們還可以在同一個宣告中混合實用*和&,如下: 

int **p1; 
// p1 is a pointer    to a pointer    to an int. 
int *&p2; 
// p2 is a reference to a pointer    to an int. 
int &*p3; 
// ERROR: Pointer    to a reference is illegal. 
int &&p4;
// ERROR: Reference to a reference is illegal.


注:p1是一個int型別的指標的指標;p2是一個int型別的指標的引用;p3是一個int型別引用的指標(不合法!);p4是一個int型別引用的引用(不合法!)。 

const修飾符 

當你想阻止一個變數被改變,可能會用到const關鍵字。在你給一個變數加上const修飾符的同時,通常需要對它進行初始化,因為以後的任何時候你將沒有機會再去改變它。例如: 

const int n=5; 
int const m=10;


上述兩個變數n和m其實是同一種類型的——都是const int(整形恆量)。因為C++標準規定,const關鍵字放在型別或變數名之前等價的。我個人更喜歡第一種宣告方式,因為它更突出了const修飾符的作用。當const與指標一起使用時,容易讓人感到迷惑。例如,我們來看一下下面的p和q的宣告: 

const int *p; 
int const *q;


他們當中哪一個代表const int型別的指標(const直接修飾int),哪一個代表int型別的const指標(const直接修飾指標)?實際上,p和q都被宣告為const int型別的指標。而int型別的const指標應該這樣宣告: 


int * const r= &n;
// n has been declared as an int


這裡,p和q都是指向const int型別的指標,也就是說,你在以後的程式裡不能改變*p的值。而r是一個const指標,它在宣告的時候被初始化指向變數n(即r=&n;)之後,r的值將不再允許被改變(但*r的值可以改變)。 

組合上述兩種const修飾的情況,我們來宣告一個指向const int型別的const指標,如下: 

const int * const p=&n 
// n has been declared as const int


下面給出的一些關於const的宣告,將幫助你徹底理清const的用法。不過請注意,下面的一些宣告是不能被編譯通過的,因為他們需要在宣告的同時進行初始化。為了簡潔起見,我忽略了初始化部分;因為加入初始化程式碼的話,下面每個宣告都將增加兩行程式碼。 

char ** p1; 
//      pointer to      pointer to      char 
const char **p2;
//      pointer to      pointer to const char 
char * const * p3;
//      pointer to const pointer to      char 
const char * const * p4;
//      pointer to const pointer to const char 
char ** const p5;
// const pointer to      pointer to      char 
const char ** const p6;
// const pointer to      pointer to const char 
char * const * const p7;
// const pointer to const pointer to      char 
const char * const * const p8;
// const pointer to const pointer to const char


注:p1是指向char型別的指標的指標;p2是指向const char型別的指標的指標;p3是指向char型別的const指標;p4是指向const char型別的const指標;p5是指向char型別的指標的const指標;p6是指向const char型別的指標的const指標;p7是指向char型別const指標的const指標;p8是指向const char型別的const指標的const指標。 

typedef的妙用 

typedef給你一種方式來克服“*只適合於變數而不適合於型別”的弊端。你可以如下使用typedef: 

typedef char * PCHAR; 
PCHAR p,q;


這裡的p和q都被宣告為指標。(如果不使用typedef,q將被宣告為一個char變數,這跟我們的第一眼感覺不太一致!)下面有一些使用typedef的宣告,並且給出瞭解釋: 

typedef char * a;
// a is a pointer to a char 

typedef a b();
// b is a function that returns 
// a pointer to a char 

typedef b *c;
// c is a pointer to a function 
// that returns a pointer to a char 

typedef c d();
// d is a function returning 
// a pointer to a function 
// that returns a pointer to a char 

typedef d *e;
// e is a pointer to a function 
// returning a pointer to a 
// function that returns a 
// pointer to a char 

e var[10];
// var is an array of 10 pointers to 
// functions returning pointers to 
// functions returning pointers to chars.


typedef經常用在一個結構宣告之前,如下。這樣,當建立結構變數的時候,允許你不使用關鍵字struct(在C中,建立結構變數時要求使用struct關鍵字,如struct tagPOINT a;而在C++中,struct可以忽略,如tagPOINT b)。 

typedef struct tagPOINT 
{ 
    int x; 
    int y; 
}POINT; 

POINT p; /* Valid C code */
函式指標

函式指標可能是最容易引起理解上的困惑的宣告。函式指標在DOS時代寫TSR程式時用得最多;在Win32和X-Windows時代,他們被用在需要回調函式的場合。當然,還有其它很多地方需要用到函式指標:虛擬函式表,STL中的一些模板,Win NT/2K/XP系統服務等。讓我們來看一個函式指標的簡單例子:





int (*p)(char);


這裡p被宣告為一個函式指標,這個函式帶一個char型別的引數,並且有一個int型別的返回值。另外,帶有兩個float型別引數、返回值是char型別的指標的指標的函式指標可以宣告如下: 

char ** (*p)(float, float);


那麼,帶兩個char型別的const指標引數、無返回值的函式指標又該如何宣告呢?參考如下: 

void * (*a[5])(char * const, char * const);


“右左法則”是一個簡單的法則,但能讓你準確理解所有的宣告。這個法則運用如下:從最內部的括號開始閱讀宣告,向右看,然後向左看。當你碰到一個括號時就調轉閱讀的方向。括號內的所有內容都分析完畢就跳出括號的範圍。這樣繼續,直到整個宣告都被分析完畢。 

對上述“右左法則”做一個小小的修正:當你第一次開始閱讀宣告的時候,你必須從變數名開始,而不是從最內部的括號。 

下面結合例子來演示一下“右左法則”的使用。 

int * (* (*fp1) (int) ) [10];


閱讀步驟: 

1. 從變數名開始——fp1 

2. 往右看,什麼也沒有,碰到了),因此往左看,碰到一個*——一個指標 

3. 跳出括號,碰到了(int)——一個帶一個int引數的函式 

4. 向左看,發現一個*——(函式)返回一個指標 

5. 跳出括號,向右看,碰到[10]——一個10元素的陣列 

6. 向左看,發現一個*——指標 

7. 向左看,發現int——int型別 

總結:fp1被宣告成為一個函式的指標,該函式返回指向指標陣列的指標. 

再來看一個例子: 

int *( *( *arr[5])())();


閱讀步驟: 

1. 從變數名開始——arr 

2. 往右看,發現是一個數組——一個5元素的陣列 

3. 向左看,發現一個*——指標 

4. 跳出括號,向右看,發現()——不帶引數的函式 

5. 向左看,碰到*——(函式)返回一個指標 

6. 跳出括號,向右發現()——不帶引數的函式 

7. 向左,發現*——(函式)返回一個指標 

8. 繼續向左,發現int——int型別 

還有更多的例子: 

float ( * ( *b()) [] )();
// b is a function that returns a 
// pointer to an array of pointers 
// to functions returning floats. 
void * ( *c) ( char, int (*)());
// c is a pointer to a function that takes 
// two parameters: 
// a char and a pointer to a 
// function that takes no 
// parameters and returns 
// an int 
// and returns a pointer to void. 
void ** (*d) (int &, 
char **(*)(char *, char **));
// d is a pointer to a function that takes 
// two parameters: 
// a reference to an int and a pointer 
// to a function that takes two parameters: 
// a pointer to a char and a pointer 
// to a pointer to a char 
// and returns a pointer to a pointer 
// to a char 
// and returns a pointer to a pointer to void 
float ( * ( * e[10]) 
    (int &) ) [5];
// e is an array of 10 pointers to 
// functions that take a single 
// reference to an int as an argument 
// and return pointers to 
// an array of 5 floats.