C語言指針導學(4)——分清函數(shù)指針和指針函數(shù)
http://blog.csdn.net/porscheyin/article/details/3461632/
四.分清函數(shù)指針和指針函數(shù)
關于指針和數(shù)組斬不斷理還亂的恩怨還真是說了不少,不過現(xiàn)在應該已經(jīng)理清了。有了上一講的基礎,本講的內容相對來說就比較容易理解了。
1.指向函數(shù)的指針(函數(shù)指針)
來分析這樣一個聲明,void (*f) ( );雖然()的優(yōu)先級高于*,但由于有括號存在,首先執(zhí)行的是解引用,所以f是一個指針;接下來執(zhí)行( ),表明f指向一個函數(shù),這個函數(shù)不返回任何值。現(xiàn)在得出結論:f是一個指向不接受參數(shù)且不返回任何值的函數(shù)的指針,簡稱函數(shù)指針(pointer to function)。
對比一下int(*p) [100],p是一個指向含有100個整型元素的數(shù)組的指針,它們有一個共同的特點:指針聲明符(*)和標識符(f或p)都被限制在一個括號中,由于括號的優(yōu)先級是最高的,所以我們從標識符開始由內向外分析,即可得到以上結果。
<1>.初始化
注意指向函數(shù)的指針(函數(shù)指針)指向的是函數(shù)而非普通的變量,它所指向的函數(shù)也是有特定類型的,函數(shù)的類型由它的返回值類型以及形參列表確定,和函數(shù)名無關。對函數(shù)指針初始化時可以采用相同類型函數(shù)的函數(shù)名或函數(shù)指針(當然還有零指針常量)。假如有函數(shù)void test ( ),int wrong_match (int)和函數(shù)指針void (*ptf) ( )。
下面的初始化是錯誤的,因為函數(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ù)名會被轉換為指向這個函數(shù)的指針,除非這個函數(shù)名作為&操作符或sizeof操作符的操作數(shù)(注意:函數(shù)名用于sizeof的操作數(shù)是非法的)。也就是說f = test;中test被自動轉換為&test,而f= &test;中已經(jīng)顯示使用了&test,所以test就不會再發(fā)生轉換了。因此直接引用函數(shù)名等效于在函數(shù)名上應用&運算符,兩種方法都會得到指向該函數(shù)的指針。
<2>.通過函數(shù)指針調用函數(shù)
通過函數(shù)指針調用函數(shù)可以有兩種方法,直接使用函數(shù)指針或在函數(shù)指針前使用解引用運算符,如下所示:
f = test;
ptf = test;
f ( );
(*f) ( );//指針兩側的括號非常重要,表示先對f解引用,然后再調用相應的函數(shù)
ptf ( );
(*ptf) ( );//括號同樣不能少
以上語句都能達到調用test函數(shù)的作用。ANSI C標準將f ( )認為是(*f)( )的簡寫形式,并且推薦使用f ( )形式,因為它更符合函數(shù)調用的邏輯。要注意的是:如果指向函數(shù)的指針沒有初始化,或者具有0值(零指針常量),那么該指針不能在函數(shù)調用中使用。只有當指針已經(jīng)初始化,或被賦值后指向某個函數(shù)才能安全地用來調用函數(shù)。
<3>.探究函數(shù)名
現(xiàn)在有如下程序:
#include
void test( )
{
printf("test called!\n");
}
int main()
{
void (*f) ( );
f = test;
f ( );
(*f)( );
//test++;//error,標準禁止對指向函數(shù)的指針進行自增運算
//test = test + 2;//error,不能對函數(shù)名賦值,函數(shù)名也不能用于進行算術運算
printf("%p\n", test);
printf("%p\n", &test);
printf("%p\n", *test);
return 0;
}
在我機器上的運行結果為:
test called!
test called!
004013EE
004013EE
004013EE
這個程序中較難理解的是3個輸出語句都可以得到函數(shù)的入口地址。首先來看函數(shù)名test,它與數(shù)組名類似(注意:只是類似),是一個符號用來標識一個函數(shù)的入口地址,在使用中函數(shù)名會被轉換為指向這個函數(shù)的指針,指針的值就是函數(shù)的入口地址,&test在前面已經(jīng)說了:顯示獲取函數(shù)的地址。對于*test,可以認為由于test已經(jīng)被轉換成了函數(shù)指針,指向這個函數(shù),所以*test就是取這個指針所指向的函數(shù)名,而又根據(jù)函數(shù)名會被轉換指向該函數(shù)的指針的規(guī)則,這個函數(shù)也轉變成了一個指針,所以*test最終也是一個指向函數(shù)test的指針。對它們采用%p格式項輸出,都會得到以16進制數(shù)表示的函數(shù)test的入口地址。注意函數(shù)的地址在編譯期是未知的,而是在鏈接時確定的。
2.返回指針的函數(shù)(指針函數(shù))
類比指針數(shù)組(還記得嗎),理解指針函數(shù)將會更加輕松。所謂指針函數(shù),就是返回指針的函數(shù),函數(shù)可以不返回任何值,也可以返回整型值,實型值,字符型值,當然也可以返回指針值。一個指針函數(shù)的聲明:int *f(int i, int j);回想一下指針數(shù)組的聲明:char *cars[10];同樣的把它寫成好理解的形式(非業(yè)界慣例)int* f(int i, int j);這樣一來已經(jīng)十分明了了,由于( )的優(yōu)先級高于*,因此f先與()結合,所以f是一個具有兩個int型參數(shù),返回一個指向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ù)的聲明可能會痛苦一點兒,但練習兩三次應該是可以理解并掌握的。首先來看這個聲明:int (*function(int)) (double*,char);要了解此聲明的含義,首先來看function(int),將function聲明為一個函數(shù),它帶有一個int型的形式參數(shù),這個函數(shù)的返回值為一個指針,正是我們本將開頭講過的函數(shù)指針int (*) (double*, char);這個指針指向一個函數(shù),此函數(shù)返回int型并帶有兩個分別是double*型和char型的形參。如果使用typedef可以將這個聲明簡化:
typedefint (*ptf) (double*, char);
ptffunction(int );
要說明一下,對于typedefint (*ptf) (double*,char);注意不要用#define的思維來看待typedef,如果用#define的思維來看的話會以為(*ptf)(double*, char)是int的別名,但這樣的別名看起來好像又不是合法的名字,于是會處于迷茫狀態(tài)。實際上,上面的語句把ptf定義為一種函數(shù)指針類型的別名,它和函數(shù)指針類型int (*) (double*, char);等價,也就是說ptf現(xiàn)在也是一種類型。
3.函數(shù)指針和指針函數(shù)的混合使用
函數(shù)指針不僅可以作為返回值類型,還可以作為函數(shù)的形式參數(shù),如果一個函數(shù)的形參和返回值都是函數(shù)指針,這個聲明看起來會更加復雜,例如:
void (*signal (int sig, void (*func) (intsiga)) ) ( int siga );看上去確實有些惱人,我們來一步一步的分析。現(xiàn)在要分析的是signal,因為緊鄰signal的是優(yōu)先級最高的括號,首先與括號結合,所以signal為一個函數(shù),括號內為signal的兩個形參,一個為int型,一個為指向函數(shù)的指針。接下來從向左看,*表示指向某對象的指針,它所處的位置表明它是signal的返回值類型,現(xiàn)在可以把已經(jīng)分析過的signal整體去掉,得到void (*) ( int siga ),很清晰了吧。又是一個函數(shù)指針,這個指針與signal形參表中的第二個參數(shù)類型一樣,都是指向接受一個int型形參且不返回任何值的函數(shù)的指針。同樣地,用typedef可以將這個聲明簡化:
typedef void (*p_sig) (int);
p_sig signal(int sig, p_sig func);
這個signal函數(shù)是C語言的庫函數(shù),在signal.h中定義,用來處理系統(tǒng)中產(chǎn)生的信號,是UNIX/Linux編程中經(jīng)常用到的一個函數(shù),所以在此單獨拿出來講解一下。
4.函數(shù)指針數(shù)組
還有一種較為常用的關于函數(shù)指針的用法——函數(shù)指針數(shù)組。假設現(xiàn)在有一個文件處理程序,通過一個菜單按鈕來選擇相應的操作(打開文件,讀文件,寫文件,關閉文件)。這些操作都實現(xiàn)為函數(shù)且類型相同,分別為:
void open( );
void read( );
void write( );
void close( );
現(xiàn)在定義一個函數(shù)指針類型的別名PF:typedefvoid (*PF) ( );把以上4種操作取地址放入一個數(shù)組中,得到:
PF file_options[ ] = {
&open,
&read,
&write,
&close
};
這個數(shù)組中的元素都是指向不接受參數(shù)且不返回任何值的函數(shù)的指針,因此這是一個函數(shù)指針數(shù)組。接下來,定義一個函數(shù)指針類型的指針action并初始化為函數(shù)指針數(shù)組的第一個元素:PF* action = file_options;,如果不好理解,可以類比一下int ia[4] = {0, 1, 2, 3}; int *ip = ia;,這里PF相當于int,這樣應該比較好懂了。通過對指針action進行下標操作可以調用數(shù)組中的任一操作,如:action[2]( )會調用write操作,以此類推。在實際中,指針action可以和鼠標或者其他GUI對象相關聯(lián),以達到相應的目的。
5.關于指針的復雜聲明
第4點中的函數(shù)指針數(shù)組采用了typedef來聲明,這是應該提倡的方法,因為它可讀性更高。如果不使用typedef,那么分析起來就會比較復雜,結果是void (*file_options[ ]) ( );對于C語言的復雜聲明我不想講太多,因為在實際中用到的機會并不多,并且推薦大家多用typedef來簡化聲明的復雜度。對于分析復雜聲明有一個極為有效的方法——右左法則。右左法則的大致描述為:從未定義的變量名開始閱讀聲明,先向右看,然后向左看。當遇到括號時就調轉閱讀的方向。括號內的所有內容都分析完畢就跳出括號。這樣一直繼續(xù)下去,直到整個聲明都被分析完畢。來分析一個的例子:int * (* (*fp) (int) ) [10];
閱讀步驟:
1.從未定義的變量名開始閱讀--------------------------------------------fp
2.往右看,什么也沒有,遇到了),因此往左看,遇到一個* ------一個指向某對象的指針
3.跳出括號,遇到了(int) -----------------------------------一個帶一個int參數(shù)的函數(shù)
4.向左看,發(fā)現(xiàn)一個* ---------------------------------------(函數(shù))返回一個指向某對象的指針
5.跳出括號,向右看,遇到[10] ------------------------------一個10元素的數(shù)組
6.向左看,發(fā)現(xiàn)一個* ---------------------------------------一個指向某對象指針
7.向左看,發(fā)現(xiàn)int -----------------------------------------int類型
所以fp是指向函數(shù)的指針,該函數(shù)返回一個指向數(shù)組的指針,此數(shù)組有10個int*型的元素。
對此我不再多舉例了,下面給出一些聲明,有興趣的朋友可以試著分析一下,答案我會在下一講中給出:
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]) ( );