C语言函数指针在形参列表和返回值中的函数声明写法

 
Category: C_C++

写在前面

最近看网络, 发现系统调用中的信号函数的声明有点奇怪, 如下:

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, ba1, 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`的函数指针;
  2. signal()的返回值, 是一个返回值类型为`void`, 形参类型为`int`的函数指针.

看到这里, 希望大家对含有函数指针的函数声明有了一个新的认识. 如果有任何问题也欢迎大家一起交流学习~