“在C語言中,函數本身不是變數,但可以定義指向函數的指標,這種指標可以被賦值、存放於數組之中,傳遞給函數及作為函數的傳回值等” --《The C Programming Language Second Edition》
下面給出幾個簡單的例子來說明指向函數的指標。
第一個例子說明指向函數的指標如何說明、賦值、調用。
#include
#define TESTDATE 100
int func(int a) /* func用於列印一個整數 */
{
return printf("%d\n",a);
}
main()
{
int (*FunctionPionter)(int a);
FunctionPionter = func;
(*FunctionPionter)(TESTDATE);
return 0;
}
其中重點語句的含義如下:
int (*FunctionPionter)(int a);
FunctionPionter: 指向一個返回整數的函數的指標,這個指標有一個整數參數。
FunctionPionter = func;
將FunctionPionter指向函數func;其中函數必須已經定義,且函數和函數指標的說明的傳回值必須一致。
(*FunctionPionter)(TESTDATE);
通過函數指標調用函數;因為函數指標已經指向函數,所以用*取出函數指標的內容就為函數本身。
下面這個例子顯示如何將指向函數的指標傳遞給函數、作為函數的傳回型別。在這個例子中,有三個函數:
hello:返回字元指標的函數,用來返回字串“hello world!\n”
RetFunc:返回一個指向函數的指標的函數,且返回指標所指的那個函數為一個返回字元指標的函數。
call:返回一個void *型的指標,且call有一個指向函數的指標的參數,且這個函數指標返回一個字元指標
#include
#define MAX 100
main()
{
void *call(char *(*)());
char *(*RtnFunc())();
/* 上面兩個說明有些複雜 */
printf("%s",call(RtnFunc()));
return 0;
}
char *hello()
{
return "Hello World!\n";
}
char *(*RtnFunc())()
{
return hello;
}
void *call(char *(*func)())
{
return (*func)();
}
上面的例子中,main()無法直接調用hello函數,利用兩個函數分別返回hello和調用hello,實現了在main()中調用hello。雖然,似乎這個程式顯得多餘但卻很好的說明了如何把指向函數的指標傳遞給函數、作為函數的返回。其中call函數利用了void *型指標的靈活機制,使得call的適用性大為增加,這也正是指向函數的指標的優點之一。同樣的例子是《The C Programming Language Second Edition》中下面這個函數調用:
qsort((void **) lineptr, 0, nlines-1, (int (*)(void *, void *))(numeric ? numcmp : strcmp));
其中,使用了兩次強制類型轉換,其中第二甚至是利用指向函數的指標,將函數的類型進行了轉換。當然上面語句在某些編譯器上無法通過,因為某些編譯器要求條件表達:
運算式1 ? 運算式2 : 運算式3
中運算式2與運算式3的類型相同。當然這樣的要求是不符合ANSI標準的。在ANSI標準中,如果運算式2與運算式3的類型不同,則結果的類型由類型轉換規則決定。當然,我們可以變同一下,先將兩個函數的類型進行強制轉換來達到目的:
qsort((void **) lineptr, 0, nlines-1, numeric ? (int (*)(void *, void *))numcmp : (int (*)(void *, void *))strcmp));
對於如何直接說明一個像RtnFunc一樣返回指向函數的指標的函數,我查閱了不少資料,都沒有找到答案,最後是自己硬著頭皮摸索出來的。由此,我也對C的複雜說明有了更深刻的體會,將在以後的技術日記中寫出來。當然在我看來,過多的、不合適的使用這些複雜說明,並不是一種好的編程風格,因為它將使程式變得難以理解,同時也增加了出錯的可能性。
一個比較好的折衷的方法是使用typedef來使程式的含義明朗。下面給出用typedef給寫上面那個程式的例子,其中定義個一個類型PtoFun,用typedef說明PtoFun是指向函數的指標類型,指標所指的函數返回一個字元指標,且沒有參數。
#include
#define MAX 100
typedef char *(*PtoFun)();
main()
{
void *call(PtoFun);
PtoFun RtnFunc();
printf("%s",call(RtnFunc()));
return 0;
}
char *hello()
{
return "Hello World!\n";
}
PtoFun RtnFunc()
{
return hello;
}
void *call(PtoFun func)
{
return (*func)();
}
改寫後的程式的可讀性大為增加,給人一目瞭然的感覺。