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

來源:互聯網
上載者:User

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

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

( *( void(*)())0)();

     這是當電腦啟動時,硬體將調用首地址為0位置的子常式。像這樣的運算式恐怕會令每個C/C++程式員的內心都“不寒而慄”吧。

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

float f , g ;

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

     同樣的邏輯也適用於函數和指標類型的聲明,例如:

float ff();

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

float *pf;

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

     以上這些形式在聲明中還可以組合起來,就像在運算式中進行組合一樣,因此:

float *g() , (*h)();

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

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

float (*h)();

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

(float (*)())

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

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

( *( void(*)())0)();

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

(*fp)();

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

     運算式(*fp)()中,*fp兩側的括弧非常重要,因為函數運算子()的優先順序高於單目運算子*。如果*fp兩側沒有括弧,那麼*fp()實際上與*(fp())的含義完全一致。

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

(*0)()

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

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

viod (*fp)();

     因此,將常數0轉型為“指向傳回值為void的函數的指標”類型,可以這樣寫:

(void (*)())0

     因此,我們可以用(void(*)())0來替換fp,從而得到:

( *( void(*)())0)();

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

typedef void (*fp)();(*(fp)0)();

這個問題就可以解決了。

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

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

void sigfunc(int n){/* 特定訊號處理部分*/}

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

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

void sigfunc(int );

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

void (*sfp)(int);

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

void (*signal(something))(int);

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

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

void (*sfp)(int);

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

void (*signal(int,void(*)(int)))(int);

     同樣地,使用typedef可以簡化上面的函式宣告:

typedef void (*HANDLER)(int);HANDLER signal(int , HANDLER);

 

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

 

參考書籍:C陷阱與缺陷

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.