回调函数的原理、为什么使用回调函数


在看代码时遇到回调函数,从代码上看其实就是对函数指针的应用,但是不太明白使用回调函数对整个代码结构有什么意义?为什么不直接调用函数接口实现相关功能呢?看了一些资料,结合代码先做一个小小的总结

什么是回调函数

先放一段简单的代码

/*******************************API********************************/
#include"callback.h"

CCALLBACK::CCALLBACK(){}
CCALLBACK::~CCALLBACK(){}
void CCALLBACK::regist_callback(callBack cb)
{
    p_cb = cb;
}

void CCALLBACK::use_callBack(std::string str,int n)
{
    //TODO
    //回调函数
    p_cb(str,n);
}
/*******************************main program*****************/
#include"callback.h"
#include<iostream>

//回调函数
void callbackFun(std::string str,int n)
{
    for(int i=0;i<n;i++)
        std::cout<<str<<std::endl;
}

int main()
{
    CCALLBACK m_callback;
    std::string str = "hello world";
    int n = 3;

    m_callback.regist_callback(callbackFun);
    m_callback.use_callBack(str,n);

    return 0;
}

回调的概念:
第一部分API是API库文件,第二部分main program是项目的主程序,主程序调用API中的库函数实现某个功能
回调函数callbackFun在主程序中定义
注册回调函数regist_callback在API库文件中定义

什么是回调呢,
首先,主程序中使用注册回调函数regist_callback把回调函数的地址告诉API
然后,主程序使用APIuse_callBack函数实现某个功能,在这个过程中库函数use_callBack调用了回调函数callbackFun,而回调函数在执行时调用了库函数use_callBack的两个参数string str和int n
这种相互调用关系被称为回调

为什么使用回调函数

上述大致就是回调函数的使用过程了,根据项目的实际需求,回调函数的实现、需要调用的参数会随之变化,对于回调函数的使用原理基本是不变的。
但是为什么要使用回调函数呢?在这个简单示例里,完全可以把回调函数中的实现放入到API中,在主程序中直接调用库函数就可以实现同样的功能效果,更不用注册回调函数这么麻烦。

/*******************************API********************************/
void use_callBack(string str,int n)
{
	for (int i = 0; i < n; i++)
		cout << str << endl;
}

/*******************************main program*****************/
int main()
{
	string str="hello world";
	int n = 3;
	use_callBack(str,n);

	system("pause");
    return 0;
}

当使用回调函数这一机制时,无非是把传递的参数由普通的数据类型变成函数指针罢了。考虑到一个问题,有时在我们自己的程序中调用API中的函数时,有的API可能仅提供给了我们头文件和封装好的API库,这使得我们无法看到API函数中的实现,也不可以进行修改。当我们需求有变化时,上述所说的直接调用库函数来实现某些功能就无法实现了。

例如:
我们将这个例子做的复杂一些

  1. API中use_callBack函数的功能修改为获取当前的时间——时:分:秒,API只提供头文件和封装好的库文件
  2. 回调函数callbackFun来显示当前的时间,首先我们将显示方式设置为hour/min/sec,格式为24小时制
/*******************************main program*****************/
#include"callback.h"
#include<iostream>
#include <string>
#include <cstring>
#include <unistd.h>

using namespace std;

void callbackFun(int hour,int min, int sec)
{
    char time_now[20];
    memset(time_now,0,20);
    sprintf(time_now,"%02d/%02d/%02d",hour,min,sec);
    std::cout<<time_now<<endl;
}

int main()
{
    CCALLBACK m_callback;
    m_callback.regist_callback(callbackFun);
    while(1)
    {
        m_callback.use_callBack();
        sleep(1);
    }

    return 0;
}

那么,如果我们的需求变了,想要12小时制显示,并且格式想将hour/min/sec改为hour:min:sec,这时只需要修改我们的回调函数就可以了。

void callbackFun(int hour,int min, int sec)
{
    char time_now[20];
    memset(time_now,0,20);
    if(hour>12)hour-=12;//将24小时制改为12小时制
    sprintf(time_now,"%02d/%02d/%02d",hour,min,sec);
    std::cout<<time_now<<endl;
}

这个例子可能不是那么恰当,但是如果再继续将这个例子改变一下,我们创建两个类,其中第一个类仍然是之前用到的API中的CCALLBACK,我们把主程序中的内容放到另一个类B中去,类B是我们自己设计的模块,这样由CCALLBACK类来获取时间,由类B显示时间,当需求再次变化时例如时间的可视化显示,可视化界面的设计等等,这样我们只需要设计我们这边的B中的相关函数便可以了,无需修改API中的内容。

回调函数使用总结

使用回调函数有时并非是一种好的策略,而大量使用回调函数更不是一个好的选择。回调函数本身是一种破坏系统结构的设计思路 [1],当我们的代码很庞大时,回调函数会绝对的变化系统的运行轨迹,执行顺序,调用顺序,让读到你的代码的人非常的懵头转向,给代码优化带来困扰。

在两个模块之间相互传递参数我们也可以在声明全局变量时添加extern关键字,但是如果需要对这种全局变量进行修改,还需要引入lockunlock对数据进行保护,如果锁比较多,还可能会造成死锁。

但是到底怎么去使用回调函数,我想可能要根据实际情况和自己累积的编程经验去判断了。

参考链接:[1]https://blog.csdn.net/llzhang_fly/article/details/104933969

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值