Algorithm-Pattern
  • Introduction
  • C & C++
    • C语言
      • C/C++编译器
      • 宏的使用
      • 编译过程
      • 指针 & 数组
      • 柔性数组
      • 函数指针 & 回调函数
      • C标准库之stdio
      • C标准库之string
      • C标准库之errno
      • C标准库之stdarg
      • C标准库之regex
    • C++基础语法
      • 自增(++) & 自减(--)
      • c语言到c++
      • 可变模板参数
      • 强制类型转换
      • C/C++类型转换的本质
      • 指针 & 引用
      • const的用法
      • static的用法
      • 重要的关键字(一)
      • 重要的关键字(二)
      • 内存申请和释放
      • 内联函数
      • 函数 & 运算符重载
      • 面向对象之封装
      • 构造函数 & 析构函数
      • 面向对象之继承
      • 面向对象之多态
      • 泛型编程
      • 异常
      • 再谈函数指针
    • C++并发编程
      • C++的锁
      • 并发与多线程
    • C++高级特性
      • 函数对象
      • 移动语义 & 完美转发
      • lambda表达式
      • RTTI技术
      • RAII技术
      • 智能指针
      • 模板的特化
      • C++静态库和动态库
      • 内存溢出和内存泄漏
    • STL基础
      • String
      • array/vector/list
      • deque/priority_queue
      • set/map
      • unordered_set/unordered_map
      • algorithm_1
      • functional
      • allocator
    • C++标准库
      • IO
      • Tuple
      • regex
      • bitset
      • numeric
    • STL深入源码
      • vector内部实现
      • deque内部实现
      • sort函数实现
    • 第三方库
      • JsonCpp
      • ProtoBuf
  • 数据结构
    • 线性表
    • 字符串
    • 栈和队列
    • 二叉树
    • 平衡二叉树
    • 平衡多路搜索树
    • 树结构的延申
    • 图
    • 二进制
    • 散列表
  • 算法基础
    • 排序算法
    • 查找算法
    • 数学问题
    • 并查集
    • 递归算法
    • 附加——主定理
    • Catalan数
  • 算法设计思想
    • 滑动窗口思想
    • BFS/DFS
    • 二分法
    • 回溯法
    • 贪心算法
    • 分治法
    • 动态规划
    • 分支限界算法
    • 有限状态机(FSM)
  • LeetCode系列
    • 死磕二叉树
    • 股票买卖问题
    • 打家劫舍问题
    • 跳跃游戏问题
    • 括号匹配问题
    • 石子游戏问题
    • 子序列问题
    • 数组 & 矩阵
    • 排列 & 组合
  • 经典算法问题
    • 几何问题
    • 区间问题
    • 背包问题
    • 石子堆问题
    • 表达式求值
  • 面试题
    • 数据结构和算法基础
    • 程序设计题
      • 实现双线程交替打印
      • C++实现读写锁
      • 实现阻塞队列
      • 实现环形队列
      • 实现线程池
      • 实现智能指针
      • 实现string类
      • 实现高性能local cache
      • 实现内存池
      • 生产者-消费者模型
      • 设计定时器
    • 经典的算法题
    • C++面试题总结
    • 面试算法题总结
由 GitBook 提供支持
在本页
  • 1、指针函数
  • 2、函数指针
  • 2.1、三种声明方式
  • 3、回调函数

这有帮助吗?

  1. C & C++
  2. C语言

函数指针 & 回调函数

上一页柔性数组下一页C标准库之stdio

最后更新于4年前

这有帮助吗?

1、指针函数

指针函数: 本质是一个函数,不过它的返回值是一个指针。其声明的形式如下所示:

ret *func(args, ...);

指针函数和普通函数没有区别,但有一点需要注意的是: 避免出现返回局部变量指针的情况。看例子:

int* getSum(int n)
{
    if (n < 0)
        return NULL;
    int sum = 0;   // static int sum = 0;
    for (int i = 0; i < n; i++)
    {
        sum += i;
    }
    return &sum;
}

int main() {
    cout << *getSum(10) << endl;
    cout << "Wait for a while...\n" << endl;
    int* iptr = getSum(10);
    cout << *iptr << endl;
    return 0;
}

在GCC编译器下没有输出,也就是说程序运行到第14行就强行退出了,原因在于,一般的局部变量是存放于栈区的,当函数结束,栈区的变量就会释放掉,如果我们在函数内部定义一个变量,在使用一个指针去指向这个变量,当函数调用结束时,这个变量的空间就已经被释放,这时就算返回了该地址的指针,也不一定会得到正确的值。并且该地址可能会被后面的程序所使用,这时候得到的就不是我们想要的结果。甚至更严重的是,如果因此访问到了不可访问的内容,很有可能造成段错误等程序崩溃的情况。(需要说明的是该程序在VC++编译器下可以正常运行,这与编译器的优化有关,但不符合C语言的特性)

解决方法,将局部变量sum声明成static int, 用static修饰的变量是静态变量,而静态变量是存放在数据段的,它的生命周期存在于整个程序运行期间,只要程序没有结束,该变量就会一直存在,所以该指针就能一直访问到该变量。还有一种解决方案是使用全局变量,因为全局变量也是放在数据段的,但是并不推荐使用全局变量。

函数指针 的本质是一个指针,该指针的地址指向了一个函数。函数的定义是存在于代码段,因此,每个函数在代码段中有着自己的入口地址,函数指针就是指向代码段中函数入口地址的指针。其声明的形式如下所示:

ret (*p)(args, ...);

ret为返回值,*p作为一个整体,代表的是指向该函数的指针,args为形参列表。其中p被称为函数指针变量 。

  1. 函数指针的定义形式中的数据类型是指函数的返回值的类型。

  2. 指向函数的指针变量不是固定指向哪一个函数的,而只是表示定义了一个这样类型的变量,它是专门用来存放函数的入口地址的;在程序中把哪一个函数的地址赋给它,它就指向哪一个函数。

  3. 在给函数指针变量赋值时,只需给出函数名,而不必给出参数。

  4. 在一个程序中,指针变量p可以先后指向不同的函数,但一个函数不能赋给一个不一致的函数指针(即不能让一个函数指针指向与其类型不一致的函数)。

  5. 定义了一个函数指针并让它指向了一个函数后,对函数的调用可以通过函数名调用,也可以通过函数指针调用(即用指向函数的指针变量调用)。

  6. 函数指针只能指向函数的入口处,而不可能指向函数中间的某一条指令。不能用*(p+1)来表示函数的下一条指令。

  7. 函数指针变量常用的用途之一是把指针作为参数传递到其他函数。、

int func(int a, int b)
{
    cout << "func(int, int)" << endl;
    return 0;
}

int func(int a, int b, int c)
{
    cout << "func(int, int, int)" << endl;
    return 0;
}

//方式1 :声明一种函数类型
typedef int(MY_FUNC)(int, int);  // MY_FUNC是一个函数

//方式2 :声明一种函数类型的指针类型
typedef int(*MY_FUNC_P)(int, int);// MY_FUNC_P是一个函数指针

int ()
{
    //1.方式1举例
    MY_FUNC* fp = NULL;
    fp = func;
    fp(15, 20);

    //2.方式2举例
    MY_FUNC_P fp1 = NULL;
    fp1 = func;
    fp1(25, 30);

    //方式3 :直接通过指针类型创建,不需用typedef预定义。
    int(*fp3)(int, int) = NULL;    // pf3是指向某函数的指针
    fp3 = func; //fp3 ---> func(int,int)
    fp3(27, 89);

    //实际上在给函数指针赋值的时候,是会发生函数重载匹配的
    //在调用函数指针的时候,所调用的函数就已经固定了。
    int(*fp4)(int, int, int) = NULL;
    fp4 = func; //fp4 ---> func(int,int,int)
    fp4(10, 30, 30);
    
    // 获取函数指针的大小
    unsigned psize = sizeof ( int (*) (int, int));
    cout << psize << endl;  // 4

    return 0;
}

函数注册和回调:回调函数是对函数指针的应用,通过使用函数指针来调用一个函数,而函数注册就是把函数指针作为参数传递出去便于别的模块使用的过程。回调函数可以把调用者与被调用者分开,所以调用者不关心谁是被调用者以及被调用者如何实现。

在设计模式中注册回调的方式叫做回调模式。在SDK开发中,为增强开发者的SDK通用性,排序或者一些算法逻辑需要使用者进行编写。这时候就需要向SDK传递回调函数。 注册回调能使下层主动与上层通信。从而避免了上层不停询问下层的模式。SDK的接口会提供一个注册回调函数,来规范回调函数的格式,如返回值,参数等。使用者编写一个固定格式的回调函数,来调用SDK提供的注册回调函数。

在c中注册回调的实现方式是通过函数指针,在c++中可以通过function和bind实现。

// SDK
typedef void(*P)(string s);//使用函数指针通常先typedef
P p = nullptr;//sdk模块创建函数指针对象

void print(P p, string str)//回调函数
{
    p = prin;
    p(str);
}
//使用者
void my_print(string str)//回调函数
{
    printf(str.c_str());
}

int test1()
{
    print(my_print, "hello word");
    return 0;
}

许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

2、函数指针

2.1、三种声明方式

3、回调函数

✏️
✏️
🎤
✏️