tags: C/C++ Pointer
寫在前面
最近看網絡, 發現系統調用中的信号函數的聲明有點奇怪, 如下:
void (*signal(int sig, void (*func)(int)))(int);
雖然書中給出了解釋, 但是奈何自己的C語言基礎不好, 看着比較費勁, 下面就重新研究一下C語言中的函數指針, 包括以下的幾種情況.
- 函數指針的聲明(兩種, 算上
的話就是三種, 其實采用宏定義的方式也可以, 但是宏定義隻是簡單的變量替換, 不建議在這裡使用);C++
- 函數指針作為函數傳回值時函數的聲明;
- 函數指針作為函數參數時函數的聲明.
傳統的這種寫法(例如上面提到的 signal()
函數)看起來實在是不好了解, 特别是括号的嵌套法則很容易出錯, 這裡還是推薦先對函數指針進行聲明之後再寫入函數的聲明中, 也相當于是一種劃分子問題的思想.
函數指針的聲明
先來看教科書中給出的函數指針的聲明方式:(這也是最基本的一種)
類型辨別符 (*指針變量名) (形參清單)
舉個例子, 對于一個隻傳回兩數之和的函數, 其形參清單當然是
(int, int)
, 如下所示:
// 函數聲明
int add(int a, int b);
int add(int, int);// 可以不加形參變量名, 推薦這種寫法
// 函數定義
int add(int a, int b){return a+b;}
采用基本寫法聲明指向這類函數的函數指針:
int (*funp) (int, int);
這裡我采用了
這類
, 是因為對于其他函數, 隻要其為一種傳回值為
int
, 形參清單為
(int, int)
的函數, 那麼就可以用
funp
這個指針指向該函數, 例如:
int minus(int a,int b){return a-b;}
funp = minus;
當然, 雖然初學者經常使用基本寫法來聲明函數指針, 但是這種方法在比較複雜的情況中(我後面要提到的兩種情況)聲明容易出錯, 下面來看通過給出别名的方式聲明函數指針, 仍以相加函數為例:(寫成大寫是類型定義的變量命名規則)
typedef int (*FUNP)(int, int);
這種寫法雖然看起來跟上面沒有很大差別, 但是在聲明函數傳回值為函數指針的時候比較有用, 稍後我介紹具體的例子.
當然, 對于習慣使用
C++
的朋友來說, 使用
using
關鍵字指定類型别名更加友善, 上面的函數指針聲明可以這樣寫:
using FUNP = int (*)(int, int);
這種寫法與
typedef
的寫法是等價的, 都是給出了一個
FUNP
類型作為函數指針的類型, 使用起來比較友善.
傳回函數指針的函數的聲明
仍以上面的兩數相加為例, 如果這時候想定義一個新的函數
func
, 這個新的函數傳入兩個數, 但是傳回值是上面定義的
add()
函數的函數指針, 那這個函數的聲明應該怎麼寫呢?
先來看第一種.
基本形式
延續上面基本的函數指針聲明形式, 可以寫出如下的函數聲明:
int add(int, int);
int (*func(int, int))(int, int);
可以看出來這次的聲明就比較複雜了, 在指針變量名後面還有一個括号, 為友善了解, 大家可以從内層括号往外看:
-
是函數func(int, int)
的帶參數清單的聲明形式, 其參數類型為兩個func
;int
-
的傳回值類型是func
, 即一個函數指針, 該指針所指向的函數是: **參數類型是兩個int(*)(int, int)
, 傳回值類型是int
**的函數.int
下面是具體的執行個體代碼:
#include <stdio.h>
int add(int, int);
int (*func(int, int))(int, int);
int main() {
int a = 5, b = 5;
int a1 = 3, b1 = 3;
// 函數調用, `func(a, b)`傳回一個函數指針
// 後面的`(a,b)`才是真正進行相加運算的形參
int ans = func(a1, b1)(a, b);
printf("ans=%d\n", ans);
return 0;
/*
a1=3
b1=3
ans=10
*/
}
int add(int x, int y) { return x + y; }
// 傳回函數指針的函數, 傳回的函數指針,形參清單為兩個int, 傳回值為int
int (*func(int a1, int b1))(int, int) {
printf("a1=%d\n", a1);
printf("b1=%d\n", b1);
return add;
}
在
func
函數的調用中, 為避免引起混淆, 傳參時我寫了兩組值, 分别是
a, b
和
a1, b1
, 首先傳入的
a1, b1
并不是真正作加法的參數, 而是僅被
func
内部的
printf
列印輸出了,
func(a1, b1)
傳回了一個函數指針, 這之後的
(a, b)
才是真正用作
add()
函數形參的兩數.
這裡還要注意, 聲明與定義的寫法架構是一樣的, 但是在函數定義的時候需要給出形參變量名, 這就要注意形參變量名不能加在外部, 而是要加在
func()
這個括号裡面, 外部(即函數體左大括号左邊的小括号内)的形參類型是
func
傳回的函數指針所指向的函數的形參清單, 剛接觸函數指針時, 這一點尤其容易出錯.
采用typedef定義函數指針類型
通過上面的例子可以看出, 用基本的函數指針聲明方法來聲明傳回值為函數指針的函數的時候, 往往是很複雜的, 還要照顧到形參清單中變量名的位置, 一不小心括号套錯了也會引發編譯錯誤. 下面來看一種寫法很簡潔的函數聲明方式, 就是通過上面介紹的采用
typedef
為函數指針定義新的類型(别名)的方式.
還是上面的例子, 先通過
typedef
給出函數指針類型的聲明, 然後直接就能以類型别名
FUNP
作為函數的傳回值類型了.
typedef int (*FUNP)(int, int); // function ptr
int add(int, int);
FUNP fun(int, int);
具體的代碼如下:
#include <stdio.h>
typedef int (*FUNP)(int, int); // function ptr
int add(int, int);
FUNP fun(int, int);
int main() {
int a = 5, b = 5;
int a1 = 3, b1 = 3;
int ans = fun(a1, b1)(a, b);
printf("ans=%d\n", ans);
return 0;
/* a1=3 */
/* b1=3 */
/* ans=10 */
}
int add(int x, int y) { return x + y; }
FUNP fun(int a1, int b1) {
printf("a1=%d\n", a1);
printf("b1=%d\n", b1);
return add;
}
可以看出這種寫法雖然與上面基本形式等價, 但是了解起來很容易, 也不容易出錯. 實際使用中還是推薦第二種寫法.
函數指針作為參數的函數聲明
這裡就比較簡單了, 直接在形參清單中做文章即可, 這裡也就不贅述了. 依舊是兩種寫法.
基本寫法
#include <stdio.h>
int add(int, int);
int func(int, int, int (*)(int, int));
int main(int argc, char *argv[]) {
int a = 3, b = 3;
int ans = func(a, b, add);
printf("ans=%d \n", ans); // ans=6
return 0;
}
int add(int a, int b) { return a + b; }
int func(int x, int y, int (*f1)(int, int)) { return f1(x, y); }
typedef寫法
#include <stdio.h>
int add(int, int);
typedef int (*FUNP)(int, int);
int func(int, int, FUNP);
int main(int argc, char *argv[]) {
int a = 3, b = 3;
int ans = func(a, b, add);
printf("ans=%d \n", ans); // ans=6
return 0;
}
int add(int a, int b) { return a + b; }
int func(int x, int y, FUNP f1) { return f1(x, y); }
結束語
有了上面的各類型的函數聲明的分析, 相信大家已經能看出來
signal()
函數的聲明意味着什麼吧:
void (*signal(int sig, void (*func)(int)))(int);
- 看函數
的形參清單signal()
;(int sig, void(*func)(int))
- 第一參數為
類型;int
- 第二參數為傳回值類型為
, 形參類型為void
的函數指針;int
- 看
的傳回值, 是一個傳回值類型為signal()
, 形參類型為void
的函數指針.int