C语言回调函数和this指针详细介绍

在C里面,经常需要提供一个函数地址,注册到结构里,然后在程序执行到特定阶段时,回调该函数。创建线程,注册线程运行的主函数就是一个典型的例子。这里以简单的回调实例,说明C++中回调函数为成员函数时有关this指针的问题。由于C++对C的继承关系,C++没有自己的线程封装技术,一般而言我们创建线程时,还是用C的回调函数机制。类似的例子也挺多的。在Java等纯粹的面向对象语言,则不一样,不光有自己的独立的线程类型,对于回调,也是注册整个对象,而不是注册一个方法,如常用的观察者模式。这里,在网上查阅了大量关于this指针、类成员函数和静态成员函数的相关知识点,结合自己的理解作一些总结。

关于回调函数,类的成员函数作为回调函数,一般而言大家已经形成了编程范式,讨论一些生僻的用法,可能被认为是腐朽的,无价值的。这里只想客观分析一下技术点,思想可能在类似的场景中遇到也说不准。

通常我们理解的成员函数和this指针是:

《深入探索C++对象模型》中提到成员函数时,当成员函数不是静态的,虚函数,那么我们有以下结论:

(1)  &类名::函数名 获取的是成员函数的实际地址;

(2) 对于函数x来讲obj.x()编译器转化后表现为x(&obj),&obj作为this指针传入;

(3) 无法通过强制类型转换在类成员函数指针与其外形几乎一样的普通函数指针之间进行有效的转换。

通常我们理解的是类的普通成员函数有一个隐藏的参数,即第一个参数,其值是this。如果希望一个成员函数既能访问类的数据成员,又能作为回调函数,有如下几种方法:

1、静态成员函数作为回调函数

为了不失封装性,可以将需要作为回调的函数声明为静态的。静态的成员函数,可以直接在类的外部调用。我们知道静态成员函数是不能直接访问类的非静态数据和接口的。那么此时需要知道具体的对象地址或者引用才能访问具体的对象成员。又有两个方法能实现这个:

1)将对象的地址用全局变量记录,在静态成员函数中通过该全局变量访问数据成员和方法。来看具体的代码实例:

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

typedef void (*func)(void*);

class CallBack;
class CallBackTest;

CallBack* g_obj = NULL;
CallBackTest* g_test = NULL;

class CallBackTest
{
public:
    CallBackTest()
    {
        m_fptr = NULL;
        m_arg = NULL;
    }

    ~CallBackTest()
    {

    }

    void registerProc(func fptr, void* arg = NULL)
    {
        m_fptr = fptr;
        if (arg != NULL)
        {
            m_arg = arg;
        }
    }

    void doCallBack()
    {
        m_fptr(m_arg);
    }

private:
    func m_fptr;
    void* m_arg;

};

class CallBack
{
public:
    CallBack(CallBackTest* t) : a(2)
    {
        if (t)
        {
            t->registerProc((func)display);
        }
    }

    ~CallBack()
    {

    }

    static void display(void* p)
    {
        if (g_obj)
        {
            g_obj->a++;
            printf("a is: %d", g_obj->a);
        }
    }

private:
    int a;

};

int main(int argc, char** argv)
{
    g_test = new CallBackTest();
    g_obj = new CallBack(g_test);
    g_test->doCallBack();

    return 0;
}
如上代码,实现对CallBack成员函数的回调。在callback类的构造函数中注册静态的成员函数到callbacktest类中。如果对该代码稍加该井,可以将g_obj变量放在callback类里面,作为一个静态成员,这样就更好了。更优雅的,将g_obj作为display的参数传入,就更好了。于是有了我们通常的做法,将成员函数声明为静态的,带一个参数,是其所在的类的对象指针,这样我们可以在注册的时候将this指针传递给静态成员函数,使用起来就好像是静态的成员函数有了this指针一样。

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

typedef void (*func)(void*);

class CallBack;
class CallBackTest;

class CallBackTest
{
public:
    CallBackTest()
    {

    }

    ~CallBackTest()
    {

    }

    void registerProc(func fptr, void* arg = NULL)
    {
        m_fptr = fptr;
        if (arg != NULL)
        {
            m_arg = arg;
        }
    }

    void doCallBack()
    {
        m_fptr(m_arg);
    }

private:
    func m_fptr;
    void* m_arg;

};

class CallBack
{
public:
    CallBack(CallBackTest* t) : a(2)
    {
        if (t)
        {
            t->registerProc((func)display, this);
        }
    }

    ~CallBack()
    {

    }

    static void display(void* _this = NULL)
    {
        if (!_this)
        {
            return;
        }
        CallBack* pc = (CallBack*)_this;
        pc->a++;
        printf("a is: %d", pc->a);
    }

private:
    int a;

};

int main(int argc, char** argv)
{
    CallBackTest* cbt = new CallBackTest();
    CallBack* cb = new CallBack(cbt);
    cbt->doCallBack();

    return 0;
}
最常用和正统的解决方法,借助于static成员函数对类数据成员的可见性,可以很方便的利用:

pc->a++;
printf("a is: %d", pc->a);
这样的语句来操作类的成员函数和成员数据。但是仍然不能像普通成员函数那样利用隐藏的this指针就直接操作类的成员函数。肯定有很多“好事”的同学希望直接像普通的成员函数那样访问类的成员。接下来就探讨一下这个方法。

2、非静态成员函数作为回调函数

既然我们知道,非静态成员函数有一个隐藏的参数,那么能否注册的时候,多传入一个参数,然后隐藏的那个指向对象的参数默认就转为this指针的值了,相当于在调用时给this赋值。可以做一个尝试,代码如下:

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

typedef void (*func)(void*);

class CallBack;
class CallBackTest;

class CallBackTest
{
public:
    CallBackTest()
    {

    }

    ~CallBackTest()
    {

    }

    void registerProc(func fptr, void* arg = NULL)
    {
        m_fptr = fptr;
        if (arg != NULL)
        {
            m_arg = arg;
        }
    }

    void doCallBack()
    {
        m_fptr(m_arg);
    }

private:
    func m_fptr;
    void* m_arg;

};

class CallBack
{
public:
    CallBack(CallBackTest* t) : a(2)
    {
        if (t)
        {
            t->registerProc((func)display, this);
        }
    }

    ~CallBack()
    {

    }

    void display()
    {
        a++;
        printf("a is: %d", a);
    }

private:
    int a;

};

int main(int argc, char** argv)
{
    CallBackTest* cbt = new CallBackTest();
    CallBack* cb = new CallBack(cbt);
    cbt->doCallBack();

    return 0;
}
尝试失败了,提示编译错误。在附录的引用[1]文中,作者采用了更直接的给指针变量赋值的方式,避开了编译错误的问题,但调用时仍然会报错。因此this指针并不是简单的在函数调用时以第一个参数的方式传递进去的,在理解成员函数访问数据的过程可以这样去理解,但是实际上的运行过程并不是这样的。在引文1、2中给出了一些可行的办法,进一步找了一下,这个也就是thunk技术,由于与平台和编译器的行为强相关。大体思路是,首先将this指针填写到指定的寄存器或者指定的地方,当调用成员函数名时,会自动根据寄存器的地址值加上偏移量实现跳转。这里不详细介绍了,有兴趣的同学可以参考链接。

使用静态成员函数加上参数传入this指针的方式应该说是目前比较完善的解决办法。不失封装性,又不失易用性。

时间: 2024-08-02 11:43:25

C语言回调函数和this指针详细介绍的相关文章

C语言 栈的表示和实现详细介绍_C 语言

C语言 栈的表示和实现详细介绍 定义:栈是限定仅在表尾进行插入和删除操作的线性表. 栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表.它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来).栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针. 栈是允许在同一端进行插入和删除操作的特殊线性表.允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom):栈底固定,而栈顶浮动:

C语言 一级指针与二级指针详细介绍_C 语言

指针的概念          指针就是地址, 利用这个地址可以找到指定的数据          指针就是地址, 那么在使用的时候, 常常会简单的说 指针变量为指针          指针变量就是存储地址的变量         int *p1;// 申请了一个变量, 即在内存中开辟了一块内存, 存储数据                     // 开辟了 8 个字节, 在 Mac 下 指针都占 8 个字节          使用指针, 实际上应该说成使用指针变量          1> 算术运算

PHP 匿名函数与注意事项详细介绍_php技巧

PHP 匿名函数与注意事项 PHP5.2 以前:autoload, PDO 和 MySQLi, 类型约束 PHP5.2:JSON 支持 PHP5.3:弃用的功能,匿名函数,新增魔术方法,命名空间,后期静态绑定,Heredoc 和 Nowdoc, const, 三元运算符,Phar PHP5.4:Short Open Tag, 数组简写形式,Traits, 内置 Web 服务器,细节修改 PHP5.5:yield, list() 用于 foreach, 细节修改 PHP5.6: 常量增强,可变函数

C++指针 详细介绍及总结_C 语言

指针的概念: 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址.要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区.让我们分别说明. 先声明几个指针放着做例子:  例一:  int *ptr; char *ptr; int **ptr; int (*ptr)[3]; int *(*ptr)[4]; 指针的类型 从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型.这是指

PHP写的加密函数,支持私人密钥(详细介绍)_php技巧

在开发PHP系统时,会员部分往往是一个必不可少的模块,而密码的处理又是不得不面对的问题,PHP 的 Mcrypt 加密库又需要额外设置,很多人都是直接使用md5()函数加密,这个方法的确安全,但是因为md5是不可逆加密,无法还原密码,因此也有一些不便之处,本文介绍加密函数支持私钥,用起来还是不错的.代码如下:PHP: 复制代码 代码如下: <ol><li class="li1"><div class="de1"> </div

深入浅出剖析C语言函数指针与回调函数(三)

前面两篇文章: http://blog.csdn.net/morixinguan/article/details/65494239 http://blog.csdn.net/morixinguan/article/details/65938128 在UNix多线程编程中,我们会使用到以下函数: Pthread_create, 我们来看看它的原型: int  pthread_create((pthread_t *thread,  pthread_attr_t  *attr,  void  *(*s

详解JavaScript的回调函数_javascript技巧

本文的目录: 什么是回调或高级函数 回调函数是如何实现的 实现回调函数的基本原则 回调地狱的问题和解决方案 实现自己的回调函数 在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以"存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值". 因为function是内置对象,我

jQuery的load()方法及其回调函数用法实例_jquery

本文实例讲述了jQuery的load()方法及其回调函数用法.分享给大家供大家参考.具体如下: 下面的js代码演示了jQuery的load()方法的使用,并演示了带回调函数(callback)的load方法的使用 <!DOCTYPE html> <html> <head> <script src="js/jquery.min.js"> </script> <script> $(document).ready(fun

Photoshop滤镜开发简介(2)--Photoshop回调函数

        在上一篇文章中,我们介绍了开发Photoshop滤镜插件最基本的一些概念和基础.Ps为了满足插件的应用需求,同时也给插件提供了大量的回调函数(或服务).例如,滤镜可以在一次调用后,保存最近一次用户设置的参数,并应用到下次调用或显示UI.这就是通过Ps的回调函数完成的.这一篇文章我们将讲解最重要的一些Ps回调函数.了解本文之后,我们将能够使用回调函数,完成例如存储我们的滤镜参数等必要的工作.本篇文章将比第一篇复杂和深入的多,但同时从这篇文章我们也可以一窥PS内部的秘密:缜密的系统设