詳解C/C++函式指標宣告

鴨脖發表於2013-04-24

 要理解一個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);

 

     那麼,現在的你對函式指標理解了嗎?如果你看完了此篇文章,相信你一定會有意想不到的收穫哦!

相關文章