**:
要理解乙個c程式,僅僅理解組成該程式的符號是不夠的。程式設計師還必須理解這些符號是如何組合成宣告、表示式、語句和程式的。
我們先來看看下面的乙個語句:
1
( *(
void
(*)())0)();
這是當計算機啟動時,硬體將呼叫首位址為0位置的子例程。像這樣的表示式恐怕會令每個c/c++程式設計師的內心都「不寒而慄」吧。
然而,完全不用害怕,任何c變數的宣告都是由兩部分組成:型別以及一組類似表示式的宣告符。最簡單的宣告變數,如:
1
float
f , g ;
這個宣告的含義是:當對其求值時,表示式f和g的型別為浮點型。
同樣的邏輯也適用於函式和指標型別的宣告,例如:
1
float
ff();
這個宣告的含義是:表示式ff()求值結果是乙個浮點數,也就是說,ff是乙個返回值為浮點型別的函式,類似地:
1
float
*pf;
這個宣告的含義是*pf是乙個浮點數,也就是說,pf是乙個指向浮點數的指標。
以上這些形式在宣告中還可以組合起來,就像在表示式中進行組合一樣,因此:
1
float
*g() , (*h)();
表示*g()與(*h)()是浮點表示式。因為()結合優先順序高於*,*g()也就是*(g()):g是乙個函式,該函式的返回值型別為指向浮點數的指標。同理,可以得出h是乙個函式指標,h所指向函式的返回值為浮點型別。
一旦我們知道了如何宣告乙個給定型別的變數,那麼該型別的型別轉換符就很容易得到了:只需要把宣告中的變數名和宣告末尾的分號去掉,再將剩餘的部分用乙個括號整個「封裝」起來即可。例如:
1
float
(*h)();
表示h是乙個指向返回值為浮點型別的函式的指標,因此,
1
(
float
(*)())
表示乙個「指向返回值為浮點型別的函式的指標」的型別轉換符。
那麼,我們現在來看看前面我們提出的表示式:
1
( *(
void
(*)())0)();
第一步,假定變數fp是乙個函式指標,那麼如何呼叫fp所指向的函式呢?呼叫方法如下:
1
(*fp)();
因為fp是乙個函式指標,那麼*fp就是該指標所指向的函式,所以(*fp)()就是呼叫該函式的方式。
表示式(*fp)()中,*fp兩側的括號非常重要,因為函式運算子()的優先順序高於單目運算子*。如果*fp兩側沒有括號,那麼*fp()實際上與*(fp())的含義完全一致。
現在剩下的問題就只是找到乙個恰到的表示式來替換fp。我們將在分析的第二步來解決這個問題。如果c編譯器能夠理解我們大腦中對於型別的認識,那麼我們可以這樣寫:
1
(*0)()
上式並不能生效,因為運算子*必須要乙個指標來做運算元。而且這個指標還應該是乙個函式指標,這樣經運算子*作用後的結果才能作為函式被呼叫。因此,在上式中必須對0作型別轉換,轉換後的型別可以大致描述為:「指向返回值為void型別的函式的指標」。
如果fp是乙個指向返回值為void型別的函式的指標,那麼(*fp)()的值為void,fp的宣告如下:
1
viod (*fp)();
因此,將常數0轉型為「指向返回值為void的函式的指標」型別,可以這樣寫:
1
(
void
(*)())0
因此,我們可以用(void(*)())0來替換fp,從而得到:
1
( *(
void
(*)())0)();
當然,我們用typedef來解決這個問題能夠表述更加清晰:
1
2
typedef
void
(*fp)();
(*(fp)0)();
這個問題就可以解決了。
我們再來考慮signal庫函式,一般情況下,程式設計師並不主動宣告signal函式,而是直接使用標頭檔案signal.h中的宣告。那麼,在標頭檔案signal.h中,signal函式是如何宣告的呢?
首先,讓我們從使用者定義的訊號處理函式開始考慮,這無疑是最容易解決的。該函式可以定義如下:
1
2
3
void
sigfunc(
int
n)
函式sigfunc的引數是乙個代表特定訊號的整數值,此處我們暫時忽略它。
上面假設的函式體定義了sigfunc函式,因而sigfunc函式的宣告可以如下:
1
void
sigfunc(
int
);
現在假定我們希望宣告乙個指向sigfunc函式的指標變數,不妨命名為sfp。因而sfp指向sigfunc函式,*sfp就代表sigfunc函式,因此*sfp可以被呼叫。因此我們可以如下這樣宣告sfp:
1
void
(*sfp)(
int
);
因為signal函式的返回值型別與sfp的返回值型別一樣,上式也就宣告了signal函式,我們不妨可以如下宣告signal函式:
1
void
(*
signal
(something))(
int
);
此處的something代表了signal函式的引數型別,我們還需要進一步了解如何宣告它們。上面宣告可以這樣理解:傳遞適當的引數以呼叫signal函式,對signal函式返回值(為函式指標型別)解除引用,然後傳遞乙個整型引數呼叫解除引用後所得函式,最後返回值為void型別。因此,signal函式的返回值是乙個指向返回值為void型別的函式指標。
那麼,signal函式的引數又是如何呢?,signal函式接受兩個引數:乙個整型的訊號編號,以及乙個指向使用者定義的訊號處理函式的指標。我們此前一定定義了指向使用者定義的訊號處理函式的指標sfp:
1
void
(*sfp)(
int
);
sfp的型別可以通過將上面的宣告中的sfp去掉而得到,即 void(*)(int)。此外,signal函式的返回值是乙個指向呼叫前的使用者定義訊號處理函式的指標,這個指標的型別與sfp指標型別一致。因此我們可以如下宣告signal函式:
1
void
(*
signal
(
int
,
void
(*)(
int
)))(
int
);
同樣地,使用typedef可以簡化上面的函式宣告:
1
2
typedef void (*handler)(int);
handler signal(int , handler);
那麼,現在的你對函式指標理解了嗎?如果你看完了此篇文章,相信你一定會有意想不到的收穫哦!
參考書籍:c陷阱與缺陷
詳解C C 函式指標宣告
要理解乙個c程式,僅僅理解組成該程式的符號是不夠的。程式設計師還必須理解這些符號是如何組合成宣告 表示式 語句和程式的。我們先來看看下面的乙個語句 1 void 0 這是當計算機啟動時,硬體將呼叫首位址為0位置的子例程。像這樣的表示式恐怕會令每個c c 程式設計師的內心都 不寒而慄 吧。然而,完全不...
詳解C C 函式指標宣告
要理解乙個c程式,僅僅理解組成該程式的符號是不夠的。程式設計師還必須理解這些符號是如何組合成宣告 表示式 語句和程式的。我們先來看看下面的乙個語句 1 void 0 這是當計算機啟動時,硬體將呼叫首位址為0位置的子例程。像這樣的表示式恐怕會令每個c c 程式設計師的內心都 不寒而慄 吧。然而,完全不...
c c 函式指標詳解
今天我們要介紹的是函式指標。內容由以下3部分組成 函式指標的概念,為了面試時能夠說出來。函式指標的用法 當然是為工作用到嘍 函式指標的例項,用一些練習來加深印象。經典面試題 函式指標和指標函式的區別 好 開始!函式指標的概念 1.1 函式指標 是指向函式的指標變數 在程式執行中,函式是程式演算法指令...