当前位置:   article > 正文

C/C++之回调函数与函数指针和类成员函数指针_c++回调函数函数指针可以是类对象的成员函数吗

c++回调函数函数指针可以是类对象的成员函数吗

函数指针

(1)概念:指针是一个变量,是用来指向内存地址的。一个程序运行时,所有和运行相关的物件都是需要加载到内存中,这就决定了程序运行时的任何物件都可以用指针来指向它。函数是存放在内存代码区域内的,它们同样有地址,因此同样可以用指针来存取函数,把这种指向函数入口地址的指针称为函数指针。

(2)先来看一个Hello World程序:

  1. int main(int argc,char* argv[])
  2. {
  3. printf("Hello World!\n");
  4. return 0;
  5. }

       然后,采用函数调用的形式来实现:

  1. void Invoke(char* s);
  2. int main(int argc,char* argv[])
  3. {
  4. Invoke("Hello World!\n");
  5. return 0;
  6. }
  7. void Invoke(char* s)
  8. {
  9. printf(s);
  10. }

      用函数指针的方式来实现:

  1. void Invoke(char* s);
  2. int main()
  3. {
  4. void (*fp)(char* s); //声明一个函数指针(fp)
  5. fp=Invoke; //将Invoke函数的入口地址赋值给fp
  6. fp("Hello World!\n"); //函数指针fp实现函数调用
  7. return 0;
  8. }
  9. void Invoke(char* s)
  10. {
  11. printf(s);
  12. }

      由上知道:函数指针函数的声明之间唯一区别就是,用指针名(*fp)代替了函数名Invoke,这样这声明了一个函数指针,然后进行赋值fp=Invoke就可以进行函数指针的调用了。声明函数指针时,只要函数返回值类型、参数个数、参数类型等保持一致,就可以声明一个函数指针了。注意,函数指针必须用括号括起来 void (*fp)(char* s)。

     实际中,为了方便,通常用宏定义的方式来声明函数指针,实现程序如下:

  1. typedef void (*FP)(char* s);
  2. void Invoke(char* s);
  3. int main(int argc,char* argv[])
  4. {
  5. FP fp; //通常是用宏FP来声明一个函数指针fp
  6. fp=Invoke;
  7. fp("Hello World!\n");
  8. return 0;
  9. }
  10. void Invoke(char* s)
  11. {
  12. printf(s);
  13. }

 

函数指针数组

      下面用程序对函数指针数组来个大致了解:

 

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. typedef void (*FP)(char* s);
  5. void f1(char* s){cout<<s;}
  6. void f2(char* s){cout<<s;}
  7. void f3(char* s){cout<<s;}
  8. int main(int argc,char* argv[])
  9. {
  10. void* a[]={f1,f2,f3}; //定义了指针数组,这里a是一个普通指针
  11. a[0]("Hello World!\n"); //编译错误,指针数组不能用下标的方式来调用函数
  12. FP f[]={f1,f2,f3}; //定义一个函数指针的数组,这里的f是一个函数指针
  13. f[0]("Hello World!\n"); //正确,函数指针的数组进行下标操作可以进行函数的间接调用
  14. return 0;
  15. }

 

回调函数

(1)概念:回调函数,顾名思义,就是使用者自己定义一个函数,使用者自己实现这个函数的程序内容,然后把这个函数作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数。

(2)标准Hello World程序:

  1. int main(int argc,char* argv[])
  2. {
  3. printf("Hello World!\n");
  4. return 0;
  5. }

      将它修改成函数回调样式:

  1. //定义回调函数
  2. void PrintfText()
  3. {
  4. printf("Hello World!\n");
  5. }
  6. //定义实现回调函数的"调用函数"
  7. void CallPrintfText(void (*callfuct)())
  8. {
  9. callfuct();
  10. }
  11. //在main函数中实现函数回调
  12. int main(int argc,char* argv[])
  13. {
  14. CallPrintfText(PrintfText);
  15. return 0;
  16. }

 

      修改成带参的回调样式:

 

  1. //定义带参回调函数
  2. void PrintfText(char* s)
  3. {
  4. printf(s);
  5. }
  6. //定义实现带参回调函数的"调用函数"
  7. void CallPrintfText(void (*callfuct)(char*),char* s)
  8. {
  9. callfuct(s);
  10. }
  11. //在main函数中实现带参的函数回调
  12. int main(int argc,char* argv[])
  13. {
  14. CallPrintfText(PrintfText,"Hello World!\n");
  15. return 0;
  16. }

      至此,对回调函数应该有了一个大致的了解。

 

C++ 函数指针 & 类成员函数指针

一、函数指针

函数存放在内存的代码区域内,它们同样有地址.如果我们有一个 int test(int a) 的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。

1、函数指针的定义方式

data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn);

例如:

int (*fp)(int a); // 这里就定义了一个指向函数(这个函数参数仅仅为一个 int 类型,函数返回值是 int 类型)的指针 fp。

实例

int test(int a) { return a; } int main(int argc, const char * argv[]) { int (*fp)(int a); fp = test; cout<<fp(2)<<endl; return 0; }

注意:函数指针所指向的函数一定要保持函数的返回值类型,函数参数个数,类型一致。

2、typedef 定义可以简化函数指针的定义

实例

int test(int a) { return a; } int main(int argc, const char * argv[]) { typedef int (*fp)(int a); fp f = test; cout<<f(2)<<endl; return 0; }

3、 函数指针同样是可以作为参数传递给函数的

实例

int test(int a) { return a-1; } int test2(int (*fun)(int),int b) { int c = fun(10)+b; return c; } int main(int argc, const char * argv[]) { typedef int (*fp)(int a); fp f = test; cout<<test2(f, 1)<<endl; // 调用 test2 的时候,把test函数的地址作为参数传递给了 test2 return 0; }

执行以上代码,输出结果为:

10

4、利用函数指针,我们可以构成函数指针数组,更明确点的说法是构成指向函数的指针数组。

实例

void t1(){cout<<"test1"<<endl;} void t2(){cout<<"test2"<<endl;} void t3(){cout<<"test3"<<endl;} int main(int argc, const char * argv[]) { typedef void (*fp)(void); fp b[] = {t1,t2,t3}; // b[] 为一个指向函数的指针数组 b[0](); // 利用指向函数的指针数组进行下标操作就可以进行函数的间接调用了 return 0; }

二、指向类成员函数的函数指针

定义:类成员函数指针(member function pointer),是 C++ 语言的一类指针数据类型,用于存储一个指定类具有给定的形参列表与返回值类型的成员函数的访问信息。

基本上要注意的有两点:

  • 1、函数指针赋值要使用 &
  • 2、使用 .* (实例对象)或者 ->*(实例对象指针)调用类成员函数指针所指向的函数

下面看两个例子:

A) 类成员函数指针指向类中的非静态成员函数

对于 nonstatic member function (非静态成员函数)取地址,获得该函数在内存中的实际地址

对于 virtual function(虚函数), 其地址在编译时期是未知的,所以对于 virtual member function(虚成员函数)取其地址,所能获得的只是一个索引值

实例

//指向类成员函数的函数指针 #include <iostream> #include <cstdio> using namespace std; class A { public: A(int aa = 0):a(aa){} ~A(){} void setA(int aa = 1) { a = aa; } virtual void print() { cout << "A: " << a << endl; } virtual void printa() { cout << "A1: " << a << endl; } private: int a; }; class B:public A { public: B():A(), b(0){} B(int aa, int bb):A(aa), b(bb){} ~B(){} virtual void print() { A::print(); cout << "B: " << b << endl; } virtual void printa() { A::printa(); cout << "B: " << b << endl; } private: int b; }; int main(void) { A a; B b; void (A::*ptr)(int) = &A::setA; A* pa = &a; //对于非虚函数,返回其在内存的真实地址 printf("A::set(): %p\n", &A::setA); //对于虚函数, 返回其在虚函数表的偏移位置 printf("B::print(): %p\n", &A::print); printf("B::print(): %p\n", &A::printa); a.print(); a.setA(10); a.print(); a.setA(100); a.print(); //对于指向类成员函数的函数指针,引用时必须传入一个类对象的this指针,所以必须由类实体调用 (pa->*ptr)(1000); a.print(); (a.*ptr)(10000); a.print(); return 0; }

执行以上代码,输出结果为:

  1. A::set(): 0x8048a38
  2. B::print(): 0x1
  3. B::print(): 0x5
  4. A: 0
  5. A: 10
  6. A: 100
  7. A: 1000
  8. A: 10000

B) 类成员函数指针指向类中的静态成员函数

实例

#include <iostream> using namespace std; class A{ public: //p1是一个指向非static成员函数的函数指针 void (A::*p1)(void); //p2是一个指向static成员函数的函数指针 void (*p2)(void); A(){ /*对 **指向非static成员函数的指针 **和 **指向static成员函数的指针 **的变量的赋值方式是一样的,都是&ClassName::memberVariable形式 **区别在于: **对p1只能用非static成员函数赋值 **对p2只能用static成员函数赋值 ** **再有,赋值时如果直接&memberVariable,则在VS中报"编译器错误 C2276" **参见:http://msdn.microsoft.com/zh-cn/library/850cstw1.aspx */ p1 =&A::funa; //函数指针赋值一定要使用 & p2 =&A::funb; //p1 =&A::funb;//error //p2 =&A::funa;//error //p1=&funa;//error,编译器错误 C2276 //p2=&funb;//error,编译器错误 C2276 } void funa(void){ puts("A"); } static void funb(void){ puts("B"); } }; int main() { A a; //p是指向A中非static成员函数的函数指针 void (A::*p)(void); (a.*a.p1)(); //打印 A //使用.*(实例对象)或者->*(实例对象指针)调用类成员函数指针所指向的函数 p = a.p1; (a.*p)();//打印 A A *b = &a; (b->*p)(); //打印 A /*尽管a.p2本身是个非static变量,但是a.p2是指向static函数的函数指针, **所以下面这就话是错的! */ // p = a.p2;//error void (*pp)(void); pp = &A::funb; pp(); //打印 B return 0; }


总结

类成员函数指针与普通函数指针不是一码事。前者要用 .* 与 ->* 运算符来使用,而后者可以用 * 运算符(称为"解引用"dereference,或称"间址"indirection)。

普通函数指针实际上保存的是函数体的开始地址,因此也称"代码指针",以区别于 C/C++ 最常用的数据指针。

而类成员函数指针就不仅仅是类成员函数的内存起始地址,还需要能解决因为 C++ 的多重继承、虚继承而带来的类实例地址的调整问题,所以类成员函数指针在调用的时候一定要传入类实例对象。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/305891
推荐阅读
相关标签
  

闽ICP备14008679号