C++领域回调函数总结<一> ---- 常见使用

简介:

回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。这样从理论上讲,C++类的成员函数是不能当作回调函数的。但我们在用C++编程时总希望在类内实现其功能,即要保持封装性,如果把回调函数写作普通函数有诸多不便。经过网上搜索和自己研究,发现了几种巧妙的方法,可以使得类成员函数当作回调函数使用。

使用场景:

回调函数是不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。回调函数使用是必要的,在我们想通过一个统一接口实现不同的内容,这时用回掉函数非常合适。比如,我们为几个不同的设备分别写了不同的显示函数:void TVshow(); void ComputerShow(); void NoteBookShow()...等等。这是我们想用一个统一的显示函数,我们这时就可以用回掉函数了。
    void show(void (*ptr)()); 
    使用时根据所传入的参数不同而调用不同的回调函数。不同的编程语言可能有不同的语法,下面举一个c语言中回调函数的例子,其中一个回调函数不带参数,另一个回调函数带参数。如例:

//Test.h

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

int Test1()
{ 
    for (int i=0; i<30; i++) 
    {  
        printf("The %d th charactor is: %c/n",
                       i, (char)(‘a‘ + i%26));
  } 
  return 0;
}

int Test2(int num)
{
  for (int i=0; i<num; i++) 
  {
    printf("The %d th charactor is: %c/n", 
                       i, (char)(‘a‘ + i%26));
  }
  return 0;
}

void Caller1(int (*ptr)())//指向函数的指针作函数参数
{ 
    (*ptr)();
}

void Caller2(int n, int (*ptr)(int n))//指向函数的指针作函数参数,
                     //这里第一个参数是为指向函数的指针服务的,
{
   (*ptr)(n);
}

int main( int argc, char *argv[], char *envp[] )
{ 
    printf("************************/n"); 
    Caller1(Test1); //相当于调用Test1();  
    printf("&&&&&&************************/n"); 
    Caller2(30, Test2); //相当于调用Test2(30); 
    return 0;
}

以上通过将回调函数的地址传给调用者从而实现调用,但是需要注意的是带参回调函数的用法。要实现回调,必须首先定义函数指针。函数指针的定义这里稍微提一下。比如: int (*ptr)(int n); 这里ptr是一个函数指针,其中(*ptr)的括号不能省略,因为括号的优先级高于星号,那样就成了一个返回类型为整型的函数声明了。

本段参考:http://zq2007.blog.hexun.com/9068988_d.html

这里采用Linux C++中线程创建函数pthread_create举例,其原型如下:

int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );

  1. 第一个参数为指向线程标识符的指针。
  2. 第二个参数用来设置线程属性。
  3. 第三个参数是线程运行函数的起始地址,即回调函数。
  4. 最后一个参数是运行函数的参数。

这里我们只关注第三个参数start_run,它是一个函数指针,指向一个以void*为参数,返回值为void*的函数,这个函数被当作线程的回调函数使用,线程启动后便会执行该函数的代码。

方法一:回调函数为普通函数,但在函数体内执行成员函数

class MyClass  
{  
    pthread_t TID;  
public:  
    void func()  
    {  
        //子线程执行代码  
    }  
  
    bool startThread()  
    {//启动子线程  
        int ret = pthread_create( &TID , 
                 NULL , callback , this );  
        if( ret != 0 )  
            return false;  
        else  
            return true;  
    }  
};  
  
static void* callback( void* arg )  
{//回调函数  
    ((MyClass*)arg)->func();调用成员函数  
    return NULL;  
}  
  
int main()  
{  
    MyClass a;  
    a.startThread();  
}

类MyClass需要在自己内部开辟一个子线程来执行成员函数func()中的代码,子线程通过调用startThread()成员函数来启动。这里将回调函数callback写在了类外面,传递的参数是一个指向MyClass对象的指针(在pthrad_create()中由第4个参数this指定),回调函数经过强制转换把void*变为MyClass*,然后再调用arg->func()执行子线程的代码。这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数,以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。缺点是回调函数在类外,影响了封装性,这里把callback()限定为static,防止在其它文件中调用此函数。

方法二:回调函数为类内静态成员函数,在其内部调用成员函数

在方法一上稍作更改,把回调函数搬到类MyClass里,这样就保持了封装性。代码如下:

class MyClass  
{  
    static MyClass* CurMy;//存储回调函数调用的对象  
    static void* callback(void*);//回调函数  
    pthread_t TID;  
    void func()  
    {  
        //子线程执行代码  
    }  
      
    void setCurMy()  
    {//设置当前对象为回调函数调用的对象  
        CurMy = this;  
    }  
public:  
    bool startThread()  
    {//启动子线程  
        setCurMy();  
        int ret = pthread_create( &TID , NULL , 
                     MyClass::callback , NULL );  
        if( ret != 0 )  
            return false;  
        else  
            return true;  
    }  
};  
MyClass* MyClass::CurMy = NULL;  
void* MyClass::callback(void*)  
{  
    CurMy->func();  
    return NULL;  
}  
  
int main()  
{  
    MyClass a;  
    a.startThread();  
}

类MyClass有了1个静态数据成员CurMy和1个静态成员函数callback。CurMy用来存储一个对象的指针,充当方法一中回调函数的参数arg。callback当作回调函数,执行CurMy->func()的代码。每次建立线程前先要调用setCurMy()来让CurMy指向当前自己。这个方法的好处时封装性得到了很好的保护,MyClass对外只公开一个接口startThread(),子线程代码和回调函数都被设为私有,外界不可见。另外没有占用callback的参数,可以从外界传递参数进来。但每个对象启动子线程前一定要注意先调用setCurMy()让CurMy正确的指向自身,否则将为其它对象开启线程,这样很引发很严重的后果。

方法三:对成员函数进行强制转换,当作回调函数

class MyClass  
{  
    pthread_t TID;  
    void func()  
    {  
        //子线程执行代码  
    }  
public:  
    bool startThread()  
    {//启动子线程  
        typedef void* (*FUNC)(void*);//定义FUNC类型是一个指向函数的指针,
                                //该函数参数为void*,返回值为void*  
        FUNC callback = (FUNC)&MyClass::func;//强制转换func()的类型  
        int ret = pthread_create( &TID , NULL , callback , this );  
        if( ret != 0 )  
            return false;  
        else  
            return true;  
    }  
};  
  
int main()  
{  
    MyClass a;  
    a.startThread();  
}

这个方法是原理是,MyClass::func最终会转化成 void func(MyClass *this); 也就是说在原第一个参数前插入指向对象本身的this指针。可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数。对编译器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)这两种函数指针虽然看上去很不一样,但他们的最终形式是相同的,因此就可以把成员函数指针强制转换成普通函数的指针来当作回调函数。在建立线程时要把当前对象的指针this当作参数传给回调函数(成员函数func),这样才能知道线程是针对哪个对象建立的。方法三的封装性比方法二更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。

注:与tr1::function对象结合使用,能获得更好的效果,详情见http://blog.csdn.net/this_capslock/article/details/38564719

本段参考:http://blog.csdn.net/this_capslock/article/details/1700100

时间: 2024-10-29 08:06:52

C++领域回调函数总结<一> ---- 常见使用的相关文章

JS 对于回调函数的理解,和常见的使用场景应用,使用注意点

  很经常我们会遇到这样一种情况: 例如,你需要和其他人合作,别人提供数据,而你不需要关注别人获取或者构建数据的方式方法. 你只要对这个拿到的数据进行操作. 这样,就相当于我们提供一个外在的函数,别人调用这个函数,返回相应的数据. 例如: ? 1 2 3 4 5 6 7 8 function (num , numFun) { if(num < 10){ //do sth }else { //do sth numFun(); } } 在num的判断之后执行NumFun的函数. 所谓的回调函数,可以

不使用回调函数的ajax请求实现(async和await简化回调函数嵌套)

在常规的服务器端程序设计中, 比如说爬虫程序, 发送http请求的过程会使整个执行过程阻塞,直到http请求响应完成代码才会继续执行, 以php为例子 $url = "http://www.google.com.hk"; $result = file_get_contents($url); echo $result; 当代码执行到第二行时,程序便陷入了等待,直到请求完成,程序才会继续往下跑将抓取到的html输出.这种做法的好处是代码简洁明了,运行流程清晰, 容易维护. 缺点就是程序的运

javascript回调函数

回调函数在使用上是把一个函数当成参数传给另一个函数,在另一个函数中作为返回结果. 以下是一个简单的回调函数例子:tom到店里买东西,刚好没货了,店主问他拿了信息记录到本子上了,过几天又有货了,店主就翻本子找tom的电话,那店主找电话的过程就是一个回调函数. function outPutPhone(name) { console.log(name + "电话:10000"); } //使用回调函数的函数 function searchInfo(name, callback) { cal

初步了解回调函数

1. 诸葛亮给赵子龙一个锦囊,吩咐他危急时打开按锦囊指示办, 锦囊里的命令就是回调函数,危急时刻就是回调的时机. 不同的锦囊里可以有不同的命令. 2. 回调函数最常见的是鼠标键盘钩子的回调.看了这个就很明白了.第一人要第二人等待某件事A(挂钩,主调函数),在某个时刻T发生事件A(有消息发生),第二人要告诉第一人要相应的完成事件B(回调处理函数).可见主调函数和回调函数都是第一个人干的. 生活中这样的例子就是:1.孩子告诉妈妈:明天我早起床,早点叫我.这是主调函数A.2.妈妈第二天,早了半个小时叫

java回调函数这样说,应该明白了吧!

有哥们问我回调怎么用,回调怎么理解?怎么说好呢,只可意会不可言传呐,非也,回调在实际开发中使用频率其实是很高的,恰好我小时候也被回调函数欺负过,竟然问了,那么肯定要好好分享一下我的一些经验. 网传回调的理解 所谓的回调函数就是:在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现,B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用,这种机制就称为回调. 估计看完已经晕在厕所了,可以暂时忽略- 那么从现在开始可以先用我

ArcGIS API for JavaScript 4.2学习笔记[7] 鹰眼(缩略图的实现及异步处理、Promise、回调函数、监听的笔记)

文前说明:关于style就是页面的css暂时不做评论,因为官方给的例子的样式实在太简单了,照抄阅读即可. 这篇文章有着大量AJS 4.x版本添加的内容,如监听watch.Promise对象.回调函数.异步处理等内容,原理性的东西我会在文末解释,各位看官不用担心看不懂,我尽量用通俗的语言解释这些. 惯例,如果不习惯从头看到尾,可以直接跳到后面看总结. 大家应该看过商业地图的缩略图功能吧?以度娘地图为例,在使用街景地图的时候,左下角会出现一个地点一样的2D小地图: 这个就是鹰眼功能的应用,在很多桌面

回调函数学习

今天又学习了一天scrapy. 其中基本用法是  设置strat_url,实现parse(),实现parse()的回调函数prase_item(),以及parse_item()的回调函数parse_item_details(); 回调函数的意思,有点懂,又时常糊涂: 在知乎上看了个帖子,比较通俗易懂,摘录如下: 回调方法介绍之中国好室友篇(Java示例) 前言 在Java社区的各种开源工具中,回调方法的使用俯拾即是.所以熟悉回调方法无疑能加速自己对开源轮子的掌握.网上搜了一些文章,奈何对回调方法

理解和使用 JavaScript 中的回调函数

原文:http://javascriptissexy.com/ 在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以"存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值". 因为function是内置对象,我们可以将它作为参数传递给另一个函数,延迟到函数中执行,甚至执行后

C函数指针与回调函数

一.函数指针 简单声明一个函数指针并不意味着它马上就可以使用,和其它指针一样,对函数指针执行简接访问之前必须把它初始化为指向某一个函数. int f(int); int (*pf)(int)=&f; 第二个声明创建了函数指针pf,并把它初始化为指向函数f.函数指针的初始化也可以通过一条赋值语句完成.在函数指针的初始化之前具有f的原型是很重要的,否则编译器就无法检查f的类型是否与pf所指向的类型一致. 初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针.&am