關於C C 函式指標宣告的理解

2021-06-18 01:26:16 字數 3512 閱讀 3012

【前言】

由於最近對函式指標的理解比較模糊,所有又重新學習了一把關於函式指標的知識,參考了很多書籍和網上的文章。現在本人進行一下分享和總結。本文的其實只是整理和總結別人現有的文章,作為備用參考文件。

【正文】

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

ok,對函式指標的理解到此結束。

參考文章:

allen

2013-08-23

詳解C C 函式指標宣告

要理解乙個c程式,僅僅理解組成該程式的符號是不夠的。程式設計師還必須理解這些符號是如何組合成宣告 表示式 語句和程式的。我們先來看看下面的乙個語句 1 void 0 這是當計算機啟動時,硬體將呼叫首位址為0位置的子例程。像這樣的表示式恐怕會令每個c c 程式設計師的內心都 不寒而慄 吧。然而,完全不...

詳解C C 函式指標宣告

要理解乙個c程式,僅僅理解組成該程式的符號是不夠的。程式設計師還必須理解這些符號是如何組合成宣告 表示式 語句和程式的。我們先來看看下面的乙個語句 1 void 0 這是當計算機啟動時,硬體將呼叫首位址為0位置的子例程。像這樣的表示式恐怕會令每個c c 程式設計師的內心都 不寒而慄 吧。然而,完全不...

詳解C C 函式指標宣告

要理解乙個c程式,僅僅理解組成該程式的符號是不夠的。程式設計師還必須理解這些符號是如何組合成宣告 表示式 語句和程式的。我們先來看看下面的乙個語句 1 void 0 這是當計算機啟動時,硬體將呼叫首位址為0位置的子例程。像這樣的表示式恐怕會令每個c c 程式設計師的內心都 不寒而慄 吧。然而,完全不...