
看了C陷阱與缺陷,第一個(gè)給我震撼的就是理解函數聲明了,下面是我的理解。
1.理解函數聲明
為了模擬開(kāi)機啟動(dòng)時(shí)的情形,我們必須設計出一個(gè)C語(yǔ)句,以顯示調用位于地址0的子例程。調用語(yǔ)句如下:
(*(void (*)())0)();
膽顫了吧?首先我們從函數的聲明說(shuō)起:有如下一個(gè)函數
void func(){...}
那么,要想聲明一個(gè)函數指針,指向這類(lèi)函數,怎樣聲明呢?如下:
void (*pf)();
那么,想要將一個(gè)值轉換成一個(gè)指針,指向這類(lèi)函數,怎么強制轉換呢?如下:
(void (*)())value
現在value被轉換成了一個(gè)指向函數的指針,怎么調用它呢?如下:
((void) (*)()value)();
或 (*(void) (*)()value)();
或 (******(void) (*)value)();
神奇吧,看了上面的函數調用方式!其實(shí)你完全可以這樣調用函數:
func();
(*func)();
(********func)();
沒(méi)有任何問(wèn)題!
對于初學(xué)的時(shí)候,最容易頭暈的就是指針?lè )?到底是什么東西?它是變量的一部分?還是聲明類(lèi)型的一部分?其實(shí),仔細回憶C中標識符的定義規則就知道,指針?lè )?必須是類(lèi)型聲明的一部分,因為變量的聲明不能含有指針?lè )?,否則是一個(gè)非法的變量!
2.C語(yǔ)言中的聲明
對比簡(jiǎn)單類(lèi)型的強制轉換我們就可以更加明白上面的強制轉換,簡(jiǎn)單類(lèi)型定義如下:
int a;
char b;
b=(char)a;
int *a;
char *b;
b=(int *)a;
其實(shí),在各種指針之間轉換很少見(jiàn),最常見(jiàn)的就是將void指針轉換成各種指針,用過(guò)malloc族函數嗎?
a=(int *)malloc(sizeof(int));
由于聲明符和表達式類(lèi)似,所以你可以這樣聲明
int ((a)); //將a聲明為一個(gè)int型值
int *func(),(*fp)();
前者是一個(gè)函數,后者是一個(gè)指針,所以千萬(wàn)不要對指針定義成函數了,分不清概念的時(shí)候最容易這樣
int (*fp)(){
/*do something*/
}
顯然,這里將一個(gè)指針定義成了一個(gè)函數!這是任何編譯器都不能容忍的。
結合簡(jiǎn)單變量的類(lèi)型,出去聲明中的變量,得到的就是這個(gè)變量的類(lèi)型,如float a,那么要將一個(gè)變量轉換成a的類(lèi)型,那么只需要
(float)value;即可,同理:
float *b,除去變量b,得到b的類(lèi)型是(float *)。
float (*fp)();除去變量fp,得到的類(lèi)型是float (*)(),所以要將變量轉換成fp類(lèi)型的值時(shí),只需要(float (*)())value,即可!這樣value即被轉換成一個(gè)函數指針了,也就像最前面的例子中那樣!
如果,更復雜,有聲明如下:
float * (*fp)(); //返回一個(gè)float指針
其實(shí),這也是唬人的,同理可知:強制轉換類(lèi)型為(float * (*)())value;
3.其他考量
看到第一個(gè)例子時(shí),可能想,能不能這樣子:(*0)();呢?
不行,因為0是一個(gè)數字,而*必須要操作一個(gè)指針,而且對于要調用的函數是void型的,所以這個(gè)指針應該轉換成相應的類(lèi)型,所以需要將0轉成一個(gè)指向void返回值的參數為空的函數的指針。
最后,linux內核中的信號處理函數定義如下:
void (*signal(int,void (*)(int)))(int);
首先,將上面的函數聲明看成這樣void (*p)(int);可知,p是一個(gè)函數,所以signal函數的返回類(lèi)型為一個(gè)函數指針,指向的函數類(lèi)型是
void (*)(int);使用typedef可以簡(jiǎn)化上面signal函數的聲明
typedef void (*HANDLER)(int);
HANDLER signal(int,HANDLER);
解釋一下:signal是一個(gè)返回函數指針的函數,返回的指針指向的函數類(lèi)型是void (*)(int);而signal的參數一個(gè)是int,另一個(gè)是一個(gè)函數,類(lèi)型為void (*)(int),剛好就是最初聲明的樣子。
摘自C陷阱和缺陷一書(shū),但被網(wǎng)上一個(gè)自認為是高手的挫人改成這個(gè)樣子,湊合著(zhù)看吧
聲明與函數 有一段程序存儲在起始地址為0的一段內存上,如果我們想要調用這段程序,請問(wèn)該如何去做?
答案 答案是(*(void (*)( ) )0)( )??雌饋?lái)確實(shí)令人頭大,那好,讓我們知難而上,從兩個(gè)不同的途徑來(lái)詳細分析這個(gè)問(wèn)題。
答案分析:從尾到頭
首先,最基本的函數聲明:void function (paramList); 最基本的函數調用:function(paramList); 鑒于問(wèn)題中的函數沒(méi)有參數,函數調用可簡(jiǎn)化為 function();
其次,根據問(wèn)題描述,可以知道0是這個(gè)函數的入口地址,也就是說(shuō),0是一個(gè)函數的指針。使用函數指針的函數聲明形式是:void (*pFunction)(),相應的調用形式是: (*pFunction)(),則問(wèn)題中的函數調用可以寫(xiě)作:(*0)( )。
第三,大家知道,函數指針變量不能是一個(gè)常數,因此上式中的0必須要被轉化為函數指針。我們先來(lái)研究一下,對于使用函數指針的函數:比如void(*pFunction)( ),函數指針變量的原型是什么? 這個(gè)問(wèn)題很簡(jiǎn)單,pFunction函數指針原型是( void (*)( )),即去掉變量名,清晰起見(jiàn),整個(gè)加上()號。所以將0強制轉換為一個(gè)返回值為void,參數為空的函數指針如下:( void (*)( ) )。OK,結合2)和3)的分析,結果出來(lái)了,那就是:(*(void (*)( ) )0)( ) 。
答案分析:從頭到尾理解答案
(void (*)( )) ,是一個(gè)返回值為void,參數為空的函數指針原型.(void (*)())0,把0轉變成一個(gè)返回值為void,參數為空的函數指針,指針指向的地址為0. *(void (*)())0,前面加上*表示整個(gè)是一個(gè)返回值為void的函數的名字 (*(void (*)( ))0)( ),這當然就是一個(gè)函數了。我們可以使用typedef清晰聲明如下: typedef void (*pFun)( ); 這樣函數變?yōu)?(*(pFun)0 )( );
問(wèn)題:三個(gè)聲明的分析 對聲明進(jìn)行分析,最根本的方法還是類(lèi)比替換法,從那些最基本的聲明上進(jìn)行類(lèi)比,簡(jiǎn)化,從而進(jìn)行理解,下面通過(guò)分析三個(gè)例子,來(lái)具體闡述如何使用這種方法。
#1:int* (*a[5])(int, char*);
首先看到標識符名a,"[]"優(yōu)先級大于"*",a與"[5]"先結合。所以a是一個(gè)數組,這個(gè)數組有5個(gè)元素,每一個(gè)元素都是一個(gè)指針,指針指向"(int, char*)",很明顯,指向的是一個(gè)函數,這個(gè)函數參數是"int, char*",返回值是"int*"。OK,結束了一個(gè)。:)
#2:void (*b[10]) (void (*)());
b是一個(gè)數組,這個(gè)數組有10個(gè)元素,每一個(gè)元素都是一個(gè)指針,指針指向一個(gè)函數,函數參數是"void (*)()"【注10】,返回值是"void"。完畢! 注意:這個(gè)參數又是一個(gè)指針,指向一個(gè)函數,函數參數為空,返回值是"void"。
#3. double(*)() (*pa)[9]; pa是一個(gè)指針,指針指向一個(gè)數組,這個(gè)數組有9個(gè)元素,每一個(gè)元素都是"double(*)()"(也即一個(gè)函數指針,指向一個(gè)函數,這個(gè)函數的參數為空,返回值是"double")。
(自己注:#3看起來(lái)有點(diǎn)難理解,如果把double(*)()換成int, 變成int (*pa)[9]這樣就不難理解了)
聯(lián)系客服