回调函数

http://learn.akae.cn/media/ch24s05.html

回调函数 请点评

如果参数是一个函数指针,调用者可以传递一个函数的地址给实现者,让实现者去调用它,这称为回调函数(Callback Function)。例如qsort(3)bsearch(3)

表 24.7. 回调函数示例:void func(void (*f)(void *), void *p);

调用者 实现者
  1. 提供一个回调函数,再提供一个准备传给回调函数的参数。

  2. 把回调函数传给参数f,把准备传给回调函数的参数按void *类型传给参数p

  1. 在适当的时候根据调用者传来的函数指针f调用回调函数,将调用者传来的参数p转交给回调函数,即调用f(p);

 

以下是一个简单的例子。实现了一个repeat_three_times函数,可以把调用者传来的任何回调函数连续执行三次。

 

#ifndef PARA_CALLBACK_H

#define PARA_CALLBACK_H

 

typedef void (*callback_t)(void *);

extern void repeat_three_times(callback_t, void *);

 

#endif

 

#include "para_callback.h"

 

void repeat_three_times(callback_t f, void *para)

{

     f(para);

     f(para);

     f(para);

}

 

#include <stdio.h>

#include "para_callback.h"

 

void say_hello(void *str)

{

     printf("Hello %s/n", (const char *)str);

}

 

void count_numbers(void *num)

{

     int i;

     for(i=1; i<=(int)num; i++)

     printf("%d ", i);

     putchar('/n');

}

 

int main(void)

{

     repeat_three_times(say_hello, (void *)"Guys");

     repeat_three_times(count_numbers, (void *)4);

     return 0;

}

回顾一下前面几节的例子,参数类型都是由实现者规定的。而本例中回调函数的参数按什么类型解释由调用者规定,对于实现者来说就是一个void *指针,实现者只负责将这个指针转交给回调函数,而不关心它到底指向什么数据类型。调用者知道自己传的参数是char *型的,那么在自己提供的回调函数中就应该知道参数要转换成char *型来解释。

回调函数的一个典型应用就是实现类似C++的泛型算法(Generics Algorithm)。下面实现的max函数可以在任意一组对象中找出最大值,可以是一组int、一组char或者一组结构体,但是实现者并不知道怎样去比较两个对象的大小,调用者需要提供一个做比较操作的回调函数。

 

#ifndef GENERICS_H

#define GENERICS_H

 

typedef int (*cmp_t)(void *, void *);

extern void *max(void *data[], int num, cmp_t cmp);

 

#endif

 

#include "generics.h"

 

void *max(void *data[], int num, cmp_t cmp)

{

     int i;

     void *temp = data[0];

     for(i=1; i<num; i++) {

     if(cmp(temp, data[i])<0)

          temp = data[i];

     }

     return temp;

}

 

#include <stdio.h>

#include "generics.h"

 

typedef struct {

     const char *name;

     int score;

} student_t;

 

int cmp_student(void *a, void *b)

{

     if(((student_t *)a)->score > ((student_t *)b)->score)

     return 1;

     else if(((student_t *)a)->score == ((student_t *)b)->score)

     return 0;

     else

     return -1;

}

 

int main(void)

{

     student_t list[4] = {{"Tom", 68}, {"Jerry", 72},

                 {"Moby", 60}, {"Kirby", 89}};

     student_t *plist[4] = {&list[0], &list[1], &list[2], &list[3]};

     student_t *pmax = max((void **)plist, 4, cmp_student);

     printf("%s gets the highest score %d/n", pmax->name, pmax->score);

 

     return 0;

}

max函数之所以能对一组任意类型的对象进行操作,关键在于传给max的是指向对象的指针所构成的数组,而不是对象本身所构成的数组,这样max不必关心对象到底是什么类型,只需转给比较函数cmp,然后根据比较结果做相应操作即可,cmp是调用者提供的回调函数,调用者当然知道对象是什么类型以及如何比较。

以上举例的回调函数是被同步调用的,调用者调用max函数,max函数则调用cmp函数,相当于调用者间接调了自己提供的回调函数。在实际系统中,异步调用也是回调函数的一种典型用法,调用者首先将回调函数传给实现者,实现者记住这个函数,这称为注册一个回调函数,然后当某个事件发生时实现者再调用先前注册的函数,比如sigaction(2)注册一个信号处理函数,当信号产生时由系统调用该函数进行处理,再比如pthread_create(3)注册一个线程函数,当发生调度时系统切换到新注册的线程函数中运行,在GUI编程中异步回调函数更是有普遍的应用,例如为某个按钮注册一个回调函数,当用户点击按钮时调用它。

以下是一个代码框架。

 

#ifndef REGISTRY_H

#define REGISTRY_H

 

typedef void (*registry_t)(void);

extern void register_func(registry_t);

 

#endif

 

#include <unistd.h>

#include "registry.h"

 

static registry_t func;

 

void register_func(registry_t f)

{

     func = f;

}

 

static void on_some_event(void)

{

     ...

     func();

     ...

}

既然参数可以是函数指针,返回值同样也可以是函数指针,因此可以有func()();这样的调用。返回函数的函数在C语言中很少见,在一些函数式编程语言例如LISP、Haskell中则很常见,基本思想是把函数也当作一种数据来操作,输入、输出和参与运算,操作函数的函数称为高阶函数(High-order Function)。

习题 请点评

1、[K&R]的5.6节有一个qsort函数的实现,可以对一组任意类型的对象做快速排序。请读者仿照那个例子,写一个插入排序的函数和一个折半查找的函数。

 

-------------------------------------------------------------------------------------------------------------------------

C++中建立对象间消息连接的一种系统方法——回调函数
2007-10-04 17:07

用过C++进行过面向对象程序设计的用户都知道,程序中的对象很少单独存在。
不考虑对象间的相互作用几乎是不可能的。所以,标识对象间的关系或建立对象间的消
息连接是面向对象程序设计的一项重要任务。本文着重从C++程序设计的角度,提出一种
建立对象间消息连接的实用方法。如果你想详细了解面向对象程序设计技术,请参阅有
关专著。大家都知道对象是数据和方法的封装体。在C++中,它们分别表现为数据成员和
成员函数。程序设计者通过执行对象的各种方法,来改变对象的状态(即改变对象的属
性数据)。从而使该对象发生某些“事件”。当一对象发生某事件时,它通常需向其它
相关对象发送“消息”,请求它们作出一些处理。 这时,发生事件并向其它对象请求处
理的对象被称为“事件对象”,而处理事件的对象被称为“回调对象”。回调对象对事
件的处理称为“回调函数”。在C++中,这一过程相当于:当事件对象发生事件时,调用
回调对象的某些成员函数。通常的作法是回调对象向事件对象传递对象指针。但这种方
法不通用。为了减少程序设计的工作量,本文提出一种建立对象间消息连接的系统方法
。它的思路是:将“事件发生→请求处理→执行处理”这一过程抽象成一个“回调”(
CallBack)类。通过继承,用户可以轻松获取建立对象间消息连接的机制。
   一、回调类的数据结构及其成员函数
        本文提出的CallBack类支持三种回调函数。它们是:回调对象中的成员函数,属
于回调类的静态成员函数和普通的C函数。CallBackle类中包含一回调函数表callBackL
ist。它用于记录事件名称,指向回调函数及回调对象的指针。该表的每一个节点为一个
事件记录EventRecord。每个事件记录包含三个域:事件名指针eventName,指向回调对
象的指针pointerToCBO,指向回调函数的指针pointerToCBF或pointerToCBSF(其中,po
interToCBF指向回调对象的成员函数,pointerToCBSF指向回调类的静态成员函数或普通
函数。它们同处于一共用体内)。CallBack类所提供的回调机制是这样的:在事件对象上
注册回调对象中的回调函数;当事件发生时,事件对象在其回调表中检索并执行回调函
数。从而使二者的消息连接得以建立。(关于该类的具体实现,请参阅文后所附的程序
清单) 回调对象
事件对象
事件名    回调对象指针   回调函数指针
“event”     pointerCBO    pointerToCBF或
                         pointerTOCBSF
- - - - - -
AddCallBack: 注册事件名和指向回调函数,回调对象的指针
CallCallBack: 在回调表中,检索注册在指定事件上回调函数并调用它们
事件发生时,调用CallCallBack函数
对事件event进行处理的成员函数
从CallBack类继承的回调表callBackList, 成员函数AddCallBack和CallCallBack。
当回调函数为静态成员函数或普通C函数时, pointerToCBO为NULL。
事件名是回调表callBackLis中的检索关键字。
回调对象中其它成员函数
CallBack类的成员函数AddCallBack用来将回调函数注册到事件对象的回调表中。它有
两个重载版本:
void CallBack::AddCallBack(char *event,CallBackFunction cbf,CallBack *p);
        void CallBack::AddCallBack(char *event,CallBackStaticFunction cbsf);
其中,第一个AddCallBack用来将某回调对象的成员函数注册到事件对象的回调表中。第
二个AddCallBack用来将或某回调类的静态成员函数注册到事件对象的回调表中。在上参
数表中,event是指向事件名字符串的指针,p是指向回调对象的指针,cbf和cbsf分别是
指向成员函数及静态成员函数(或普通函数)的指针。当回调函数来自某回调对象Some
Object时,传递成员函数指针应采用如下格式:(CallBackFunction)&SomeObject::M
emberFunctionName; 传递SomeObject类的某静态成员函数指针应采用格式:(CallBac
kStaticFunction)& SomeObject::FunctionName;传递程序中普通函数指针时,只需传
递函数名即可。
CallBack类的成员函数void CallBack::CallCallBack(char *ename, CallData callda
ta = NULL)用来调用注册在事件ename上的所有回调函数。其中,calldata为数据指针(
CallData实际上就是void*,详见程序清单)。事件对象可通过它向回调对象传递有用的
数据。该成员函数通常在事件对象的成员函数中调用,因为通常只有事件对象的成员函
数才能改变对象的内部数据,从而使某些事件发生。
成员函数RemoveCallback用来删除注册在事件对象上的回调函数。它的三个重载版本依
次为:
void CallBack::RemoveCallBack(char *event,CallBackFunction cbf,CallBack *p);

        void CallBack::RemoveCallBack(char *event,CallBackStaticFunction cbsf
);
        void CallBack::RemoveCallBack(char *event);
其中,event,cbf,cbsf,p等参数和成员函数AddCallBack中各参数一样。第一个RemoveC
allBack用于删除注册在事件event上某回调对象的一个成员函数。第二个RemoveCallBa
ck用于删除注册在事件event上的某普通函数或某回调类的一个静态成员函数。第三个R
emoveCallBack用于删除注册在事件event上的全部回调函数。
   二、CallBack类的使用方法
使用CallBack类,可按以下步骤进行:
1.确定程序中哪些对象间存在关系,需要建立消息连接。并确定在各特定消息连接关系
中,哪个对象是事件对象,哪个对象是回调对象。
2.事件对象类和回调对象类都必须从CallBack类继承,以获得回调支持。
3.为事件对象注册回调数据。包括:事件名,回调函数名,指向回调对象的指针。
4.当你感兴趣的事件发生时,在事件对象类引发事件的成员函数中调用CallCallBack函
数。
        下面是一个具体的例子。通过它你会对Callback类的使用方法有进一步的了解。

   //测试程序文件:test.cpp
#include"callback.h"
   //“扬声器”类
class Speaker:public CallBack
{
     private:
         int volume;
     public:
         Speaker(int v): volume(v) {}
                void IncreaseVolume(int v) //增加音量成员函数
         {
             volume += v;
             if(volume > 20){
             //“音量大于20”事件发生了
            //调用注册在两事件上的回调函数
                 CallCallBack("音量改变了");
                 CallCallBack("音量大于20", &volume);
             }
         }
         void DecreaseVolume(int v) //降低音量成员函数
         {
             volume -= v;
             if(volume < 5){ //“音量小于5”事件发生了
//调用注册在两事件上的回调函数
CallCallBack("音量改变了");
                 CallCallBack("音量小于5", &volume);
             }
         }
};
   //“耳朵”类
class Ear : public CallBack
{
     public:
       static void Response(CallData callData) //对“音量改变”的反应
       {
           cout<<"音量改变了."<<endl;
       }
       void HighVoiceResponse(CallData callData)//对高音的反应
       {
           cout<<”喂!太吵了!现在音量是:"<<*((int *)callData)<<endl;
       }
       void LowVoiceResponse(CallData callData)// 对低音的反应
       {
           cout<<"啊!我听不清了。现在音量是:"<<*((int *)callData)<<endl;
       }
};
   void main(void)
{
Speaker s(10); //现在音量为10
Ear e;
     //为事件对象s注册回调函数
s.AddCallBack("音量大于20”,(CallBackFunction)&Ear::HighVoiceResponse,&e);
        s.AddCallBack("音量小于5”,(CallBackFunction)&Ear::LowVoiceResponse,&
e);
        s.AddCallBack("音量改变了",(CallBackStaticFunction)&Ear::Response);
        s.IncreaseVolume(12);//将音量增加12,现在音量位22
        s.DecreaseVolume(20);//将音量减少20,现在音量位2
}
运行结果:
音量改变了.
喂!太吵了!现在音量是:22
音量改变了.
啊!我听不清了。现在音量是:2
   在上例中,扬声器对象s为事件对象,耳朵对象e为回调对象。。s上被注册了三个事件
:“音量改变了”,“音量大于20”,“音量小于5”。 回调函数分别为:Ear::Respo
nse, Ear::HighVoiceResponse,Ear::LowVoiceResponse。当扬声器s通过其成员函数
IncreaseVolume和 DecreaseVolume改变音量时,回调对象e会自动作出反应。可见,通
过使用CallBack类,在对象间建立消息连接已变为一项很简单和优美的工作。
由于笔者水平有限,该类的设计必有不完善之处。如果您对它感兴趣,笔者可与各位C+
+玩家共同探讨这类问题。联系方式:[email protected]
   附:程序清单(本程序在MS VC++5.0和TC++3.0上均编译通过)
//回调类的类结构:callback.h
#ifndef _CALLBACK_H
#define _CALLBACK_H
#include<stdlib.h>
#include<string.h>
#include<iostream.h>
#define CALLBACKLIST_INIT_SIZE 10
#define CALLBACKLIST_INCREMENT 5
class CallBack;
typedef void *CallData;//回调数据指针类型定义
typedef void (CallBack::*CallBackFunction)(CallData); //指向回调成员函数的指

typedef void (*CallBackStaticFunction)(CallData); //指向静态成员函数或普通函
数的指针类型定义
   class EventRecord{
     private:
         char *eventName;        //回调事件名称
         CallBack *pointerToCBO;//指向回调对象的指针
         //指向成员函数的指针和指向静态成员函数(或普通函数)指针的共用体
         union{
             CallBackFunction pointerToCBF;
             CallBackStaticFunction pointerToCBSF;
         };
       public:
         EventRecord(void); //事件记录类的缺省构造函数
//构造包含成员函数的事件记录
EventRecord(char *ename,CallBack *pCBO,CallBackFunction pCBF);
//构造包含静态成员函数或普通函数的事件记录
EventRecord(char *ename,CallBackStaticFunction pCBSF);
           ~EventRecord(void);//析构事件记录
         void operator = (const EventRecord& er);//重载赋值运算符
         //判断当前事件记录的事件名是否为ename
int operator == (char *ename) const;
         //判断当前事件记录是否和指定事件记录相等
int operator == (const EventRecord& er) const;
        void Flush(void);        //将当前事件记录清空
         int IsEmpty(void) const;//判断事件记录是否为空(即事件名是否为空)
           friend class CallBack;   //让CallBack类能访问EventRecord的私有成员;

};
   class CallBack {
     private:
         EventRecord *callBackList; //回调事件表
         int curpos;                 //当前事件记录位置
         int lastpos;                //回调表中最后一空闲位置
         int size;                   //回调表的大小
           void MoveFirst(void) { curpos = 0; }//将当前记录置为第一条记录
         void MoveNext(void) //将下一条记录置为当前记录
{
             if(curpos == lastpos) return;
             curpos++;
}
         //判断回调表是否被遍历完
int EndOfList(void) const { return curpos == lastpos; }
     public:
         CallBack(void);//构造函数
         CallBack(const CallBack& cb);//拷贝构造函数
         ~CallBack(void);//析构函数
         void operator = (const CallBack& cb);// 重载赋值运算符
        //将回调对象的成员函数、静态成员函数(或普通函数)
//注册为事件对象的回调函数
         void AddCallBack(char *event,CallBackFunction cbf,CallBack *p);
         void AddCallBack(char *event,CallBackStaticFunction cbsf);
           //删除注册在指定事件上的回调函数
         void RemoveCallBack(char *event,CallBackFunction cbf,CallBack *p);
         void RemoveCallBack(char *event,CallBackStaticFunction cbsf);
         void RemoveCallBack(char *event);// 删除某事件的全部记录
           //执行注册在某一事件上的所有回调函数
         void CallCallBack(char *event, CallData calldata = NULL);
};
#endif
   //回调类的实现:callback.cpp
#include"callback.h"
 //EventRecord类的实现
EventRecord::EventRecord(void)
{
     eventName = NULL;
     pointerToCBO = NULL;
     //因为sizeof(CallBackFunction) > sizeof(CallBackStaticFunction)
     pointerToCBF = NULL;
}
EventRecord::EventRecord(char *ename, CallBack *pCBO, CallBackFunction pCBF)

                  :pointerToCBO(pCBO), pointerToCBF(pCBF)
{
     eventName = strdup(ename);
}
EventRecord::EventRecord(char *ename, CallBackStaticFunction pCBSF)
   :pointerToCBO(NULL), pointerToCBSF(pCBSF)
{
     eventName = strdup(ename);
}
   EventRecord::~EventRecord(void)
{
     if(eventName) delete eventName;
}
   void EventRecord::operator = (const EventRecord& er)
{
     if(er.eventName)
         eventName = strdup(er.eventName);
     else
         eventName = NULL;
     pointerToCBO = er.pointerToCBO;
     pointerToCBF = er.pointerToCBF;
}
   int EventRecord::operator == (char *ename) const
{
     if((eventName == NULL)||ename == NULL)
         return eventName == ename;
     else
         return strcmp(eventName,ename) == 0;
}
int EventRecord::operator == (const EventRecord& er) const
{
     return (er == eventName)
           &&(pointerToCBO == er.pointerToCBO)
           &&(pointerToCBO ?
             (pointerToCBF == er.pointerToCBF):
             (pointerToCBSF == er.pointerToCBSF));
}
   void EventRecord::Flush(void)
{
     if(eventName){
       delete eventName;
       eventName = NULL;
     }
     pointerToCBO = NULL;
     pointerToCBF = NULL;
}
int EventRecord::IsEmpty(void) const
{
     if(eventName == NULL)
         return 1;
     else
         return 0;
}
   //Callback类的实现
CallBack::CallBack(void)
{
//按初始尺寸为回调表分配内存空间
callBackList = new EventRecord[CALLBACKLIST_INIT_SIZE];
     if(!callBackList){
         cerr<<"CallBack: memory allocation error."<<endl;
         exit(1);
     }
     size = CALLBACKLIST_INIT_SIZE;
     lastpos = 0;
     curpos = 0;
}
   CallBack::CallBack(const CallBack& cb): curpos(cb.curpos),lastpos(cb.lastp
os),size(cb.size)
{
     callBackList = new EventRecord[size];
     if(!callBackList){
         cerr<<"CallBack: memory allocation error."<<endl;
         exit(1);
     }
     //一一复制各条事件记录
for(int i = 0; i < size; i++) callBackList[i] = cb.callBackList[i];
}
   void CallBack::operator = (const CallBack& cb)
{
     curpos = cb.curpos;
     lastpos = cb.lastpos;
     size = cb.size;
       delete [] callBackList;//删除旧的回调表
     callBackList = new EventRecord[size];//重新分配内存空间
     if(!callBackList){
         cerr<<"CallBack: memory allocation error."<<endl;
         exit(1);
     }
     //一一复制各条事件记录
     for(int i = 0; i < size; i++) callBackList[i] = cb.callBackList[i];
}
   CallBack::~CallBack(void)
{
     delete [] callBackList;
}
   void CallBack::AddCallBack(char *event, CallBackFunction pCBF, CallBack *p
CBO)
{
     //如事件名为空,退出
     if( (event == NULL)?1:(strlen(event) == 0)) return;
     //寻找因删除事件记录而产生的第一个空闲位置,并填写新事件记录
     for(int start=0;start<lastpos;start++)
         if(callBackList[start].IsEmpty()){
             callBackList[start] = EventRecord(event,pCBO,pCBF);
             break;
         }
     if(start < lastpos) return; //确实存在空闲位置
     //没有空闲位置,在回调表后追加新记录
     if(lastpos == size) //回调表已满,需“伸长”
     {
         EventRecord *tempList = callBackList;//暂存旧回调表指针
         //以一定的步长“伸长”回调表
callBackList = new EventRecord[size + CALLBACKLIST_INCREMENT];
         if(!callBackList){
             cerr<<"CallBack: memory allocation error."<<endl;
             exit(1);
         }
         //复制旧回调表中的记录
         for(int i = 0; i < size; i++) callBackList[i] = tempList[i];
         delete [] tempList;//删除旧回调表
         size += CALLBACKLIST_INCREMENT;//记下新回调表的尺寸
     }
    //构造新的事件记录并将其填入回调表中
     callBackList[lastpos] = EventRecord(event,pCBO,pCBF);
     lastpos++;
}
   void CallBack::AddCallBack(char *event,CallBackStaticFunction pCBSF)
{
     if( (event == NULL)?1:(strlen(event) == 0)) return;
     for(int start=0;start<lastpos;start++)
         if(callBackList[start].IsEmpty()){
             callBackList[start] = EventRecord(event,pCBSF);
             break;
         }
     if(start < lastpos) return; //a hole is found
       if(lastpos == size) //event list is insufficient
     {
         EventRecord *tempList = callBackList;
         callBackList = new EventRecord[size + CALLBACKLIST_INCREMENT];
         if(!callBackList){
             cerr<<"CallBack: memory allocation error."<<endl;
             exit(1);
         }
           for(int i = 0; i < size; i++) callBackList[i] = tempList[i];
         delete [] tempList;
         size += CALLBACKLIST_INCREMENT;
     }
     callBackList[lastpos] = EventRecord(event,pCBSF);
     lastpos++;
}
//删除注册在指定事件上的成员函数
void CallBack::RemoveCallBack(char *event, CallBackFunction pCBF, CallBack *
pCBO)
{
     if( (event == NULL)?1:(strlen(event) == 0)) return;
     EventRecord er(event,pCBO,pCBF);
     for(int i = 0; i < lastpos; i++)
     if(callBackList[i] == er) callBackList[i].Flush();
}
//删除注册在指定事件上的静态成员函数或普通函数
void CallBack::RemoveCallBack(char *event,CallBackStaticFunction pCBSF)
{
     if( (event == NULL)?1:(strlen(event) == 0)) return;
     EventRecord er(event,pCBSF);
     for(int i = 0; i < lastpos; i++)
         if(callBackList[i] == er) callBackList[i].Flush();
}
//删除注册在指定事件上的所有回调函数
void CallBack::RemoveCallBack(char *event)
{
     if( (event == NULL)?1:(strlen(event) == 0)) return;
     for(int i = 0; i < lastpos; i++)
         if(callBackList[i] == event) callBackList[i].Flush();
}
void CallBack::CallCallBack(char *event, CallData callData)
{
     if( (event == NULL)?1:(strlen(event) == 0)) return;
     CallBack *pCBO;
     CallBackFunction pCBF;
     CallBackStaticFunction pCBSF;
     MoveFirst();
     while(!EndOfList())
     {
         //如当前事件记录和指定事件不匹配,转入下一条记录继续循环
         if(!(callBackList[curpos] == event))
         {
             MoveNext();
             continue;
         }
         //如找到匹配记录
         pCBO = callBackList[curpos].pointerToCBO;
         //如事件记录中回调对象指针为空,说明该记录中保存的是静态函数指针
if(pCBO == NULL){
             pCBSF = callBackList[curpos].pointerToCBSF;
             pCBSF(callData);//调用该静态回调函数
         }
         else //如事件记录中回调对象指针非空,说明该记录中保存的是成员函数指针

         {
             pCBF = callBackList[curpos].pointerToCBF;
             (pCBO->*pCBF)(callData);// 调用该回调对象的成员函数
         }
         MoveNext();
     }
}

你可能感兴趣的:(回调函数)