天天看點

詳解C/C++函數指針聲明

 要了解一個C程式,僅僅了解組成該程式的符号是不夠的。程式員還必須了解這些符号是如何組合成聲明、表達式、語句和程式的。

     我們先來看看下面的一個語句:

<code>( *(</code><code>void</code><code>(*)())0)();</code>

     這是當計算機啟動時,硬體将調用首位址為0位置的子例程。像這樣的表達式恐怕會令每個C/C++程式員的内心都“不寒而栗”吧。

     然而,完全不用害怕,任何C變量的聲明都是由兩部分組成:類型以及一組類似表達式的聲明符。最簡單的聲明變量,如:

<code>float</code> <code>f , g ;</code>

     這個聲明的含義是:當對其求值時,表達式f和g的類型為浮點型。

     同樣的邏輯也适用于函數和指針類型的聲明,例如:

<code>float</code> <code>ff();</code>

     這個聲明的含義是:表達式ff()求值結果是一個浮點數,也就是說,ff是一個傳回值為浮點類型的函數,類似地:

<code>float</code> <code>*pf;</code>

     這個聲明的含義是*pf是一個浮點數,也就是說,pf是一個指向浮點數的指針。

     以上這些形式在聲明中還可以組合起來,就像在表達式中進行組合一樣,是以:

<code>float</code> <code>*g() , (*h)();</code>

表示*g()與(*h)()是浮點表達式。因為()結合優先級高于*,*g()也就是*(g()):g是一個函數,該函數的傳回值類型為指向浮點數的指針。同理,可以得出h是一個函數指針,h所指向函數的傳回值為浮點類型。

     一旦我們知道了如何聲明一個給定類型的變量,那麼該類型的類型轉換符就很容易得到了:隻需要把聲明中的變量名和聲明末尾的分号去掉,再将剩餘的部分用一個括号整個“封裝”起來即可。例如:

<code>float</code> <code>(*h)();</code>

表示h是一個指向傳回值為浮點類型的函數的指針,是以,

<code>(</code><code>float</code> <code>(*)())</code>

表示一個“指向傳回值為浮點類型的函數的指針”的類型轉換符。

     那麼,我們現在來看看前面我們提出的表達式:

     第一步,假定變量fp是一個函數指針,那麼如何調用fp所指向的函數呢?調用方法如下:

<code>(*fp)();</code>

     因為fp是一個函數指針,那麼*fp就是該指針所指向的函數,是以(*fp)()就是調用該函數的方式。

     表達式(*fp)()中,*fp兩側的括号非常重要,因為函數運算符()的優先級高于單目運算符*。如果*fp兩側沒有括号,那麼*fp()實際上與*(fp())的含義完全一緻。

     現在剩下的問題就隻是找到一個恰到的表達式來替換fp。我們将在分析的第二步來解決這個問題。如果C編譯器能夠了解我們大腦中對于類型的認識,那麼我們可以這樣寫:

<code>(*0)()</code>

     上式并不能生效,因為運算符*必須要一個指針來做操作數。而且這個指針還應該是一個函數指針,這樣經運算符*作用後的結果才能作為函數被調用。是以,在上式中必須對0作類型轉換,轉換後的類型可以大緻描述為:“指向傳回值為void類型的函數的指針”。

     如果fp是一個指向傳回值為void類型的函數的指針,那麼(*fp)()的值為void,fp的聲明如下:

<code>viod (*fp)();</code>

     是以,将常數0轉型為“指向傳回值為void的函數的指針”類型,可以這樣寫:

<code>(</code><code>void</code> <code>(*)())0</code>

     是以,我們可以用(void(*)())0來替換fp,進而得到:

     當然,我們用typedef來解決這個問題能夠表述更加清晰:

<code>typedef</code> <code>void</code> <code>(*fp)();</code>

<code>(*(fp)0)();</code>

這個問題就可以解決了。

     我們再來考慮signal庫函數,一般情況下,程式員并不主動聲明signal函數,而是直接使用頭檔案signal.h中的聲明。那麼,在頭檔案signal.h中,signal函數是如何聲明的呢?

     首先,讓我們從使用者定義的信号處理函數開始考慮,這無疑是最容易解決的。該函數可以定義如下:

<code>void</code> <code>sigfunc(</code><code>int</code> <code>n){</code>

<code>        </code><code>/* 特定信号處理部分*/</code>

<code>}</code>

     函數sigfunc的參數是一個代表特定信号的整數值,此處我們暫時忽略它。

     上面假設的函數體定義了sigfunc函數,因而sigfunc函數的聲明可以如下:

<code>void</code> <code>sigfunc(</code><code>int</code> <code>);</code>

     現在假定我們希望聲明一個指向sigfunc函數的指針變量,不妨命名為sfp。因而sfp指向sigfunc函數,*sfp就代表sigfunc函數,是以*sfp可以被調用。是以我們可以如下這樣聲明sfp:

<code>void</code> <code>(*sfp)(</code><code>int</code><code>);</code>

     因為signal函數的傳回值類型與sfp的傳回值類型一樣,上式也就聲明了signal函數,我們不妨可以如下聲明signal函數:

<code>void</code> <code>(*</code><code>signal</code><code>(something))(</code><code>int</code><code>);</code>

     此處的something代表了signal函數的參數類型,我們還需要進一步了解如何聲明它們。上面聲明可以這樣了解:傳遞适當的參數以調用signal函數,對signal函數傳回值(為函數指針類型)解除引用,然後傳遞一個整型參數調用解除引用後所得函數,最後傳回值為void類型。是以,signal函數的傳回值是一個指向傳回值為void類型的函數指針。

     那麼,signal函數的參數又是如何呢?,signal函數接受兩個參數:一個整型的信号編号,以及一個指向使用者定義的信号處理函數的指針。我們此前一定定義了指向使用者定義的信号處理函數的指針sfp:

     sfp的類型可以通過将上面的聲明中的sfp去掉而得到,即 void(*)(int)。此外,signal函數的傳回值是一個指向調用前的使用者定義信号處理函數的指針,這個指針的類型與sfp指針類型一緻。是以我們可以如下聲明signal函數:

<code>void</code> <code>(*</code><code>signal</code><code>(</code><code>int</code><code>,</code><code>void</code><code>(*)(</code><code>int</code><code>)))(</code><code>int</code><code>);</code>

     同樣地,使用typedef可以簡化上面的函數聲明:

<code>typedef void (*HANDLER)(int);</code>

<code>HANDLER signal(int , HANDLER);</code>

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

參考書籍:C陷阱與缺陷

繼續閱讀