1. 程式人生 > >C語言指標導學(4)——分清函式指標和指標函式

C語言指標導學(4)——分清函式指標和指標函式

四.分清函式指標和指標函式

關於指標和陣列斬不斷理還亂的恩怨還真是說了不少,不過現在應該已經理清了。有了上一講的基礎,本講的內容相對來說就比較容易理解了。

1.指向函式的指標(函式指標)

來分析這樣一個宣告,void (*f) ( );雖然( )的優先順序高於*,但由於有括號存在,首先執行的是解引用,所以f是一個指標;接下來執行( ),表明f指向一個函式,這個函式不返回任何值。現在得出結論:f是一個指向不接受引數且不返回任何值的函式的指標,簡稱函式指標(pointer to function)

對比一下int (*p) [100]p是一個指向含有100個整型元素的陣列的指標,它們有一個共同的特點:指標宣告符

(*)和識別符號(fp)都被限制在一個括號中,由於括號的優先順序是最高的,所以我們從識別符號開始由內向外分析,即可得到以上結果。

<1>.初始化

注意指向函式的指標(函式指標)指向的是函式而非普通的變數,它所指向的函式也是有特定型別的,函式的型別由它的返回值型別以及形參列表確定,和函式名無關。對函式指標初始化時可以採用相同型別函式的函式名或函式指標(當然還有零指標常量)。假如有函式void test ( )int wrong_match (int)和函式指標void (*ptf) ( )

下面的初始化是錯誤的,因為函式指標的型別與函式的型別不匹配:

f = wrong_match;

f = & wrong_match;

ptf = wrong_match;

ptf = & wrong_match;

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

f = test;

f = &test;

ptf = test;

ptf = &test;

f = pf;

要做出解釋的是test&test都可以用來初始化函式指標。C語言規定函式名會被轉換為指向這個函式的指標,除非這個函式名作為&操作符或sizeof操作符的運算元(注意:函式名用於sizeof的運算元是非法的)也就是說f = test;test被自動轉換為&test,而f = &test;

中已經顯示使用了&test,所以test就不會再發生轉換了。因此直接引用函式名等效於在函式名上應用&運算子,兩種方法都會得到指向該函式的指標。

<2>.通過函式指標呼叫函式

通過函式指標呼叫函式可以有兩種方法,直接使用函式指標或在函式指標前使用解引用運算子,如下所示:

f = test;

ptf = test;

f ( );

(*f) ( );//指標兩側的括號非常重要,表示先對f解引用,然後再呼叫相應的函式

ptf ( );

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

以上語句都能達到呼叫test函式的作用。ANSI C標準將f ( )認為是(*f)( )的簡寫形式,並且推薦使用f ( )形式,因為它更符合函式呼叫的邏輯。要注意的是:如果指向函式的指標沒有初始化,或者具有0(零指標常量),那麼該指標不能在函式呼叫中使用。只有當指標已經初始化,或被賦值後指向某個函式才能安全地用來呼叫函式。

<3>.探究函式名

現在有如下程式:

#include <stdio.h>

void test( )

{

printf("test called!/n");

}

int main( )

{

void (*f) ( );

    f = test;

f ( );

    (*f)( );

    //test++;//error,標準禁止對指向函式的指標進行自增運算

//test = test + 2;//error,不能對函式名賦值,函式名也不能用於進行算術運算

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

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

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

  return 0;

}

在我機器上的執行結果為:

test called!

test called!

004013EE

004013EE

004013EE

這個程式中較難理解的是3個輸出語句都可以得到函式的入口地址。首先來看函式名test,它與陣列名類似(注意:只是類似),是一個符號用來標識一個函式的入口地址,在使用中函式名會被轉換為指向這個函式的指標,指標的值就是函式的入口地址,&test在前面已經說了:顯示獲取函式的地址。對於*test,可以認為由於test已經被轉換成了函式指標,指向這個函式,所以*test就是取這個指標所指向的函式名,而又根據函式名會被轉換指向該函式的指標的規則,這個函式也轉變成了一個指標,所以*test最終也是一個指向函式test的指標。對它們採用%p格式項輸出,都會得到以16進位制數表示的函式test的入口地址。注意函式的地址在編譯期是未知的,而是在連結時確定的。

2.返回指標的函式(指標函式)

類比指標陣列(還記得嗎),理解指標函式將會更加輕鬆。所謂指標函式,就是返回指標的函式,函式可以不返回任何值,也可以返回整型值,實型值,字元型值,當然也可以返回指標值。一個指標函式的宣告:int *f(int i, int j);回想一下指標陣列的宣告:char *cars[10];同樣的把它寫成好理解的形式(非業界慣例)int* f(int i, int j);這樣一來已經十分明瞭了,由於( )的優先順序高於*,因此f先與( )結合,所以f是一個具有兩個int型引數,返回一個指向int型指標的函式。

C語言的庫函式中有很多都是指標函式,比如字串處理函式,下面給出一些函式原型:

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 );

注意函式的返回值不僅僅侷限於指向變數的指標,也可以是指向函式的指標。初遇這種函式的宣告可能會痛苦一點兒,但練習兩三次應該是可以理解並掌握的。首先來看這個宣告:int (*function(int)) (double*, char);要了解此宣告的含義,首先來看function(int),將function宣告為一個函式,它帶有一個int型的形式引數,這個函式的返回值為一個指標,正是我們本將開頭講過的函式指標int (*) (double*, char);這個指標指向一個函式,此函式返回int型並帶有兩個分別是double*型和char型的形參。如果使用typedef可以將這個宣告簡化:

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

ptffunction( int );

要說明一下,對於typedefint (*ptf) (double*, char);注意不要用#define的思維來看待typedef,如果用#define的思維來看的話會以為(*ptf)(double*, char)int的別名,但這樣的別名看起來好像又不是合法的名字,於是會處於迷茫狀態。實際上,上面的語句把ptf定義為一種函式指標型別的別名,它和函式指標型別int (*) (double*, char);等價,也就是說ptf現在也是一種型別。

3.函式指標和指標函式的混合使用

函式指標不僅可以作為返回值型別,還可以作為函式的形式引數,如果一個函式的形參和返回值都是函式指標,這個宣告看起來會更加複雜,例如:

void (*signal (int sig, void (*func) (int siga)) ) ( int siga );看上去確實有些惱人,我們來一步一步的分析。現在要分析的是signal,因為緊鄰signal的是優先順序最高的括號,首先與括號結合,所以signal為一個函式,括號內為signal的兩個形參,一個為int型,一個為指向函式的指標。接下來從向左看,*表示指向某物件的指標,它所處的位置表明它是signal的返回值型別,現在可以把已經分析過的signal整體去掉,得到void (*) ( int siga ),很清晰了吧。又是一個函式指標,這個指標與signal形參表中的第二個引數型別一樣,都是指向接受一個int型形參且不返回任何值的函式的指標。同樣地,用typedef可以將這個宣告簡化:

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

p_sig signal(int sig, p_sig func);

這個signal函式是C語言的庫函式,在signal.h中定義,用來處理系統中產生的訊號,是UNIX/Linux程式設計中經常用到的一個函式,所以在此單獨拿出來講解一下。

4.函式指標陣列

還有一種較為常用的關於函式指標的用法——函式指標陣列。假設現在有一個檔案處理程式,通過一個選單按鈕來選擇相應的操作(開啟檔案,讀檔案,寫檔案,關閉檔案)。這些操作都實現為函式且型別相同,分別為:

void open( );

void read( );

void write( );

void close( );

現在定義一個函式指標型別的別名PFtypedef void (*PF) ( );把以上4種操作取地址放入一個數組中,得到:

PF file_options[ ] = {

&open,

&read,

&write,

&close

};

這個陣列中的元素都是指向不接受引數且不返回任何值的函式的指標,因此這是一個函式指標陣列。接下來,定義一個函式指標型別的指標action並初始化為函式指標陣列的第一個元素:PF* action = file_options;,如果不好理解,可以類比一下int ia[4] = {0, 1, 2, 3}; int *ip = ia;,這裡PF相當於int,這樣應該比較好懂了。通過對指標action進行下標操作可以呼叫陣列中的任一操作,如:action[2]( )會呼叫write操作,以此類推。在實際中,指標action可以和滑鼠或者其他GUI物件相關聯,以達到相應的目的。

5.關於指標的複雜宣告

4點中的函式指標陣列採用了typedef來宣告,這是應該提倡的方法,因為它可讀性更高。如果不使用typedef,那麼分析起來就會比較複雜,結果是void (*file_options[ ]) ( );對於C語言的複雜宣告我不想講太多,因為在實際中用到的機會並不多,並且推薦大家多用typedef來簡化宣告的複雜度。對於分析複雜宣告有一個極為有效的方法——右左法則。右左法則的大致描述為:從未定義的變數名開始閱讀宣告,先向右看,然後向左看。當遇到括號時就調轉閱讀的方向。括號內的所有內容都分析完畢就跳出括號。這樣一直繼續下去,直到整個宣告都被分析完畢。來分析一個的例子:int * (* (*fp) (int) ) [10];

閱讀步驟:

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

2.往右看,什麼也沒有,遇到了),因此往左看,遇到一個* ------一個指向某物件的指標

3.跳出括號,遇到了(int) -----------------------------------一個帶一個int引數的函式

4.向左看,發現一個* ---------------------------------------(函式)返回一個指向某物件的指標

5.跳出括號,向右看,遇到[10] ------------------------------一個10元素的陣列

6.向左看,發現一個* ---------------------------------------一個指向某物件指標

7.向左看,發現int ----------------------------------------- int型別

所以fp是指向函式的指標,該函式返回一個指向陣列的指標,此陣列有10int*型的元素。

對此我不再多舉例了,下面給出一些宣告,有興趣的朋友可以試著分析一下,答案我會在下一講中給出:

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]) ( );

 

前言

後記