精华内容
下载资源
问答
  • C++类成员函数指针使用介绍
    万次阅读 多人点赞
    2019-09-21 16:07:49

    前言

    在之前写过的博客中有介绍过函数指针和指针函数的区别和简单用法(文章在这里),当时的Demo非常简单,都是C语言的写法,但是当在C++中直接像C那样使用类成员函数指针时就会报错:reference to non-static member function must be called
    所以如果是C++中的成员函数指针其使用方法是有区别的,这里针对不同的场景做个补充说明。

    类成员函数的指针(非静态)

    指向类成员函数的指针与普通函数指针的区别在于,前者需要匹配函数的参数类型和个数以及返回值类型,还要匹配该函数指针所属的类类型。

    这是因为非静态的成员函数必须被绑定到一个类的对象或者指针上,才能得到被调用对象的this指针,然后才能调用指针所指的成员函数(所有类的对象都有自己数据成员的拷贝,但是成员函数都是共用的,为了区分是谁调用了成员函数,就必须有this指针,this指针是隐式的添加到函数参数列表里去的)。

    所以,对于类成员函数的指针使用包含以下几个步骤:

    声明: 指向类的成员函数的指针需要在指针前面加上类的类型,格式为:

    typedef 返回值 (类名::*指针类型名)(参数列表);
    

    赋值: 需要用类的成员函数地址赋值,格式为:

    指针类型名  指针名 = &类名::成员函数名;
    

    注意:这里的这个&符号是比较重要的:不加&,编译器会认为是在这里调用成员函数,所以需要给出参数列表,否则会报错;加了&,才认为是要获取函数指针。这是C++专门做了区别对待。

    调用: 针对调用的对象是对象还是指针,分别用.*和->*进行调用,格式为:

    (类对象.*指针名)(参数列表);
    
    (类指针->*指针名)(参数列表);
    

    注意:这里的前面一对括号是很重要的,因为()的优先级高于成员操作符指针的优先级。

    直接来看一个示例吧:

    class Calculation
    {
    public:
        int add(int a,int b){ //非静态函数
            return  a + b;
        }
    };
    
    typedef int (Calculation::*FuncCal)(int,int);
    
    int main()
    {
        FuncCal funAdd = &Calculation::add;
        Calculation * calPtr = new Calculation;
        int ret = (calPtr->*funAdd)(1,2);  //通过指针调用
    
        Calculation cal;
        int ret2 = (cal.*funAdd)(3,4);  //通过对象调用
    
        cout << "ret = " << ret << endl;
        cout << "ret2 = " << ret2 << endl;
        return 0;
    }
    

    指向类的静态函数的指针

    类的静态成员函数和普通函数的函数指针的区别在于,他们是不依赖于具体对象的,所有实例化的对象都共享同一个静态成员,所以静态成员也没有this指针的概念。

    所以,指向类的静态成员的指针就是普通的指针。

    class Calculation
    {
    public:
        static int add(int a,int b){ //非静态函数
            return  a + b;
        }
    };
    
    typedef int (*FuncCal)(int,int);
    
    int main()
    {
        FuncCal funAdd = &Calculation::add;
        int ret = (*funAdd)(1,2);  //直接引用
        int ret2 = funAdd(3,4);  //直接引用
    
        cout << "ret = " << ret << endl;
        cout << "ret2 = " << ret2 << endl;
        return 0;
    }
    
    

    总结以上两种情况的区别:

    • 如果是类的静态成员函数,那么使用函数指针和普通函数指针没区别,使用方法一样
    • 如果是类的非静态成员函数,那么使用函数指针需要加一个类限制一下。

    使用函数指针,很多情况下是用在函数的参数中,在一些复杂的计算,如果需要重复调用,并且每次调用的函数不一样,那么这时候使用函数指针就很方便了,可以减少代码量。

    参考资料:
    https://blog.csdn.net/houzijushi/article/details/81503409
    https://www.cnblogs.com/lvchaoshun/p/7806248.html
    https://www.cnblogs.com/AnnieKim/archive/2011/12/04/2275589.html

    更多相关内容
  • 博文地址:https://blog.csdn.net/maoyeahcom/article/details/107106680
  • C++类成员函数指针2

    2012-11-17 16:54:01
    1.普通函数指针指向普通函数 2.普通函数指向非静态成员函数 3. 外部的 类函数指针 指向...4. 外部的 类函数指针 指向成员函数 5. 内部的 函数指针 指向成员函数 (类似于第2条) 6. 内部的 函数指针 指向普通函数
  • c++类成员函数指针

    千次阅读 2021-01-19 18:04:56
    首先问大家一句,什么是函数指针? 肯定有的人会这样回答,函数指针?不就是指向函数地址的一个指针吗?或者就是一个存放着一个函数首地址的变量? 当然,那些有点底层基础的肯定会这样说,函数就是一堆连续的机器码...

    提出疑问

    首先问大家一句,什么是函数指针?
    肯定有的人会这样回答,函数指针?不就是指向函数地址的一个指针吗?或者就是一个存放着一个函数首地址的变量?
    当然,那些有点底层基础的肯定会这样说,函数就是一堆连续的机器码,而函数指针,就是存放了这堆连续机器码首地址的变量。
    详细了解函数指针(因为这里主要讲成员函数指针原理):
    c/c++函数指针(Hook前奏1)
    c/c++ typedef定义函数指针(Hook前奏2)
    那么大家是不是回答的时候,考虑的地方是不是仅仅局限于 一般的函数????那么成员函数呢???
    为什么得强调成员函数呢?因为成员函数包括了虚函数和非虚函数(这里涉及虚表问题,可以先简单看看列出的虚函数系列,否则接下来问题会有点难以接受。)
    虚函数系列:
    详解虚函数的实现过程之初探虚表(1)
    详解虚函数的实现过程之单继承(2)
    详解虚函数的实现过程之多重继承(3)
    详解虚函数的实现过程之虚基类(4)
    详解虚函数的实现过程之菱形继承(5)

    c++层面

    首先,上两份成员函数指针代码

    非虚函数

    #include<iostream>
    using namespace std;
    
    class a {
    	
    public:
    	 int add(int a, int b) {
    		return a + b;
    	}
    };
    
    typedef int (a::* pClassFun)(int, int);
    
    
    int main() {
    
    	pClassFun pointer = &a::add;
    	a  aa;
    	cout << (aa.*pointer)(10, 20);
    }
    

    虚函数

    #include<iostream>
    using namespace std;
    
    class a {
    	
    public:
    	virtual int add(int a, int b) {
    		return a + b;
    	}
    };
    
    typedef int (a::* pClassFun)(int, int);
    
    
    int main() {
    
    	pClassFun pointer = &a::add;
    	a  aa;
    	cout << (aa.*pointer)(10, 20);
    }
    

    一般:
    int (*pointer)(int, int); // 声明函数指针 这里,pointer指向的函数类型是int (int, int),即函数的参数是两个int型,返回值也是int型。
    注:pointer两端的括号必不可少,如果不写这对括号,则pointer是一个返回值为int* 的函数。

    成员函数指针:

    int (A::*pf)(int, int); // 声明一个成员函数指针,这里A::*pf两端的括号也是必不可少的,如果没有这对括号,则pf是一个返回A类数据成员(int型)指针的函数。
    注意:和普通函数指针不同的是,在成员函数和指向该成员的指针之间不存在自动转换规则
    pf = &A::add; //正确:必须显式地使用取址运算符(&)
    pf = A::add; // 错误

    当我们初始化一个成员函数指针时,其指向了类的某个成员函数,但并没有指定该成员所属的对象——直到使用成员函数指针时,才提供成员所属的对象。
    仔细观察后,除了一个有virtual,一个没有virtual,其它的都一样。是的,事实的确如此。

    ida动调分析

    我把所写的代码,然后生成可执行程序后,拖入ida进行分析一番
    首先分析

    非虚函数

    看了一下,它俩的汇编代码,主要是经过一个判断跳,如果是虚函数的话,那么它的寻址是比较复杂的(即跳到那个寻址比较多的代码),如果是非虚函数的话,那么它的寻址是超级简单

    在这里插入图片描述
    非虚函数执行完call _main 之后直接去取出 这个成员函数的地址

    在这里插入图片描述
    地址里面直接是函数的主体,毫无疑问,取出来的就是函数地址

    虚函数

    在这里插入图片描述
    看了一下反编译代码差不多,找到关键点(同一个位置的判断跳转,只是命名不同),jz short loc_401427 ,而这个判断跳转取决于上面取出来的eax值,接下来动调一下,

    虚函数执行完call _main 之后又多执行了几行代码

    .text:004013FA mov     [ebp+var_10], 1
    .text:00401401 mov     [ebp+var_C], 0
    .text:00401408 lea     eax, [ebp+var_28]
    .text:0040140B mov     [esp], eax
    .text:0040140E call    __ZN1aC1Ev                      ; a::a(void)
    

    然后取出地址的时候,并非直接取,而是又调用了一个函数,进行一系列的操作

    call __ZN1aC1Ev

    :00410EFC push    ebp
    .text:00410EFD mov     ebp, esp
    .text:00410EFF mov     eax, [ebp+arg_0]
    .text:00410F02 mov     dword ptr [eax], offset off_4469B8
    .text:00410F08 pop     ebp
    .text:00410F09 retn
    

    然后取地址也就这一行

     mov     dword ptr [eax], offset off_4469B8
    

    但是我们跟随一下,它是不是函数主体呢?
    在这里插入图片描述
    毫无疑问,不是。。。。所以这里取出来的是虚表的地址,再进一层,所以这个4469B8是虚表的地址。

    在这里插入图片描述
    才找到函数主体

    而且我们要看的是返回值,因为只有返回值才会赋值给我们的函数指针,并非看[eax],而是看eax,
    在这里插入图片描述
    执行完.text:00410EFF mov eax, [ebp+arg_0]
    发现eax里面的值是0x64ff18,看了一下也就是指针的地址。。

    在这里插入图片描述
    (就是从函数外面把函数指针的地址当做参数传进去,然后把虚表地址 放在指针里面,也就是把它当做虚表指针来使用。。记住,这个指针里面存放的不是函数的地址,而是虚表地址。)然后指针里面的值存放的是虚表的地址,虚表里面又放着虚函数的地址。
    在这里插入图片描述
    edx里面存放的是虚表的地址,然后再进行取内容,即取到了函数的地址,作为参数传过去。至于这加减操作,也就是虚表里面不一定只存放着一个虚函数的地址。。因为eax存放着偏移+1值,这里eax刚开始是既可以用作判断跳转,又可以当虚表偏移,所以加上eax值后必须自减1,回到正确的偏移

    近一步解释

    c++代码

    #include<iostream>
    using namespace std;
    
    class a {
    	
    public:
    	virtual int add(int a, int b) {
    		return a + b;
    	}
    	virtual int decrease(int a, int b) {
    		return a - b;
    	}
    };
    
    typedef int (a::* pClassFun)(int, int);
    
    
    int main() {
    
    	pClassFun pointer = &a::add;
    	a  aa;
    	cout << (aa.*pointer)(10, 20);
    	
    	pClassFun pointer1 = &a::decrease;
    	cout << (aa.*pointer1)(20, 10);
    }
    
    

    ida调试

    加了一个虚函数,加了一个函数指针指向它,接下来我们来看看是不是另外一个虚函数是不是紧接着放在虚表的中的第一个虚函数地址后面。如果是的话,那么另外一个函数指针就直接没用了,它直接通过第一个函数指针值加一个偏移再取内容就ok啦!

    在这里插入图片描述
    第一部分和上面一样。

    在这里插入图片描述
    第二部分是不是没有去再一次寻址了,也就是没有下面这个call

    在这里插入图片描述

    eax存放的是偏移加1值,然后一个指针是4个字节,第一个指针的偏移是0 ~ 3,第二个也就是4 ~ 7,eax存放起始偏移+1,所以也就是5

    在这里插入图片描述
    这里的意思不就是取出虚表地址,再取出函数地址相应的偏移,然后再取一次内容吗?(也就是取函数的地址)
    在这里插入图片描述

    注意

    dec是自减,也就是加上了5,但是偏移是4,所以减1,即第二个指针!!!因为eax存放着偏移+1值,这里eax刚开始是既可以用作判断跳转,又可以当虚表偏移,所以加上eax值后必须自减1,回到正确的偏移

    展开全文
  • 详解函数指针和类成员函数指针

    千次阅读 多人点赞 2017-11-12 21:35:56
    我觉得要理解这个问题,以及要理解后面的函数指针和类成员函数指针,没有什么比从计算机原理的角度来理解更容易了。这里就简要回顾一下相关知识。 众所周知,计算机(图灵机)执行程序的基本流程就是:取指令->执行...

    作者:倾夜·陨灭星尘

    一.什么是函数指针?

    函数指针,顾名思义即指向函数的指针。

    如果要问,为什么能用一个指针指向一个函数呢?我觉得要理解这个问题,以及要理解后面的函数指针和类成员函数指针,没有什么比从计算机原理的角度来理解更容易了。这里就简要回顾一下相关知识。

    众所周知,计算机(图灵机)执行程序的基本流程就是:取指令->执行指令->取下一条指令->……。取指令的位置由一个寄存器PC决定。开机时,PC通常指向0(这条指令常常是一条跳转指令。在ARM架构中,一般位置0是复位中断向量。总之都是要实现一个跳转),随后,如果没有碰到跳转指令,则PC自加一个字长执行下一条指令,否则根据跳转指令(JB,JMP,CALL等)跳转到给定位置(即改写PC)执行。

    常见的C语言程序中,if、while、for等带判断条件的指令是由条件跳转语句完成(ARM架构下指令为B,原理都是一样的)。以一个简单的条件跳转指令举例:

    if ( a == 0 )
    00CD52D5  cmp        dword ptr [a],0  ;比较
    00CD52D9  jne        main+34h (0CD52E4h)  ;根据结果跳转到0CD52E4h(if语句块后)
    goto、break、while语句则一般是jmp无条件跳转指令实现的。如以下程序:
    while ( a!=0 )
    008E52DF  cmp        dword ptr [a],0 
    008E52E3  je         main+40h (08E52F0h) 
    {
    }
    008E52E5  jmp        main+2Fh (08E52DFh) 

    可以看出来,跳转指令,无论是有无条件,无论寻址方式如何,其实都只是做了一件事——改变PC的值,跳转到指定位置。这个位置是由编译器给定的值,在此过程中,不会做除了改变PC以外的任何事情。

    CALL指令相对于跳转指令则不同。从设计目的的角度来说,CALL是为了实现函数调用。函数调用与一般的跳转相比,除了改变PC还需要考虑调用结束后恢复PC值返回原位置。高级语言中,都是使用堆栈来处理这问题:调用函数时,将返回的位置和传递参数压栈后跳转;调用结束时,弹出参数和返回位置随后跳回。现代处理器中,CALL指令除了跳转,还负责将返回位置压栈,相应的RET语句则集成了弹出返回位置和跳转。

    CALL指令的寻址方式也有多种(跳转语句同理)。概括性地说,有三种方式:相对转移(给PC加减一定值)、绝对转移(直接给PC赋值)、间接转移(将寄存器中的地址赋值给PC)。顺便一说,在RISC处理器中,往往只有最后一种。

    相对转移、绝对转移的目标地址,都是很简单地直接在指令当中给出。换句话说,跳转地址是在编译阶段就由编译器给定了。

    但是,有的时候我们需要在运行时动态地改变跳转地址。而相对转移、绝对转移是无法改变跳转地址的。所以,为了达到这一目的,一般来说,是先将跳转地址addr存入一个寄存器(或内存中一个位置),而后CPU从这个寄存器中取出地址addr进行跳转。这个地址,也就是我们所说的函数指针。

    我们进行的各种函数调用,本质上都是操作这一个地址,无论它是固化在指令中还是存储在寄存器中。而每一个函数名,就如同数组名一样,实际上都是一个地址。所以,理所当然地如同一般的指针变量一样,我们也可以有一个函数指针变量。

    二.函数指针的使用

    1.一般函数函数指针

    一般的函数指针形如:

    int( *pf )( char, int, float );

    这是一个有三个分别为char,int,float输入参数,返回值为int的函数指针变量pf。为什么*pf需要用()呢?因为*的运算符优先级比()低,如果不用()就成了*(pf())了。

    先举一个最简单的例子:

    voidNormalFunc()
    {
    cout << "Normal Func"<< endl;
    }
    void( *pfunc )();
    pfunc =NormalFunc;
    pfunc();//输出”Normal Func”

    稍复杂的如下

    int NormalFunc(char c, int i, float f )
    {
    cout << "Normal Func"<< c << i << f << endl;
    return 0;
    }
    int( *pfunc)(char,int ,float );
    pfunc =NormalFunc;
    pfunc(1,2,3);

    如果觉得这样定义太麻烦,可以用typedef做一个重命名。如:

    typedef int(*I_PFUNC_C_I_F )( char, int, float );//随手命名,一般情况下别这样
    I_PFUNC_C_I_Fpfunc;
    pfunc =NormalFunc;

    总之,函数指针和函数必须有着完全相同的类型(包括参数、返回值、调用约定_stdcall和_cdecl)。

    这样有什么好处呢?

    【动态调用、函数查找表】

    二.类成员函数函数指针

    对于类的成员函数,其指针与一般函数指针有着很大的区别。主要原因是类成员函数都隐含了一个this指针,调用时,编译器对其处理方式与一般函数不同,实际的汇编代码也不一样。因此,为了避免错误,编译器是不允许将类成员函数赋值给一般函数指针的。

    实际的汇编代码分析见第四节,这里我就只从使用的角度来解析:如何使用类成员函数指针?

    1.  常规操作方法(推荐)

    简单来说,就是在指针前加入一个域限定符,并指定成员函数对应的类实例。

    class mc:public empty
    {
    public:
           mc()
           {
            //构造函数中数组初始化,必须指定域
                  maps[0]= &mc::On_WM_PAINT;
                  maps[1]= &mc::On_WM_DESTORY;
           }
           typedefLRESULT (_stdcall mc:: *PCFUNC)( HWND, UINT, WPARAM, LPARAM );//类成员函数指针,必须指定域
           LRESULT_stdcall On_WM_PAINT(HWND, UINT, WPARAM, LPARAM)
           {
                  cout<< "ON_WM_PAINT" << endl;
                  return0;
           }
           LRESULT_stdcall On_WM_DESTORY( HWND, UINT, WPARAM, LPARAM )
           {
                  cout<< "ON_WM_DESTORY" << endl;
                  return0;
           }
           PCFUNCmaps[2];//类成员函数指针数组
           voidtest()
           {
                  (this->*maps[1])(NULL, NULL, NULL, NULL );//类内部调用
           }
    };

    使用时,在类内部需要加入this指针,在类外部则一定要指定对应的类实例:

     

          mcmyclass;//声明类实例
           mc::PCFUNCpcfunc = myclass.maps[0];//声明一个类成员函数指针变量
           (myclass.*pcfunc )( NULL, NULL, NULL, NULL );//调用第0个函数输出“ON_PAINT”
           myclass.test();//内部调用,输出“ON_DESTORY”
     

         

    2.  静态函数(不推荐)

    以上都是对非静态函数的使用方法。对于静态函数,则要容易得多。因为静态函数实际上就是一个作用域在类内部的普通函数,没有隐含的this指针。但是相应的,静态函数只能使用内部的静态变量。因此并不推荐。

    示例如下:

    class mc
    {
    public:
           mc()
           {
                  maps[0]= &mc::On_WM_PAINT;//不需要指定域
                  maps[1]= &mc::On_WM_DESTORY;
           }
           typedefLRESULT (_stdcall *PCFUNC)( HWND, UINT, WPARAM, LPARAM );//可见这里不需要指定域
           staticint i; 
           static LRESULT _stdcallOn_WM_PAINT(HWND, UINT, WPARAM, LPARAM)//需要static标识符
           {
                  cout<< "ON_WM_PAINT" << endl;
                  returni; //static函数只能使用static变量
           }
           staticLRESULT _stdcall On_WM_DESTORY( HWND, UINT, WPARAM, LPARAM )
           {
                  cout<< "ON_WM_DESTORY" << endl;
                  return0;
           }
           PCFUNCmaps[4];
           voidtest()
           {
                  maps[1](NULL, NULL, NULL, NULL );//不需要指定域
           }      
    };

    使用时也不需要指定域:

    mc::PCFUNCpcfunc = myclass.maps[0];
           myclass.test();
           pcfunc(NULL, NULL, NULL, NULL );

    3.  友元函数(较为推荐)

    可见,静态成员函数的最大限制在于其只能使用static成员变量,这使得其极其难以使用。一个好的折衷办法是使用友元函数。相应的,友元函数能够操作类中的所有成员,不过不一样的是必须显式指定类实例指针。

    class mc
    {
    public:
           mc()
           {
                  maps[0]= On_WM_PAINT;//同样不需要指定域
                  maps[1]= On_WM_DESTORY;
           }
           typedefLRESULT( _stdcall *PCFUNC )(mc&,HWND, UINT, WPARAM, LPARAM );//为了能够操作成员变量,必须传递类的引用或者指针
           staticint i;
           friendLRESULT _stdcall On_WM_PAINT(mc&src,HWND, UINT, WPARAM, LPARAM )
           {
                  cout<< "ON_WM_PAINT" << endl;
                  returnsrc.a;//这样就能通过类的引用操作成员变量,private限定的也可以
           }
           friendLRESULT _stdcall On_WM_DESTORY(mc&src, HWND, UINT, WPARAM, LPARAM )
           {
                  cout<< "ON_WM_DESTORY" << endl;
                  returnsrc.a;
           }
           PCFUNCmaps[4];
           voidtest()
           {
                  maps[1](*this,NULL, NULL, NULL, NULL );//内部调用需要使用this指针
           }
    private:
           int a;
    };
    

    在其它地方使用时,也不需要指定域。

           mcmyclass;
           mc::PCFUNCpcfunc = myclass.maps[0];
           myclass.test();
           pcfunc(myclass, NULL, NULL, NULL, NULL );
           On_WM_PAINT(myclass, NULL, NULL, NULL, NULL );//是的,即使是不用函数指针,也不需要.或者->,其实友元函数就是一个普通函数而已。
    

    如果不想在参数里面加一个引用,那么可以将这个类的指针放进一个全局变量里。如:

    mc*mc_handle=nullptr;//全局变量
    友元函数中:
    friend LRESULT _stdcall On_WM_PAINT(mc&src, HWND, UINT, WPARAM, LPARAM )
    {
           cout<< "ON_WM_PAINT" << endl;
           returnmc_handle->a;//通过全局变量操作
    }

    主函数中:

    mc myclass;
    mc_handle=&myclass;//如果不初始化的话,哼哼……
    On_WM_PAINT( myclass, NULL, NULL, NULL,NULL );

    【如果这个类只会有一个实例,可以使用“单件”设计模式

    【如果觉得用起来比较麻烦,还可以声明友元类而不是友元函数

    4.  C++11特性:std::function和std::bind(这么高端的操作为什么你不来试试?)

    C++在<functional>中引入了function和bind,使得能够更加容易地进行动态绑定。

    简单的实现如下:

    class mc
    {
    public:
           mc()
           {
                  fr[0] = bind( &mc::On_WM_PAINT,this,
                         placeholders::_1,placeholders::_2, placeholders::_3, placeholders::_4 );//绑定方式
           }
           LRESULT_stdcall On_WM_PAINT(  HWND, UINT,WPARAM, LPARAM )
           {
                  cout<< "ON_WM_PAINT" << endl;
                  returna;
           }
           LRESULT_stdcall On_WM_DESTORY( HWND, UINT, WPARAM, LPARAM )
           {
                  cout<< "ON_WM_DESTORY" << endl;
                  returna;
           }
    function<LRESULT _stdcall( HWND, UINT, WPARAM,LPARAM )>fr[4];//定义方式
    };
    

    这样,调用时只需要:

    mc myclass;
           myclass.fr[0]( NULL, NULL, NULL, NULL );//调用就是这么简单

    5.用宏定义来解放我们(让读代码的人蛋疼去吧)

    如果嫌弃上面的这么一大堆东西写起来太费劲,不妨使用下面这种宏定义:

    #define ON_WMESSAGE(msgname)  LRESULT _stdcall On_##msgname( HWND hwnd,UINTuMsgID,WPARAM wParam,LPARAM lParam )//宏定义
    #define MAP_ON_WMESSAGE(msgname)       {msgname,On_##msgname}
    ON_WMESSAGE( WM_PAINT );
    ON_WMESSAGE( WM_DESTROY );
    ON_WMESSAGE( WM_LBUTTONDBLCLK );
    ON_WMESSAGE( WM_COMMAND );
     
    struct sWM_CB
    {
           UINTuMsgID;
           LRESULT(_stdcall*WndProc )( HWND , UINT , WPARAM , LPARAM );
    };
    struct sWM_CB WM_CB_Maps[] = { 
           MAP_ON_WMESSAGE(WM_PAINT ),
           MAP_ON_WMESSAGE(WM_DESTROY ),
           MAP_ON_WMESSAGE(WM_LBUTTONDBLCLK ),
           MAP_ON_WMESSAGE(WM_COMMAND ), };
    

    以上宏定义中的##实际上是就是将宏定义参数中的字符直接连接的意思。所以,在这里,ON_WMESSAGE( WM_PAINT )等效语句实际上是:

    LRESULT_stdcall On_WM_PAINT( HWND hwnd,UINT uMsgID,WPARAM wParam,LPARAM lParam )

    MAP_ON_WMESSAGE( WM_PAINT )实际上是:

    { WM_PAINT, On_WM_PAINT }

    结合上以上的function,bind等,实际上构成了C++除了面向对象、面向过程、泛型模板之外的第四种编程——lamada元编程。如果去仔细探究windows.h源码,会发现这种编程方式到处都是。

    三.还有这种操作的函数指针(原创)

    实际上,对于计算机(特指冯诺依曼架构)而言,不管是int、char,还是什么指针、指令,其实都是一样的一个数,它们对CPU都没什么区别,让他们有区别的是我们对它的处理方式。

    函数指针也是一样的,理论上,我们可以对函数指针赋任何值。但是编译器会阻止我们,不允许我们胡乱赋值。可是,别忘了,C/C++语言之所以能够称为接近底层的高级语言,其中一个原因就是它有着一个极其强大、危险,同时也是魅力十足的特性,就是“强制类型转换”。

    所以,请看以下操作。

    我们先写上一个函数,在主函数中调用:

    void myfunc()
    {
     
    }

    然后,加入断点运行,并Alt+8打开反汇编:

    这里,我们可以看到全部机器码源代码。

    把这些源代码给写在一个数组里

    const int inst[] = { 0x81ec8b55,0x0000c0ec,0x57565300,0xFF40BD8D,0x30B9FFFF,0xb8000000,0xcccccccc,
    0x5e5fabf3,0x5de58b5b,0xc3c3c3c3};

    然后,我们定义一个函数指针

    void(*pfunc)();

    可是,这时候直接令pfunc=(void(*)())inst是不行的。我们可以迂回一下:

    inta = (int)inst;
    pfunc= (void(*)())a;

    好的,让我们执行吧!

    pfunc();

    设置一个断点,按下F11,我们成功地跳了进去:

    看出来了吗?这和源程序一摸一样!

    是不是很神奇!C++程序员,几乎无所不能!

    ……

    不过,需要浇一盆冷水的是,如果你跑一跑:

    我们什么地方错了?

    实际上,我们的原理是没有问题的,我保证,如果你把这个代码拿到8086、8051、STM32上,它是绝对能够运行的(没错,我以前在STM32上实现了)。关键原因在于,在80286以后,INTEL加入了内存分区的权限。只有高权限的情况下,才能在系统其它区域运行程序或者修改代码区程序。其原因显而易见——除了操作系统和底层驱动,其它应用程序都不应该得到修改系统代码的能力。想要修改安全区域代码的程序,不是初学者误用,就是恶意程序。

    事实上,我稍稍改了一下地址,我的可怜的杀毒软件就把我的程序杀掉了/(ㄒoㄒ)/~~

    但是,这种技术的应用是非常广泛的。手机操作系统更新、动态链接库、动态程序安全加密(SMC)……都用到了这种技术。毕竟说到底,任何程序都是数字的集合而已。

     

    四.进阶部分:

    可能你会问,平常写程序都是直接写成如func()这样的形式啊,并没有感觉在操作一个指针。实际上,这是因为编译器已经帮我们实现了这一功能,并不需要我们操心。在VS的DEBUG模式,_cdecl调用约定下,一个简单的函数调用如下:

    步骤1,使用CALL指令压栈跳转:

    func();

    00974F8D E8 92C4 FF FF       call        func (0971424h)

    步骤二,对应地址实际上是存储了一个跳转指令,执行跳转:

    func:

    00971424 E9 E737 00 00       jmp         func (0974C10h) 

    步骤三,随后跳转到的才是实际的函数部分,函数首先将当前寄存器状态压栈保护起来,执行程序,最后恢复现场并RET返回。

    void func( void)

    {

    00974C10 55                   push        ebp 

    00974C11 8BEC                mov         ebp,esp 

    00974C13 81 ECC0 00 00 00    sub         esp,0C0h 

    00974C19 53                   push        ebx 

    00974C1A 56                   push       esi 

    00974C1B 57                   push        edi 

    00974C1C 8D BD40 FF FF FF    lea         edi,[ebp-0C0h] 

    00974C22 B9 3000 00 00       mov         ecx,30h 

    00974C27 B8 CCCC CC CC       mov         eax,0CCCCCCCCh 

    00974C2C F3AB                rep stos    dword ptr es:[edi] 

    i++;

    00974C2E A0 80F6 97 00       mov         al,byte ptr ds:[0097F680h] 

    00974C33 0401                add         al,1 

    00974C35 A2 80F6 97 00       mov         byte ptr ds:[0097F680h],al 

    }

    00974C3A 5F                   pop         edi 

    00974C3B 5E                   pop         esi 

    00974C3C 5B                   pop         ebx 

    00974C3D 8BE5                mov         esp,ebp 

    00974C3F 5D                   pop         ebp 

    00974C40 C3                   ret 

    可以看出来,实际上相当繁琐。这主要是因为DEBUG模式下编译器没有进行优化。在RELEASE模式下:没有步骤二,直接跳转到函数体(我猜测DEBUG模式下多加入的步骤2是为了使用跳转表存储所有的函数,方便调试);不会将所有寄存器压栈,只会将函数中用到的压栈;对于短函数,会直接内联嵌入,不会进行函数调用。

    如果是函数指针,则步骤1的跳转将变为:

    pfunc();

    01195EFF 8BF4                mov         esi,esp 

    01195F01 FF 1520 03 1A 01    call        dword ptr ds:[11A0320h] 

    可以看出,实际上就是根据内存中地址进行了间接地址调用。DEBUG模式下,还会有以下两句:

    01195F07 3BF4                cmp         esi,esp 

    01195F09 E8 2BB4 FF FF       call        __RTC_CheckEsp (01191339h) 

    这是检查堆栈是否发生溢出用的,如果函数调用的压栈、出栈中发生错误使得前后堆栈位置不一致,则会调用__RTC_CheckEsp函数进行错误处理。

    而这一过程中有个问题,那就是我放入这个函数指针变量的数字,怎么保证它是一个函数呢?比如,要是我让pfunc=0x12345678,那会出现什么结果呢?

    很遗憾,事实上是,CPU根本无法保证它是一个函数。对于CPU来说,数据、指令都是一样的。对一个数字0x12345678来说,究竟是把它看作一个整数,还是4个char,还是一个float的一半,还是一个函数的地址,完全取决于编译器和你。换句话说,一个数字究竟发挥什么作用,不是取决于其本身,而是取决于它的身份。这个身份有个名字——数据类型。

    嗯,这句话如果推广的话,能说出很多看起来有哲理的话来呢。不过打住吧,我不想再打更多无关的字了(没看我连函数指针的用法都懒得写了吗?快进入让人激动的部分吧)

    对于C++这样的静态语言来说,当程序编译完成,编译器就无法对程序做出任何改变了。所以,为了阻止你搞破坏,它就只能在编译时阻止你。换句话说,就是必须做到“类型匹配”。所以,C++要求所有的变量都需要声明才能使用,这个过程实际上就是一个“给定身份的过程”。对于特定的指针,也就是特定的“身份”,编译器会有不同的一套处理方式。比如,对于int型指针来说,pi++实际上是pi+=4。而对于char型指针来说,pi++则是pi+=1。

    如:

    charstr[4]=”abc”;

    int *pi=str;

    这是不行的,编译器会识别出类型匹配错误,不允许通过编译。

    对于函数指针,如:

    void func(int)

    {

    //…

    }

    void(*pfunc)(int,int);

    pfunc=func;

    pfunc();

    也是不行的。对于func来说,在函数调用时会压栈1次传递1个参数,而pfunc,则会压栈2次。如果编译器让以上程序通过编译,则函数会压栈2次传递参数,返回时却只会弹出1次。而在栈参数下的值,是程序应该返回的位置,即调用时存储的CALL语句的下一条指令。如果少弹出一次,则会将第一个参数作为返回位置,换句话说,程序就“跑飞”了。这是非常严重的错误。因此,编译器不会允许这样的赋值。

    编译器花了这么大的力气,看起来好像能够堵住所有漏洞了。可是,C/C++语言之所以能够称为接近底层的高级语言,其中一个原因就是它有着一个极其强大、危险,同时也是魅力十足的特性,就是“强制类型转换”。

    类型转换非常常见。最常见的是每一本教材都会讲的“隐式类型转换”。如:

    int a=

    【/d1reportAllClassLayout】

    【Union联合体】

    【地址保护(80286加入)】

    【NULL和nullptr】

    【空类大小为1】

     

    展开全文
  • 由于的非静态成员函数中有一个隐形的this指针,因此,成员函数的指针和一般函数的指针的表现形式不一样。 1、指向一般函数的指针函数指针的声明中就包括了函数的参数类型、顺序和返回值,只能把相匹配的函数...
  • 文章目录函数指针(function pointer)普通函数指针成员函数指针(重点) 函数指针(function pointer) 与普通的指针一样,函数指针也是指针,但是不同的是函数指针指向的不是类型,而是函数地址。函数指针是通过...

    函数指针(function pointer)

    与普通的指针一样,函数指针也是指针,但是不同的是函数指针指向的不是类型,而是函数地址。函数指针是通过指向函数的指针间接调用函数。相信很多人对指向一般函数的函数指针使用的比较多,而对指向类成员函数的函数指针则比较陌生。

    普通函数指针

    通常我们所说的 函数指针 指的是指向一般普通函数的指针。和其他指针一样,函数指针指向某种特定类型,
    所有被同一指针运用的函数必须具有相同的形参类型和返回类型。

    int (*pf)(int, int);   // 声明函数指针   
    

    这里,pf指向的函数类型是int (int, int),即函数的参数是两个int型,返回值也是int型。
    注:pf两端的括号必不可少,如果不写这对括号,则pf是一个返回值为int* 的函数。

    下面来看个例子:

    namespace part1 {
    typedef int (*pFun)(int, int);
    
    int add(int a, int b) {
      return a + b;
    }
    
    string my_merge(const string& s1, const string& s2) {
      return s1 + s2;
    }
    
    void test() {
      pFun pf1 = add;
      cout << (*pf1)(2, 3) << endl;
      string (*pf2)(const string&, const string&) = my_merge;
      cout << (*pf2)("hello ", "world") << endl;
      // 下面两个输出一样:
      //0x1093e8ae0
      //0x1093e8ae0
      cout << reinterpret_cast<void*>(add) << endl;
      cout << reinterpret_cast<void*>(&add) << endl;
    
    }
    }  // namespace part1
    

    如示例代码,直接声明函数指针变量显得冗长而烦琐,所以我们可以使用typedef定义自己的函数指针类型。
    同时上面我有一个好奇的点,就是当我们:
    pFun pf1 = add;的时候,为什么是这样?
    经过查阅资料,发现这个这是一个省略的写法,也就是本来是这样的:
    pFun pf1 = &add;
    我画个图解释一下:
    这是pf1 = add;的情况
    在这里插入图片描述
    下面这是pf1 = &add;的情况:
    在这里插入图片描述

    成员函数指针(重点)

    成员函数指针(member function pointer)
    是指可以指向类的非静态成员函数(non-static member function)的指针。类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有什么区别。
    与普通函数指针不同的是,
    成员函数指针不仅要指定目标函数的形参列表和返回类型,**还必须指出成员函数所属的类。**因此,我们必须在*之前添加classname::以表示当前定义的指针指向classname的成员函树, 如下:

    int (A::*pf)(int, int);	// 声明一个成员函数指针
    

    同理,这里A::*pf两端的括号也是必不可少的,如果没有这对括号,则pf是一个返回A类数据成员(int型)指针的函数。
    注意:和普通函数指针不同的是,在成员函数和指向该成员的指针之间不存在自动转换规则。

    pf = &A::add; // 正确:必须显式地使用取址运算符(&)
    pf = A::add; // 错误

    当我们初始化一个成员函数指针时,其指向了类的某个成员函数,但并没有指定该成员所属的对象——直到使用成员函数指针时,才提供成员所属的对象。
    下面是一个成员函数指针的使用示例:

    namespace part2 {
    class A;
    typedef int (A::*pClassFun)(int, int);  // 成员函数指针类型
    class A {
     public:
      int add(int a, int b) {
        return a + b;
      }
      static int sub(int a, int b) {
        return a - b;
      }
    };
    
    void test() {
      // 绑定时不要类的对象
      pClassFun pcf1 = &A::add;  // 这里一定要加取地符,至于为什么,我也不知道
      // 如果不加的话,会出现:error: call to non-static member function without an object argument
      // 个人猜测是,没加前编译器会把这个语法默认调用为函数,加了地址后就不会去调用了
      A a;
      cout << (a.*pcf1)(10, 20) << endl;  // 30
      A* b = new A;
      cout << (b->*pcf1)(30, 30) << endl;  // 60
    
      // ----------------------------------------
      // 类静态成员函数
      typedef int (*pF)(int, int);  // 不用加类
      pF pf = &A::sub;
      cout << (*pf)(20, 2) << endl;  // 18
    }
    }   // namespace part2
    

    如示例所示,我们一样可以使用typedef定义成员函数指针的类型别名。
    另外, 我们需要留意函数指针的使用方法

    • 对于普通函数指针,是这样使用(*pf)(arguments),因为要调用函数,必须先解引用函数指针,而函数调用运算符()的优先级较高,所以(*pf)的括号必不可少;
    • 对于成员函数指针,唯一的不同是需要在某一对象上调用函数,所以只需要加上成员访问符即可:
     (obj.*pf)(arguments)         // obj 是对象  
     (objptr->*pf)(arguments)     // objptr是对象指针 
    

    (完)

    展开全文
  • 主要介绍了C++获取成员函数函数指针详解及实例代码的相关资料,需要的朋友可以参考下
  • C++ 类成员函数指针的使用方法

    千次阅读 2019-07-31 13:55:32
    C++ 类成员函数指针的使用方法 #include <iostream> void func(){ std::cout << "void func()" << std::endl; }; class Test { public: Test() {} virtual ~Test() {} public: bool func1...
  • C++ 函数指针 & 类成员函数指针

    万次阅读 多人点赞 2013-11-21 21:17:02
    一、函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。 1、函数指针的定义方式:...
  • 之前一直以为C++成员函数没法取地址,一直使用静态函数来充当回调函数,直到近日看别人的代码才发现类成员函数也有指针。。。总结记录一下。 普通函数指针 这面是普通函数的 指针的使用方法。 #include &lt;...
  • C++提供static这个关键词对静态成员进行声明,静态成员函数的实例化无关,对于同一来说,静态成员函数是所有的对象共享的。而普通成员函数需要实例化才能调用,对于每一个实例来说,普通成员函数是自己独有...
  • C++ 类成员函数函数指针

    万次阅读 多人点赞 2018-08-23 18:40:17
    当我们在 C++ 中直接像 C 那样使用成员函数指针时,通常会报错,提示你不能使用非静态的函数指针: reference to non-static member function must be called 两个解决方法: 把非静态的成员方法改成静态的...
  • 例如有如下,class A ...怎么声明A的成员函数指针或成员变量指针呢?int* (A::*func_ptr) (void) = &A::func; int A::*obj_ptr = &A::mem;上面声明了指向成员函数func的函数指针,和指向成员变量mem的变量指针
  • 通过函数指针调用一个函数的时候,此函数必须有具体的地址,因此当调用一个类成员函数的时候,此成员函数必须声明为static,即静态成员函数,这样就能通过函数指针获取到成员函数的地址,进而调用。
  • c++成员函数指针的本质

    千次阅读 2018-06-16 11:19:57
    关于c++成员函数指针的声明,类型定义,赋值和调用就不再赘述了,需要了解的朋友可以看这篇文章: ...现在我要证明,在我的编译环境中,所看到的c++的成员函数指针...
  • C++指向类成员函数指针详细解析

    千次阅读 2019-02-14 14:10:59
    类成员函数我们也可以相似的认为,它是指向同类中同一组类型的成员函数指针,当然这里的成员函数更准确的讲应该是指非静态的成员函数。前者是直接指向函数地址的,而后者我们从字面上也可以知道 它肯定是跟和...
  • 在algorithm算法中经常会碰到传递函数指针的...非静态非虚函数的函数指针,因为成员函数里面会传递this指针,所以就算是函数指针,也必须有对象来调用他们。 非静态非虚函数的函数指针格式: 返回类型(类名::*指...
  • 1.类成员函数指针调用  注意:如果在内调用,则函数指针声明要放在里面.如果是外调用,则函数指针声明放在调用中. CFunCall.h文件: class CFunCall { typedef int (CFunCall::*CLASSFUNC)(int); public: ...
  • 类成员函数指针小结

    千次阅读 2019-10-16 16:15:24
    成员函数指针 静态成员函数指针 非静态成员函数指针
  • 如何声明类成员函数指针

    千次阅读 2014-01-21 15:28:06
    比如: 有如下的一个, 里面有一个成员函数 Func() class Test {  public: ... 我们知道成员函数都隐含了一个this 变量作为参数,亦即“this”指针。 如果类成员函数原型是这样的 : int
  • C++中一般函数指针和成员函数指针的区别
  • C++ 函数指针 指向类成员函数

    千次阅读 2018-12-07 10:14:19
    //A与B的定义 class A { public:  void Test()  {  cout &lt;&lt; "A::Test()" &lt;&lt; endl;  } };   class B : public A { public:  void Test()  {  cout &lt;&...
  • ``` class A { public: int add(int a,int b) { int c = a+b; return c; } }; typedef int (*pfun)(int,int); int main() { ...难道成员函数指针转为一般函数指针不安全吗?
  • C++获取成员函数函数指针

    万次阅读 2017-08-01 11:02:43
    注意调用中非静态成员函数的时候,使用的是类名::函数名;而不是实例名::函数名。
  • c++中的函数指针和类成员函数指针

    千次阅读 2016-12-28 23:57:32
    在c中函数指针的形式很简单 函数返回type+(*函数名)+参数,为了使用方便我们经常通过typedef来重新定义,下面来看一个例子。 #include typedef int (*DGUCO_FUNC) (int,int); //加 int add(int a,int b) { ...
  • C++ 指向类成员指针

    2020-12-20 19:10:14
    前面曾写过一篇恼人的函数指针(一...简单的讲,指向类成员函数的指针与普通函数指针的区别在于,前者不仅要匹配函数的参数类型和个数以及返回值类型,还要匹配该函数指针所属的类型。总结一下,比较以下几点: a)参
  • 指向类成员/函数指针

    千次阅读 2018-08-23 16:36:04
    指向类成员/函数指针的本质并不是取地址.而是利用了对象地址的偏移量 我们创建了一个,假设我们要使用指针指向中的成员 class Student { public: Student(string n,int nu):name{n},num{nu}{} void dis...
  • //todo/*[root@Slave02 thread]# g++ bind.cpp -o bind -g -Wall -std=gnu++11 -lpthread;./bindclass void Test::print(const ... str,int32_t i) 对比测试 std::mem_fn 和 std::bind,并调用重载函数,1class voi...
  • 将一个类成员函数函数指针传递给另一个成员函数是比较简单的,只要定义一个函数指针就可以轻松实现。示例如下: #include using namespace std; class test { public: typedef void (test::*pFUN)(); void ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 455,577
精华内容 182,230
关键字:

类的成员函数指针