標籤:
一、指向函數的指標
函數名可以在運算式中被解讀成“指向函數的指標”
,因此,正如代碼清單 2-2 的實驗那樣,寫成 func 就可以取得指向函數的指標。
“指向函數的指標”本質上也是指標(地址),所以可以將它賦給指標型變數。
比如有下面的函數原型:
int func(double d);
儲存指向此函數的指標的變數的聲明如下:
int (*func_p)(double);
然後寫成下面這樣,就可以通過 func_p 調用 func,
int (*func_p)(double); // 聲明func_p = func; // 將func 賦給func_pfunc_p(0.5); // 此時,func_p 等同於func
將“指向函數的指標”儲存在變數中的技術經常被運用在如下場合:
- GUI 中的按鈕控制項記憶“當自身被按下的時候需要調用的函數”
- 根據“指向函數的指標的數組”對處理進行分配
-
後者的“指向函數的指標的數組”,像下面這樣使用:
int (*func_table[])(double) = { func0, func1, func2, func3,};┊func_table[i](0.5); // 調用func_table[i]的函數,參數為0.5
使用上面的寫法,不用寫很長的 switch case,只需通過 i 的值就可以對處理進行分配。
哦?不明白為什嗎?
確實,像
int (*func_p)(double); // 指向函數的指標
還有,
int (*func_table[])(double); // 指向函數的指標的數組
這樣的聲明,是不能用普通的方法來讀的。
關於這種聲明的解讀方式,會在後面進行說明。
二、關於指向函數的指標引起的混亂
正如上面說明的那樣,對於 C 語言,運算式中的函數名可以被解讀成“指向函數的指標”。
在訊號處理、事件驅動的程式中,這種特性往往以回呼函數的形式被使用。
/*如果發生SIGSEGV(Segmentation falut),回呼函數segv_handler */signal(SIGSEGV, segv_handler);
可是,如果基於之前說明過的 C 語言聲明規則,int func()這樣的聲明會被解釋為“返回 int的函數”,如果 函數名在運算式中,只是取出 func,則解釋成“指向返回 int 函數的指標”,是不是感覺很怪異?如果一定要使用指向函數的指標,必須要寫成&func。
對於上面訊號處理的函數,寫成
signal(SIGSEGV, &segv_handler);
這樣,實際上也能順利地執行。
相反,像
void (*func_p)();
這樣,變數 func_p 聲明為指向函數的指標,進行函數調用的時候,可以寫成
func_p();
但是像 int func()這種聲明,都是用 func()這樣的方式進行調用的,從對稱性的角度考慮,對於 void (*func_p)(),必須要寫成
(*func_p)();*
* 早期的 C 語言中,好像也只能這麼寫……
這樣也是能毫無問題地執行的。
是不是感覺 C 語言的關於指向函數的指標的文法比較混亂?
混亂產生的原因就是:“運算式中 的函數可以解讀成‘指向函數的指標’”這個意圖不明的規則(難道就是為了和數組保持一致?)。
為了照顧到這種混亂,ANSI C 標準對文法做了以下例外的規定:
- 運算式中的函數自動轉換成“指向函數的指標”。但是,當函數是地址運算子&或者 sizeof 運算子的運算元時,運算式中的函數名不能變換成“指向函數的指標”。
- 函數調用運算子()的運算元不是“函數”,而是“函數的指標”。
如果對“指向函數的指標”使用解引用*,它暫時會成為函數,但是因為在運算式中,所以它會被瞬間地變回成“指向函數的指標”。
結論就是,即使對“指向函數的指標”使用*運算子,也是對牛彈琴,因為此時的運算子*發揮不了任何作用。
因此,下面的語句也是能順利執行的,
(**********printf)("hello, world\n"); // 無論如何,*就是什麼也沒做
《征服 C 指標》摘錄4:函數 與 指標