天天看點

C語言函數指針在形參清單和傳回值中的函數聲明寫法

tags: C/C++ Pointer

寫在前面

最近看網絡, 發現系統調用中的信号函數的聲明有點奇怪, 如下:

void (*signal(int sig, void (*func)(int)))(int);      

雖然書中給出了解釋, 但是奈何自己的C語言基礎不好, 看着比較費勁, 下面就重新研究一下C語言中的函數指針, 包括以下的幾種情況.

  1. 函數指針的聲明(兩種, 算上​

    ​C++​

    ​的話就是三種, 其實采用宏定義的方式也可以, 但是宏定義隻是簡單的變量替換, 不建議在這裡使用);
  2. 函數指針作為函數傳回值時函數的聲明;
  3. 函數指針作為函數參數時函數的聲明.
傳統的這種寫法(例如上面提到的​

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

可以看出來這次的聲明就比較複雜了, 在指針變量名後面還有一個括号, 為友善了解, 大家可以從内層括号往外看:

  1. ​func(int, int)​

    ​​是函數​

    ​func​

    ​​的帶參數清單的聲明形式, 其參數類型為兩個​

    ​int​

    ​;
  2. ​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);      
  1. 看函數​

    ​signal()​

    ​​的形參清單​

    ​(int sig, void(*func)(int))​

    ​;
  1. 第一參數為​

    ​int​

    ​類型;
  2. 第二參數為傳回值類型為​

    ​void​

    ​​, 形參類型為​

    ​int​

    ​的函數指針;
  1. 看​

    ​signal()​

    ​​的傳回值, 是一個傳回值類型為​

    ​void​

    ​​, 形參類型為​

    ​int​

    ​的函數指針.

繼續閱讀