一:What?(什么是回调函数)


 

回调函数图文讲解

 

  

    谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。

 例如Win32下的窗口过程函数就是一个典型的回调函数。

一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。由于S并不知道C提供的B叫甚名谁,所以S会约定B的接

口规范(函数原型),然后由C提前通过S的一个函数R告诉S自己将要使用B函数,这个过程称为回调函数的注册,R称为注册函数。

再看看回调函数的庐山面目

下面的SmartOS中Zegbe通讯函数回调的注册  (由于是公司商业代码,所以不贴逻辑代码)

 virtual void Register(TransportHandler handler, void* param = NULL)
{
if(handler)
{
_handler = handler;
_param = param; if(!Opened) Open();
}
else
{
_handler = NULL;
_param = NULL;
}
} protected:
virtual bool OnOpen() { return true; }
virtual void OnClose() { }
virtual bool OnWrite(const byte* buf, uint len) = ;
virtual uint OnRead(byte* buf, uint len) = ; // 是否有回调函数
bool HasHandler() { return _handler != NULL; } // 引发数据到达事件
virtual uint OnReceive(byte* buf, uint len)
{
if(_handler) return _handler(this, buf, len, _param); return ;
}

这个是整个通讯流类里面的回调函数使用

下面举个例子说明回调函数的过程:

      ①:  比喻我打电话向你请教问题,当然是个难题

      ②:  你一时也想不出解决方法,我又不能拿着电话在那里傻等,于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了。   (注册回调函数)

      ③:  XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理。故事到此结束。   (回调函数操作)

其中,你后来打手机告诉我结果便是一个“回调”过程;我的手机号码必须在以前告诉你,这便是注册回调函数;我的手机号码应该有效并且手机能够接收到你的呼叫,这是回调函数必须符合接口规范。

   回调函数的编程特点是:“异步+回调 ”

二   Why? ( 回调函数实现过程)



    //def.h
#include <iostream>
#include <stdio.h>
using namespace std; typedef enum
{
CB_MOVE = , //
CB_COMEBACK, //
CB_BUYEQUIIP, //
}cb_type; typedef void(*cb_func)(void *); class CCommu //模块类
{
public:
CCommu()
{
memset(func_list, , sizeof(cb_func) *(CB_BUYEQUIIP +));
memset(func_args, , sizeof(void *) *(CB_BUYEQUIIP +));
} int reg_cb(cb_type type, cb_func func, void *args = NULL)//注册回调函数
{
if(type <= CB_BUYEQUIIP)
{
func_list[ type ] = func;
func_args[type] = args;
return ;
}
}
public:
cb_func func_list[CB_BUYEQUIIP + ] ; //函数指针数组
void * func_args[CB_BUYEQUIIP +];
}; //Gamestart.h
#include "def.h" class CGameStart
{ public:
CGameStart();
~CGameStart();
void Init();
void run();
void Execute(); //一些回调函数
void static Move(void *args);
void static Comeback(void *args);
void static Buyequip(void *args); public:
CCommu *pCommu; }; //Gamestart.cpp
#include "Gamestart.h" CGameStart::CGameStart():pCommu(NULL)
{} void CGameStart::Init() //初始化的时候,注册回调函数
{
pCommu = new CCommu;
pCommu ->reg_cb(CB_MOVE, Move , this);
pCommu->reg_cb (CB_COMEBACK, Comeback,this );
} void CGameStart::run()
{
Init();
} void CGameStart::Execute()
{
cout<<"callback funciton is running"<<endl; }
CGameStart::~CGameStart()
{
if(pCommu != NULL)
{
delete pCommu;
pCommu = NULL;
}
} void CGameStart::Move(void *args)
{
CGameStart *pGame = (CGameStart *)args;
pGame -> Execute();
}
void CGameStart::Comeback(void *args)
{
//char *str = (char *)args;
//cout << str <<endl;
} //main.cpp
#include "Gamestart.h" int main()
{ CGameStart *pGame = new CGameStart;
pGame -> run();
if(pGame->pCommu->func_list[CB_MOVE] != NULL)//回调函数的触发
{
pGame->pCommu->func_list[CB_MOVE](pGame->pCommu->func_args[CB_MOVE]);
}
return ;
}

时代在不断进步,SDK不再是古老的API接口,C++面向对象编程被广泛的用到各种库中,因此回调机制也可以采用C++的一些特性来实现。
 通过前面的讲解,其实我们不难发现回调的本质便是:SDK定义出一套接口规范,应用程序按照规定实现它。这样一说是不是很简单,

想想我们C++中的继承,想想我们亲爱的抽象基类......于是,我们得到以下的代码:

 /// sdk.h
#ifndef __SDK_H__
#define __SDK_H__ class Notifier // 回调类,应用程序需从此派生
{
public:
virtual ~Notifier() { }
virtual void got_answer(const char* answer) = ; // 纯虚函数,用户必须实现它
}; class Sdk // Sdk提供服务的类
{
public:
Sdk(Notifier* pnotifier); // 用户必须注册指向回调类的指针
void help_me(const char* question);
protected:
void do_it();
protected:
Notifier* m_pnotifier; // 用于保存回调类的指针
}; #define//__SDK_H__ /// sdk.cpp
#include "sdk.h"
#include "windows.h"
#include <iostream>
using namespace std; Sdk::Sdk(Notifier* pnotifier) : m_pnotifier(pnotifier)
{
} void Sdk::help_me(const char* question)
{
cout << "help_me: " << question << endl;
do_it();
} void Sdk::do_it()
{
cout << "thinking..." << endl;
Sleep( );
cout << "think out." << endl;
cout << "call him." << endl;
m_pnotifier->got_answer( "2." );
} /// app.cpp
#include "sdk.h" class App : public Notifier // 应用程序实现一个从回调类派生的类
{
public:
App( const char* name ) : m_sdk(this), m_name(name) // 将this指针传入
{
}
void ask( const char* question )
{
m_sdk.help_me( question );
}
void got_answer( const char* answer ) // 实现纯虚接口
{
cout << m_name << " got_answer: " << answer << endl;
}
protected:
Sdk m_sdk;
const char* m_name;
}; int main()
{
App app("ABC");
app.ask( "1+1=?");
return ;
}

三: When?  (什么时候使用回调)


如果你是SDK的使用者,一旦别人制定了回调机制,那么你被迫得使用回调函数,因此这个问题只对SDK设计者有意义。
 从引入的目的看,回调大致分为三种:
 1) SDK有消息需要通知应用程序,比如定时器被触发;
 2) SDK的执行需要应用程序的参与,比如SDK需要你提供一种排序算法;
 3) SDK的操作比较费时,但又不能让应用程序阻塞在那里,于是采用异步方式,让调用函数及时返回,SDK另起线程在后台执行操作,待操作完成后再将结果通知应用程序。

END!

参考部分网络资源

欢迎大家一起交流 ,分享程序员励志故事。   幸福的程序员 QQ群: 

  

C++之------回调函数的更多相关文章

  1. 小兔JS教程(三)-- 彻底攻略JS回调函数

    这一讲来谈谈回调函数. 其实一句话就能概括这个东西: 回调函数就是把一个函数当做参数,传入另一个函数中.传进去的目的仅仅是为了在某个时刻去执行它. 如果不执行,那么你传一个函数进去干嘛呢? 就比如说对 ...

  2. 嵌入式&iOS:回调函数(C)与block(OC)传 参/函数 对比

    C的回调函数: callBack.h 1).声明一个doSomeThingCount函数,参数为一个(无返回值,1个int参数的)函数. void DSTCount(void(*CallBack)(i ...

  3. 嵌入式&iOS:回调函数(C)与block(OC)回调对比

    学了OC的block,再写C的回调函数有点别扭,对比下区别,回忆记录下. C的回调函数: callBack.h 1).定义一个回调函数的参数数量.类型. typedef void (*CallBack ...

  4. 理解 JavaScript 回调函数并使用

    JavaScript中,函数是一等(first-class)对象:也就是说,函数是 Object 类型并且可以像其他一等对象(String,Array,Number等)一样使用.它们可以"保 ...

  5. 关于js的回调函数的一点看法

    算了一下又有好几个月没写博客了,最近在忙公司android的项目,所以也就很少抽时间来写些东西了.刚闲下来,我就翻了翻之前看的东西.做了android之后更加感觉到手机端开发的重要性,现在做nativ ...

  6. JS学习:第二周——NO.1回调函数

    [回调函数] 定义:把一个函数的定义阶段,作为参数,传给另一个函数: 回调函数调用次数,取决于条件: 回调函数可以传参: 回调函数可以给变this指向,默认是window: 回调函数没有返回值,for ...

  7. 【java回调】java两个类之间的回调函数传递

    背景交代:熟悉用js开发的cordovaAPP:对java一窍不通的我,老师让做一个监测用户拍照事件的功能,无奈没有找到现成的库,无奈自己动手开发java插件~~0基础java GreenHand,祝 ...

  8. Java|今天起,别再扯订阅和回调函数

    编程史上有两个令人匪夷所思的说辞,一个是订阅,一个是回调函数. 我想应该还有很多同学为“事件的订阅”和“回调函数”所困扰,因为事情本来就不应该按这个套路来解释. 多直白,所谓的“回调函数”你完全可以线 ...

  9. C++ 回调函数的定义与用法

    一回调函数 我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理.用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函 ...

  10. 通过修改i8042prt端口驱动中类驱动Kbdclass的回调函数地址,达到过滤键盘操作的例子

    同样也是寒江独钓的例子,但只给了思路,现贴出实现代码 原理是通过改变端口驱动中本该调用类驱动回调函数的地方下手 //替换分发函数 来实现过滤 #include <wdm.h> #inclu ...

随机推荐

  1. OpenSceneGraph是一个开源的三维引擎

    http://www.osgchina.org/OpenSceneGraph是一个开源的三维引擎,被广泛的应用在可视化仿真.游戏.虚拟现实.科学计算.三维重建.地理信息.太空探索.石油矿产等领域.OS ...

  2. Windows资源文件里VarFileInfo的Translation(EXE的语言描述信息)

    /* The following line should only be modified for localized versions. */ /* It consists of any numbe ...

  3. kibana 访问IP分布图

  4. HashMap非线程安全分析

    通过各方资料了解,HashMap不是线程安全的,但是为什么不是线程安全的,在什么情况下会出现问题呢? 1. 下面对HashMap做一个实验,两个线程,并发写入不同的值,key和value相同,最后再看 ...

  5. mybatis和hibernate对比

    Hibernate是一个数据库表和java对象之间完全映射的框架,java开发人员直接对java对象操作,而不对数据库表进行操作: Mybatis是对SQL语句和java对象进行映射,仍需要开发人员编 ...

  6. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅳ

    2.4.4 堆的算法 我们用长度为 N + 1的私有数组pq[]来表示一个大小为N的堆,我们不会使用pq[0],堆元素放在pq[1]至pq[N]中.在排序算法中,我们只能通过私有辅助函数less()和 ...

  7. docker初步

    [Note,]由于docker的局限性,docker只能运行在64位的系统中 docker软件应用程序可以重复地运行在任何地方,因为它的容器包含了所有的环境依赖关系! docker有三种方式运行 作为 ...

  8. Javascript构造函数学习

    我们经常会用JS的构造函数实现Java语言中的继承,今天整理一下构造函数的相关属性及说明. 下面定义一个构造函数: function Person(name, sex, age) { this.nam ...

  9. 关于sem_unlink什么时候删除信号量

    sem_unlink在man手册里有这么一段话: sem_unlink() removes the named semaphore referred to by name. The semaphore ...

  10. LR选择哪种方式录制

    LR选择哪种方式录制,有以下考虑原则: 1.基于浏览器的应用程序推荐使用HTML-basic script方式录制 2.不是基于浏览器的应用程序推荐使用URL-basic script方式录制 3.如 ...