C語言指針導(dǎo)學(xué)(4)——分清函數(shù)指針和指針函數(shù)(轉(zhuǎn)載)

C語言指針導(dǎo)學(xué)(4)——分清函數(shù)指針和指針函數(shù)

http://blog.csdn.net/porscheyin/article/details/3461632/


四.分清函數(shù)指針和指針函數(shù)

關(guān)于指針和數(shù)組斬不斷理還亂的恩怨還真是說了不少,不過現(xiàn)在應(yīng)該已經(jīng)理清了。有了上一講的基礎(chǔ),本講的內(nèi)容相對(duì)來說就比較容易理解了。

1.指向函數(shù)的指針(函數(shù)指針)

來分析這樣一個(gè)聲明,void (*f) ( );雖然()的優(yōu)先級(jí)高于*,但由于有括號(hào)存在,首先執(zhí)行的是解引用,所以f是一個(gè)指針;接下來執(zhí)行( ),表明f指向一個(gè)函數(shù),這個(gè)函數(shù)不返回任何值。現(xiàn)在得出結(jié)論:f是一個(gè)指向不接受參數(shù)且不返回任何值的函數(shù)的指針,簡稱函數(shù)指針(pointer to function)。

對(duì)比一下int(*p) [100],p是一個(gè)指向含有100個(gè)整型元素的數(shù)組的指針,它們有一個(gè)共同的特點(diǎn):指針聲明符(*)和標(biāo)識(shí)符(f或p)都被限制在一個(gè)括號(hào)中,由于括號(hào)的優(yōu)先級(jí)是最高的,所以我們從標(biāo)識(shí)符開始由內(nèi)向外分析,即可得到以上結(jié)果。

<1>.初始化

注意指向函數(shù)的指針(函數(shù)指針)指向的是函數(shù)而非普通的變量,它所指向的函數(shù)也是有特定類型的,函數(shù)的類型由它的返回值類型以及形參列表確定,和函數(shù)名無關(guān)。對(duì)函數(shù)指針初始化時(shí)可以采用相同類型函數(shù)的函數(shù)名或函數(shù)指針(當(dāng)然還有零指針常量)。假如有函數(shù)void test ( ),int wrong_match (int)和函數(shù)指針void (*ptf) ( )。

下面的初始化是錯(cuò)誤的,因?yàn)楹瘮?shù)指針的類型與函數(shù)的類型不匹配:

f = wrong_match;

f = & wrong_match;

ptf = wrong_match;

ptf = & wrong_match;

以下初始化及賦值是合法的:

f = test;

f = &test;

ptf = test;

ptf = &test;

f = pf;

要做出解釋的是test和&test都可以用來初始化函數(shù)指針。C語言規(guī)定函數(shù)名會(huì)被轉(zhuǎn)換為指向這個(gè)函數(shù)的指針,除非這個(gè)函數(shù)名作為&操作符或sizeof操作符的操作數(shù)(注意:函數(shù)名用于sizeof的操作數(shù)是非法的)。也就是說f = test;中test被自動(dòng)轉(zhuǎn)換為&test,而f= &test;中已經(jīng)顯示使用了&test,所以test就不會(huì)再發(fā)生轉(zhuǎn)換了。因此直接引用函數(shù)名等效于在函數(shù)名上應(yīng)用&運(yùn)算符,兩種方法都會(huì)得到指向該函數(shù)的指針。

<2>.通過函數(shù)指針調(diào)用函數(shù)

通過函數(shù)指針調(diào)用函數(shù)可以有兩種方法,直接使用函數(shù)指針或在函數(shù)指針前使用解引用運(yùn)算符,如下所示:

f = test;

ptf = test;

f ( );

(*f) ( );//指針兩側(cè)的括號(hào)非常重要,表示先對(duì)f解引用,然后再調(diào)用相應(yīng)的函數(shù)

ptf ( );

(*ptf) ( );//括號(hào)同樣不能少

以上語句都能達(dá)到調(diào)用test函數(shù)的作用。ANSI C標(biāo)準(zhǔn)將f ( )認(rèn)為是(*f)( )的簡寫形式,并且推薦使用f ( )形式,因?yàn)樗虾瘮?shù)調(diào)用的邏輯。要注意的是:如果指向函數(shù)的指針沒有初始化,或者具有0值(零指針常量),那么該指針不能在函數(shù)調(diào)用中使用。只有當(dāng)指針已經(jīng)初始化,或被賦值后指向某個(gè)函數(shù)才能安全地用來調(diào)用函數(shù)。

<3>.探究函數(shù)名

現(xiàn)在有如下程序:

#include

void test( )

{

printf("test called!\n");

}

int main()

{

void (*f) ( );

f = test;

f ( );

(*f)( );

//test++;//error,標(biāo)準(zhǔn)禁止對(duì)指向函數(shù)的指針進(jìn)行自增運(yùn)算

//test = test + 2;//error,不能對(duì)函數(shù)名賦值,函數(shù)名也不能用于進(jìn)行算術(shù)運(yùn)算

printf("%p\n", test);

printf("%p\n", &test);

printf("%p\n", *test);

return 0;

}

在我機(jī)器上的運(yùn)行結(jié)果為:

test called!

test called!

004013EE

004013EE

004013EE

這個(gè)程序中較難理解的是3個(gè)輸出語句都可以得到函數(shù)的入口地址。首先來看函數(shù)名test,它與數(shù)組名類似(注意:只是類似),是一個(gè)符號(hào)用來標(biāo)識(shí)一個(gè)函數(shù)的入口地址,在使用中函數(shù)名會(huì)被轉(zhuǎn)換為指向這個(gè)函數(shù)的指針,指針的值就是函數(shù)的入口地址,&test在前面已經(jīng)說了:顯示獲取函數(shù)的地址。對(duì)于*test,可以認(rèn)為由于test已經(jīng)被轉(zhuǎn)換成了函數(shù)指針,指向這個(gè)函數(shù),所以*test就是取這個(gè)指針?biāo)赶虻暮瘮?shù)名,而又根據(jù)函數(shù)名會(huì)被轉(zhuǎn)換指向該函數(shù)的指針的規(guī)則,這個(gè)函數(shù)也轉(zhuǎn)變成了一個(gè)指針,所以*test最終也是一個(gè)指向函數(shù)test的指針。對(duì)它們采用%p格式項(xiàng)輸出,都會(huì)得到以16進(jìn)制數(shù)表示的函數(shù)test的入口地址。注意函數(shù)的地址在編譯期是未知的,而是在鏈接時(shí)確定的。

2.返回指針的函數(shù)(指針函數(shù))

類比指針數(shù)組(還記得嗎),理解指針函數(shù)將會(huì)更加輕松。所謂指針函數(shù),就是返回指針的函數(shù),函數(shù)可以不返回任何值,也可以返回整型值,實(shí)型值,字符型值,當(dāng)然也可以返回指針值。一個(gè)指針函數(shù)的聲明:int *f(int i, int j);回想一下指針數(shù)組的聲明:char *cars[10];同樣的把它寫成好理解的形式(非業(yè)界慣例)int* f(int i, int j);這樣一來已經(jīng)十分明了了,由于( )的優(yōu)先級(jí)高于*,因此f先與()結(jié)合,所以f是一個(gè)具有兩個(gè)int型參數(shù),返回一個(gè)指向int型指針的函數(shù)。

C語言的庫函數(shù)中有很多都是指針函數(shù),比如字符串處理函數(shù),下面給出一些函數(shù)原型:

char *strcat( char *dest, const char *src );

char *strcpy( char *dest, const char *src );

char *strchr( const char *s, int c );

char *strstr( const char *src, const char*sub );

注意函數(shù)的返回值不僅僅局限于指向變量的指針,也可以是指向函數(shù)的指針。初遇這種函數(shù)的聲明可能會(huì)痛苦一點(diǎn)兒,但練習(xí)兩三次應(yīng)該是可以理解并掌握的。首先來看這個(gè)聲明:int (*function(int)) (double*,char);要了解此聲明的含義,首先來看function(int),將function聲明為一個(gè)函數(shù),它帶有一個(gè)int型的形式參數(shù),這個(gè)函數(shù)的返回值為一個(gè)指針,正是我們本將開頭講過的函數(shù)指針int (*) (double*, char);這個(gè)指針指向一個(gè)函數(shù),此函數(shù)返回int型并帶有兩個(gè)分別是double*型和char型的形參。如果使用typedef可以將這個(gè)聲明簡化:

typedefint (*ptf) (double*, char);

ptffunction(int );

要說明一下,對(duì)于typedefint (*ptf) (double*,char);注意不要用#define的思維來看待typedef,如果用#define的思維來看的話會(huì)以為(*ptf)(double*, char)是int的別名,但這樣的別名看起來好像又不是合法的名字,于是會(huì)處于迷茫狀態(tài)。實(shí)際上,上面的語句把ptf定義為一種函數(shù)指針類型的別名,它和函數(shù)指針類型int (*) (double*, char);等價(jià),也就是說ptf現(xiàn)在也是一種類型。

3.函數(shù)指針和指針函數(shù)的混合使用

函數(shù)指針不僅可以作為返回值類型,還可以作為函數(shù)的形式參數(shù),如果一個(gè)函數(shù)的形參和返回值都是函數(shù)指針,這個(gè)聲明看起來會(huì)更加復(fù)雜,例如:

void (*signal (int sig, void (*func) (intsiga)) ) ( int siga );看上去確實(shí)有些惱人,我們來一步一步的分析。現(xiàn)在要分析的是signal,因?yàn)榫o鄰signal的是優(yōu)先級(jí)最高的括號(hào),首先與括號(hào)結(jié)合,所以signal為一個(gè)函數(shù),括號(hào)內(nèi)為signal的兩個(gè)形參,一個(gè)為int型,一個(gè)為指向函數(shù)的指針。接下來從向左看,*表示指向某對(duì)象的指針,它所處的位置表明它是signal的返回值類型,現(xiàn)在可以把已經(jīng)分析過的signal整體去掉,得到void (*) ( int siga ),很清晰了吧。又是一個(gè)函數(shù)指針,這個(gè)指針與signal形參表中的第二個(gè)參數(shù)類型一樣,都是指向接受一個(gè)int型形參且不返回任何值的函數(shù)的指針。同樣地,用typedef可以將這個(gè)聲明簡化:

typedef void (*p_sig) (int);

p_sig signal(int sig, p_sig func);

這個(gè)signal函數(shù)是C語言的庫函數(shù),在signal.h中定義,用來處理系統(tǒng)中產(chǎn)生的信號(hào),是UNIX/Linux編程中經(jīng)常用到的一個(gè)函數(shù),所以在此單獨(dú)拿出來講解一下。

4.函數(shù)指針數(shù)組

還有一種較為常用的關(guān)于函數(shù)指針的用法——函數(shù)指針數(shù)組。假設(shè)現(xiàn)在有一個(gè)文件處理程序,通過一個(gè)菜單按鈕來選擇相應(yīng)的操作(打開文件,讀文件,寫文件,關(guān)閉文件)。這些操作都實(shí)現(xiàn)為函數(shù)且類型相同,分別為:

void open( );

void read( );

void write( );

void close( );

現(xiàn)在定義一個(gè)函數(shù)指針類型的別名PF:typedefvoid (*PF) ( );把以上4種操作取地址放入一個(gè)數(shù)組中,得到:

PF file_options[ ] = {

&open,

&read,

&write,

&close

};

這個(gè)數(shù)組中的元素都是指向不接受參數(shù)且不返回任何值的函數(shù)的指針,因此這是一個(gè)函數(shù)指針數(shù)組。接下來,定義一個(gè)函數(shù)指針類型的指針action并初始化為函數(shù)指針數(shù)組的第一個(gè)元素:PF* action = file_options;,如果不好理解,可以類比一下int ia[4] = {0, 1, 2, 3}; int *ip = ia;,這里PF相當(dāng)于int,這樣應(yīng)該比較好懂了。通過對(duì)指針action進(jìn)行下標(biāo)操作可以調(diào)用數(shù)組中的任一操作,如:action[2]( )會(huì)調(diào)用write操作,以此類推。在實(shí)際中,指針action可以和鼠標(biāo)或者其他GUI對(duì)象相關(guān)聯(lián),以達(dá)到相應(yīng)的目的。

5.關(guān)于指針的復(fù)雜聲明

第4點(diǎn)中的函數(shù)指針數(shù)組采用了typedef來聲明,這是應(yīng)該提倡的方法,因?yàn)樗勺x性更高。如果不使用typedef,那么分析起來就會(huì)比較復(fù)雜,結(jié)果是void (*file_options[ ]) ( );對(duì)于C語言的復(fù)雜聲明我不想講太多,因?yàn)樵趯?shí)際中用到的機(jī)會(huì)并不多,并且推薦大家多用typedef來簡化聲明的復(fù)雜度。對(duì)于分析復(fù)雜聲明有一個(gè)極為有效的方法——右左法則。右左法則的大致描述為:從未定義的變量名開始閱讀聲明,先向右看,然后向左看。當(dāng)遇到括號(hào)時(shí)就調(diào)轉(zhuǎn)閱讀的方向。括號(hào)內(nèi)的所有內(nèi)容都分析完畢就跳出括號(hào)。這樣一直繼續(xù)下去,直到整個(gè)聲明都被分析完畢。來分析一個(gè)的例子:int * (* (*fp) (int) ) [10];

閱讀步驟:

1.從未定義的變量名開始閱讀--------------------------------------------fp

2.往右看,什么也沒有,遇到了),因此往左看,遇到一個(gè)* ------一個(gè)指向某對(duì)象的指針

3.跳出括號(hào),遇到了(int) -----------------------------------一個(gè)帶一個(gè)int參數(shù)的函數(shù)

4.向左看,發(fā)現(xiàn)一個(gè)* ---------------------------------------(函數(shù))返回一個(gè)指向某對(duì)象的指針

5.跳出括號(hào),向右看,遇到[10] ------------------------------一個(gè)10元素的數(shù)組

6.向左看,發(fā)現(xiàn)一個(gè)* ---------------------------------------一個(gè)指向某對(duì)象指針

7.向左看,發(fā)現(xiàn)int -----------------------------------------int類型

所以fp是指向函數(shù)的指針,該函數(shù)返回一個(gè)指向數(shù)組的指針,此數(shù)組有10個(gè)int*型的元素。

對(duì)此我不再多舉例了,下面給出一些聲明,有興趣的朋友可以試著分析一下,答案我會(huì)在下一講中給出:

1.int *( *( *a[5]) ( ) ) ( );

2.void * (*b) ( char, int (*) ( ) );

3.float ( *(*c[10]) (int*) ) [5];

4.int ( *(*d)[2][3] ) [4][5];

5.int (*(*(*e) ( int* ))[15]) (int*);

6.int ( *(*f[4][5][6]) (int*) ) [10];

7.int *(*(*(*g)( ))[10]) ( );

前言

1.指針到底是什么

2.指針的定義及運(yùn)算

3.指針與數(shù)組的“愛恨情仇”

5.指針與結(jié)構(gòu)

6.使用指針時(shí)的“陷阱”

后記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,635評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,733評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,935評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,172評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評(píng)論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,595評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,908評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容