C++中的函数类型(1):函数指针

素颜马尾好姑娘i 2024-03-26 14:36 161阅读 0赞

在C++中如何传递一个函数,比如作为类或者其他函数的参数。
需要有表示函数的类型,有几个种不同的方式。
本来想把所有函数类型总结一下,函数指针整理完了,零零碎碎的就不少,分几篇吧。

函数指针

从c语言继承来的方式,最基本也是最本质的表示函数类型的方式。
函数就是一些连续的指令,程序加载到内存中后,本质上一个内存地址表示函数入口。
程序运行时调用函数,准备好参数堆栈、寄存器后,跳转到函数入口地址,开始执行相应的逻辑。
执行完成后,清理堆栈、保存数据,返回调用方。

相较其他方式,函数指针类型定义不是很直观。优点是,不依赖于已有函数(参照下面集中定义方式),最直接的定义方式。
对人来说可读性稍差,对编译器比较直接。

示例代码

上示例代码,看下面的说明:

  1. // 一个例子函数
  2. int func(int a) {
  3. return a+1;
  4. }
  5. typedef int (*func_type) (int); // typedef定义函数指针类型
  6. using func_type_other = int (*) (int); // using定义指针类型语法
  7. func_type ptr2 = func; // 给函数指针赋值,指向具体函数,函数名本身就是指针
  8. func_type ptr3 = &func; // 给函数指针赋值,指向具体函数,对函数名去地址操作,效果等同直接用函数名
  9. cout << ptr2(1); // 输出2
  10. cout << ptr3(2); // 输出3
  11. // 函数指针作为参数使用
  12. void call_func(func_type one_func) {
  13. cout << "call_func: " << one_func(5) << endl; // 输出 6
  14. }
  15. call_func(func); // 直接传入函数名
  16. call_func(ptr2); // 传入函数指针

定义函数指针基础步骤(定义方式1)

1)首先写一个函数原型的声明,上面的例子就是:
int func(int a);
2)把函数名用括号括起来,然后把函数名替换成“*类型名”,其他不变。
输入参数,只提供类型,参数名可有可无。
int (*func_type) (int a);
3)前面加上typedef:
typedef int (*func_type) (int a);

  1. 大功告成,func_type就是要定义的函数指针类型,可以定义函数指针的变量或参数。

说明

a)typedef声明中,类型前面的星号*是必需的,只有指针类型才能和函数名代表的地址类型匹配。
b)函数名(func)和对函数名取地址操作(&func),等效。
c)定义步骤中,1)和2)按先后顺序,3)步放在哪里都可以。

用decltype声明函数指针(定义方式2)

通过使用C++新增的decltype可以简化函数指针定义,前提是有一个同类型的函数。
如果是开发函数库时,在没有函数的情况下,定义一个接口类型,就只能用上面的方式去定义了。

  1. // 例子函数
  2. int func(int a) {
  3. return a+1;
  4. }
  5. typedef decltype(func) *func_type_two;
  6. using func_type_thress = decltype(func)*; // 注意末尾星号*,func_type_thress和func_type_two完全等价,只是新旧语法差异
  7. func_type_two ptr4 = func;
  8. // func_type_thress ptr4 = func;
  9. cout << ptr4(6);
  10. // 函数指针作为参数使用
  11. void call_func(func_type one_func) {
  12. cout << "call_func: " << one_func(5) << endl; // 输出 6
  13. }
  14. call_func(ptr4); // 可以调用,说明不同方式定义的函数指针类型相同

说明

a)类型前面的星号*是必需的,使用方式和前面的一样。
b)使用把ptr4传递给call_func,检验不同方式定义的函数指针类型相同。
其实,只要是能把函数名赋值给函数指针,就说明是相同的类型。

用终极必杀auto声明函数指针(定义方式3)

搞不定了就用auto兜底。同样,需要现有一个参考函数。对于直接定义参数类型的不合适。
适用于在代码处理过程中,存储或者传递一个函数指针。
写起来简单,对于不熟悉代码逻辑的人,可读性并不好。

  1. // 例子函数
  2. int func(int a) {
  3. return a+1;
  4. }
  5. auto *ptr5 = func;
  6. auto ptr6 = func;
  7. auto ptr7 = &func; // ptr5, ptr6, ptr7都是同样类型的函数指针
  8. auto &ptr8 = func; // ptr8是函数的引用,编译器内部处理函数引用和指针一样,通过下面的调用可以验证
  9. ptr8(8);
  10. call_func(ptr8);

函数引用,效果等同函数指针(定义方式4)

  1. // 例子函数
  2. int func(int a) {
  3. return a+1;
  4. }
  5. auto &ptr8 = func; // ptr8是函数的引用,编译器内部处理函数引用和指针一样,通过下面的调用可以验证
  6. ptr8(8);
  7. call_func(ptr8);

using定义模板类型指针

这中能力用typedef无法实现,但又是模板库中是基本能力。应该优先使用using语法。

  1. template <typename T>
  2. using Compare = bool (*) (T&, T&);
  3. template <typename T>
  4. void sort(vector<T> vec, Compare<T> compare);

总结

后续新增的各种“语法糖”简化了定义方式,本质上行最基础的定义方式是一致的。
关键要明白指针指向函数入口地址的本质,其他的只是辅助编译器识别函数指针的类型,包括输入、输出类型。

发表评论

表情:
评论列表 (有 0 条评论,161人围观)

还没有评论,来说两句吧...

相关阅读