写在前面
最近看网络, 发现系统调用中的信号函数的声明有点奇怪, 如下:
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`的函数指针.
看到这里, 希望大家对含有函数指针的函数声明有了一个新的认识. 如果有任何问题也欢迎大家一起交流学习~