天天看点

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​

    ​的函数指针.

继续阅读