C++之------回调函数
一: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++之------回调函数的更多相关文章
- 小兔JS教程(三)-- 彻底攻略JS回调函数
这一讲来谈谈回调函数. 其实一句话就能概括这个东西: 回调函数就是把一个函数当做参数,传入另一个函数中.传进去的目的仅仅是为了在某个时刻去执行它. 如果不执行,那么你传一个函数进去干嘛呢? 就比如说对 ...
- 嵌入式&iOS:回调函数(C)与block(OC)传 参/函数 对比
C的回调函数: callBack.h 1).声明一个doSomeThingCount函数,参数为一个(无返回值,1个int参数的)函数. void DSTCount(void(*CallBack)(i ...
- 嵌入式&iOS:回调函数(C)与block(OC)回调对比
学了OC的block,再写C的回调函数有点别扭,对比下区别,回忆记录下. C的回调函数: callBack.h 1).定义一个回调函数的参数数量.类型. typedef void (*CallBack ...
- 理解 JavaScript 回调函数并使用
JavaScript中,函数是一等(first-class)对象:也就是说,函数是 Object 类型并且可以像其他一等对象(String,Array,Number等)一样使用.它们可以"保 ...
- 关于js的回调函数的一点看法
算了一下又有好几个月没写博客了,最近在忙公司android的项目,所以也就很少抽时间来写些东西了.刚闲下来,我就翻了翻之前看的东西.做了android之后更加感觉到手机端开发的重要性,现在做nativ ...
- JS学习:第二周——NO.1回调函数
[回调函数] 定义:把一个函数的定义阶段,作为参数,传给另一个函数: 回调函数调用次数,取决于条件: 回调函数可以传参: 回调函数可以给变this指向,默认是window: 回调函数没有返回值,for ...
- 【java回调】java两个类之间的回调函数传递
背景交代:熟悉用js开发的cordovaAPP:对java一窍不通的我,老师让做一个监测用户拍照事件的功能,无奈没有找到现成的库,无奈自己动手开发java插件~~0基础java GreenHand,祝 ...
- Java|今天起,别再扯订阅和回调函数
编程史上有两个令人匪夷所思的说辞,一个是订阅,一个是回调函数. 我想应该还有很多同学为“事件的订阅”和“回调函数”所困扰,因为事情本来就不应该按这个套路来解释. 多直白,所谓的“回调函数”你完全可以线 ...
- C++ 回调函数的定义与用法
一回调函数 我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理.用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函 ...
- 通过修改i8042prt端口驱动中类驱动Kbdclass的回调函数地址,达到过滤键盘操作的例子
同样也是寒江独钓的例子,但只给了思路,现贴出实现代码 原理是通过改变端口驱动中本该调用类驱动回调函数的地方下手 //替换分发函数 来实现过滤 #include <wdm.h> #inclu ...
随机推荐
- No enclosing instance of type test8 is accessible. Must qualify the allocation with an enclosing instance of type test8 (e.g. x.new A() where x is an
在编译一个例子时,结果编译时出现: No enclosing instance of type test8 is accessible. Must qualify the allocation wit ...
- Keil C51程序设计中几种精确延时方法
1 使用定时器/计数器实现精确延时 单片机系统一般常选用11.059 2 MHz.12 MHz或6 MHz晶振.第一种更容易产生各种标准的波特率,后两种的一个机器周期分别为1 μs和2 μs,便于精确 ...
- 一键安装IIS的点点滴滴——For所有Microsoft的操作系统(上)
原文www.cnblogs.com/cdts_change/archive/2010/03/04/1677338.html 临近公司的软件要完工了,最近几天一直在泉哥的带领下为我们公司的产品做IIS一 ...
- inux关于readlink函数获取运行路径的小程序
inux关于readlink函数获取运行路径的小程序 相关函数: stat, lstat, symlink 表头文件: #include <unistd.h> 定义函数:int re ...
- linux----设置、添加别名(alias,unalias)
以下的说明针对centOS. 查看当前的别名 在命令行下,直接输入alias来查看. 添加临时的别名 方法为:alias[别名]=[指令名称] 该方法只是临时的,仅对当前终端有效,因此重启系统或在其他 ...
- KDTree详解及java实现
本文内容基于An introductory tutoril on kd-trees 1.KDTree介绍 KDTree根据m维空间中的数据集D构建的二叉树,能加快常用于最近邻查找(在加快k-means ...
- Ubuntu Eclipse的Tomcat小问题:不能输入server name,不能启动tomcat
Ubuntu的Eclipse上安装Tomcat环境,这是让人烦啊,万幸还是终于解决了. Eclipse上Tomcat的搭建: 1.点击Eclipse上的菜单:Windows - Preference, ...
- 01_docker学习总结
01 docker学习总结 toolbox https://hub.docker.com/ https://docs.docker.com/engine/installation/mac/#from- ...
- hdu 5429 Geometric Progression(存个大数模板)
Problem Description Determine whether a sequence is a Geometric progression or not. In mathematics, ...
- QT文件夹定位(网友提供)
#ifndef FOLDERFINDER_H #define FOLDERFINDER_H#include <QDir>class FolderFinder{public: QStr ...