一: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. LeetCode_Distinct Subsequences

    Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence ...

  2. WPF 如何缓解大量控件加载缓慢的问题

    最近有一个项目需要加载大量的控件,导致系统出现卡顿问题,经过几天的努力,终于搞定了,写一下备忘. 解决方案是首次加载时只显示可见区域控件,之后使用辅助线程进行分批加载,将分批加载的线程优先级别设置为空 ...

  3. UltraChart画柱状图上面显示数值

    http://www.cnblogs.com/kevin-h-wang/archive/2013/06/05/UltraChart.html 1.柱状图上显示数值 ? //第一种方法 this.Ult ...

  4. PERL 实现微信登录

    get 请求: https://login.weixin.qq.com/jslogin? appid=wx782c26e4c19acffb &redirect_uri=https%3A%2F% ...

  5. < welcome > 一起学习,进步,分享。

    现在时间:2014-3-24 hello world my blog. 第一次做博客,欢迎各路朋友指教.慢慢的分享学习到得东西,本人目前正在做IOS,也在学习数据库简单地应用. 作为一个IOS开发者, ...

  6. Net-Snmp安装配置

    1. 下载安装 net-snmp安装程序:net-snmp-5.4.2.1-1.win32.exe Perl安装程序:ActivePerl-5.10.0.1004-MSWin32-x86-287188 ...

  7. 使用skin++进行MFC界面美化范例

    1.下载skin++皮肤库和皮肤库,skin++皮肤库主要包括:SkinPPWTL.dll,SkinPPWTL.lib,SkinPPWTL.h这三个文件.把这三个文件 拷贝到工程目录下. 2.在工程中 ...

  8. Javascript构造函数学习

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

  9. How to check for and disable Java in OS X

    Java used to be deeply embedded in OS X, but in recent versions of the OS it's an optional install. ...

  10. Android开发 ADB server didn't ACK, failed to start daemon解决方案

    有时候在打开ddms的时候,会看到adb会报如题的错误,解决方案是打开任务管理器,(ctrl+shift+esc),然后关掉adb.exe的进程,重启eclipse就ok了. 还有许多无良商家开发的垃 ...