回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++语言中还可以使用仿函数或匿名函数。回调函数的使用可以大大提升编程的效率,这使得它在现代编程中被非常多地使用。

回调的好处

用于解耦,可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。简而言之,回调函数就是允许用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

回调还可用于通知机制。当某一事件发生时,如果使用者注册过了回调函数,则会自动执行回调函数中的内容。

回调还可用于预留逻辑,比如当时不确定或者根本不确定某一条件下要执行什么操作,就把选择权留给使用者,让使用者来实现相应的逻辑。比如某些函数库,排序算法的实现,为了能让库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑。

比如应用开发者和内核之间,应用者注册了信号处理,实则就是一种回调注册。

如下图所示:

 

回调的使用

⑴定义一个回调函数。

⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。

⑶当特定的事件或条件发生的时候,调用者使永函数指针调用回调函数对事件进行处理。

c++回调的实现

网上的例子大多太旧,没有用到现代c++的特性,还是以往函数指针的实现。

以下示例为使用现在c++14以上实现,包括lambda表达式的使用。

#include <functional>
#include <iostream>

class MyTest{
public:
    MyTest() = default;
    void doCalc(){
        //干其他事,完了
        // 执行回调
        if(myCallBack!= nullptr){
            myCallBack(1,2);
        }
    }

    using callback_t = std::function<void(const int &a, const int &b)>;

    // 注册回调
    void setCallBackHandler(const callback_t &cb){
        myCallBack = cb;
    }

private:
    // 定义回调
    callback_t myCallBack;
};

// 回调函数
void handleCallBack(const int &a,const int &b){
    std::cout << "this is from callback handleCallBack"<<std::endl;
}

int main(){

    MyTest t;

    // 回调函数
    auto f= [](const int &a,const int &b){
        std::cout << "this is from callback f"<<std::endl;
    };
    // 注册回调
    // 写法一
    t.setCallBackHandler(f);

    // 写法二
    t.setCallBackHandler([&f](auto &&a, auto &&b) {
        f(std::forward<decltype(a)>(a), std::forward<decltype(b)>(b));
    });

    // 写法三
    t.setCallBackHandler([](auto &&a, auto &&b) {
        handleCallBack(std::forward<decltype(a)>(a), std::forward<decltype(b)>(b));
    });

    t.doCalc();
}

以上对形参使用了类似如auto的写法,这是c++14的特性。

至于参数为啥要用&&?是为了做到完美转发。要做到这一点,参数 必须成为通用引用(见条款 24)。

条款 33:对 auto&&形参使用 decltype 来 std::forward。
C++14 最令人兴奋的特性之一是在参数规范中使用 auto 的泛型 lambda。 

在 lambda 中,我们可以通过检查的参数 x 的类型,来判断实参是左值还是右值。

条款 28 解释到,如果将左值实参传递给通用引用,该参数的类型将成为左值引用,如果传递的是右值,该参数将成为一个右值引用。这意味着,在 lambda 中,我们可以通过检查的参数 x 的类型,来判断实参是左值还是右值。

decltype(见条款 3)给了我们一个实现途径。如果是左值,decltype(x)得到的类型是左值引用,如果是右值,decltype(x)得到的是右值引用。

需要铭记的是:对 auto&&形参使用 decltype 来 std::forward。

以上条款详细内容参见《Effective.Modern.C++》一书。

c语言的实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int (callBack)(const void *buffer,size_t size,char *p_out);

void callFunc(callBack *my_callback, char *p_out) {
    printf("callFunc\n");
    const void *buffer = NULL;
    my_callback(buffer,0,p_out); //传入值可以随便填
}

int callBackFunc(const void *buffer, size_t size, char *p_out){
    printf("callBackFunc\n");
    memset(p_out,0x00,sizeof(char)*100);
    strcpy(p_out,"this is Callback:this is string.");
    return 1;
}

int main(int argc,char *args[]){
    char p_out[100];
    callFunc(callBackFunc,p_out);
    printf("%s\n",p_out);
    return 0;
}

引用

什么是回调函数?为什么要使用回调函数?如何使用回调函数?_llzhang_fly的博客-CSDN博客_回调函数

C++回调函数的基本理解和使用_一度凡尘的博客-CSDN博客_回调函数 

回调函数的实质——什么是回调函数,为什么要使用回调函数_斗趣的博客-CSDN博客_回调函数

c++11线程池的实现原理及回调函数的使用_特立独行的猫a的博客-CSDN博客_c++多线程回调函数 深入理解:回调函数_极客点儿的博客-CSDN博客_回调函数

C++学习之回调函数_欧特克_Glodon的博客-CSDN博客 

关于C++ 回调函数(callback) 精简且实用_zhoupian的博客-CSDN博客_c++ callback

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐