第七章 右左法則 複雜指標解析

2021-06-23 03:07:37 字數 4231 閱讀 5051

分類: 

陣列與指標的藝術2009-11-23 10:54 

9232人閱讀

收藏舉報

struct

list

編譯器程式設計

語言go

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

這是乙個會讓初學者感到頭暈目眩、感到恐懼的函式指標宣告。在熟練掌握c/c++的宣告語法之前,不學習一定的規則,想理解好這類複雜宣告是比較困難的。

c/c++所有複雜的宣告結構,都是由各種宣告巢狀構成的。如何解讀複雜指標宣告?右左法則是乙個很著名、很有效的方法。不過,右左法則其實並不是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/c++語言的陣列名是乙個不可修改的左值,它不能直接被另乙個陣列的內容修改,因此函式返回值不能為陣列。

int func[5](void);

func是乙個具有5個元素的陣列,這個陣列的元素都是函式。這也是非法的,因為陣列的元素必須是物件,但函式不是物件,不能作為陣列的元素。

實際程式設計當中,需要宣告乙個複雜指標時,如果把整個宣告寫成上面所示這些形式,將對可讀性帶來一定的損害,應該用typedef來對宣告逐層分解,增強可讀性。

typedef是一種宣告,但它宣告的不是變數,也沒有建立新型別,而是某種型別的別名。typedef有很大的用途,對乙個複雜宣告進行分解以增強可讀性是其作用之一。例如對於宣告:

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

可以這樣分解:

typedef  int (*para)[5];

typedef para (*func)(int *);

這樣就容易看得多了。

typedef的另乙個作用,是作為基於物件程式設計的高層抽象手段。在adt中,它可以用來在c/c++和現實世界的物件間建立關聯,將這些物件抽象成c/c++的型別系統。在設計adt的時候,我們常常宣告某個指標的別名,例如:

typedef struct node * list;

從adt的角度看,這個宣告是再自然不過的事情,可以用list來定義乙個列表。但從c/c++語法的角度來看,它其實是不符合c/c++宣告語法的邏輯的,它暴力地將指標宣告符從指標宣告器中分離出來,這會造成一些異於人們閱讀習慣的現象,考慮下面**:

const struct node *p1;

typedef struct node *list;

const list p2;

p1型別是const struct node*,那麼p2呢?如果你以為就是把list簡單「代入」p2,然後得出p2型別也是const struct node*的結果,就大錯特錯了。p2的型別其實是struct node * const p2,那個const限定的是p2,不是node。造成這一奇異現象的原因是指標宣告器被分割,標準中規定:

6.7.5.1 pointer declarators

semantics

if in the declaration 『『t d1』』, d1has the form

*type-qualifier-listoptd

and the type specified for ident in the declaration 『『t d』』 is

『『derived-declarator-type-list t』』

then the type specified for ident is

『『derived-declarator-type-list type-qualifier-list pointer to t』』

for each type qualifier in the list, ident is a so-qualified pointer.

指標的宣告器由指標宣告符*、可選的型別限定詞type-qualifier-listopt和識別符號d組成,這三者在邏輯上是乙個整體,構成乙個完整的指標宣告器。這也是多個變數同列定義時指標宣告符必須緊跟識別符號的原因,例如:

int *p, q, *k;

p和k都是指標,但q不是,這是因為*p、*k是乙個整體指標宣告器,以表示宣告的是乙個指標。編譯器會把指標宣告符左邊的型別包括其限定詞作為指標指向的實體的型別,右邊的限定詞限定被宣告的識別符號。但現在typedef struct node *list硬生生把*從整個指標宣告器中分離出來,編譯器找不到*,會認為const list p2中的const是限定p2的,正因如此,p2的型別是node * const而不是const node*。

雖然typedef struct node* list不符合宣告語法的邏輯,但基於typedef在adt中的重要作用以及資訊隱藏的要求,我們應該讓使用者這樣使用list a,而不是list *a,因此在adt的設計中仍應使用上述typedef語法,但需要注意其帶來的不利影響。

第七章 右左法則 複雜指標解析

int fun int 10 這是乙個會讓初學者感到頭暈目眩 感到恐懼的函式指標宣告。在熟練掌握c c 的宣告語法之前,不學習一定的規則,想理解好這類複雜宣告是比較困難的。c c 所有複雜的宣告結構,都是由各種宣告巢狀構成的。如何解讀複雜指標宣告?右左法則是乙個很著名 很有效的方法。不過,右左法則其...

第七章 右左法則 複雜指標解析

int fun int 10 這是乙個會讓初學者感到頭暈目眩 感到恐懼的函式指標宣告。在熟練掌握c c 的宣告語法之前,不學習一定的規則,想理解好這類複雜宣告是比較困難的。c c 所有複雜的宣告結構,都是由各種宣告巢狀構成的。如何解讀複雜指標宣告?右左法則是乙個很著名 很有效的方法。不過,右左法則其...

右左法則 複雜指標解析

第八章 右左法則 複雜指標解析 上一章費那麼多唇舌討論c語言的宣告,其實目的都是為了這一章,期望讀者通過對c語言宣告形式的詳細了解,樹立宣告巢狀的觀念,因為c語言所有複雜的指標宣告,都是由各種宣告巢狀構成的。如何解讀複雜指標宣告呢?右左法則是乙個既著名又常用的方法。不過,右左法則其實並不是c標準裡面...