木老师教笨笨课堂——系列讲座(从函数指针到委托) 三、C++语言里的函数指针
先来回顾一下,我们说函数指针有一个重要的特性,它可以指向任何形参相同的函数。这样可以用统一的方式去调用不同的函数。(这个我还是希望笨笨同学好好思考一下,我想也是程序思想的精髓之一吧),就好比武功秘籍中九字真言一般。“统一的方式去调用不同的实现”,如果在面向对象里就是“多态”二字。
C++语言里当然也有函数指针。面向对象的引入,出现了类、对象的概念。以前函数指针只是指向的一般的函数,或者说是全局函数。但是类的出现,有了方法。于是我们会想函数指针除了指向一般的全局函数,能否指向类的方法。这是很自然的想法,就好比我们在武侠中一次掉落悬崖,没有摔死,反而获得武功秘籍一般。于是在江湖行走,看到各大悬崖,都有纵身一跳的欲望一般。
图3-1
图3-2
实践证明是
..........
..........
..........
不行的。
当然这个也很好理解,因为不是每次跳悬崖都会有武功秘籍,美女和猿猴(猿猴需要参见金庸先生的《倚天屠龙记》)的。问题出在哪儿呢?问题在于类的成员函数,类的成员函数和一般的全局函数不一样,在于它隐藏了一个this指针。比如一个类
class CA { public: CA(); virtual ~CA(); void Fun(){}; }; |
例3-1
Fun函数其实质是Fun(CA* this),因此我们申明一个函数指针void (*fp)()指向它的话,编译是无法通过的。
有两种变通的方法,一种是把类的方法变成全局的方法,比如:
class CA { public: CA(); virtual ~CA(); }; void Fun(CA* pa){}; |
例3-2
此时函数可以声明成void (*f)(CA* fa),调用如下:
f = Fun; f(); |
还有一种办法是把成员函数声明成静态的函数。因为静态的成员函数属于类的成员,而不再属于对象的成员,因此它的函数的形参并不带有隐含的this指针。见如下代码:
class CA { public: CA(); virtual ~CA(); void Fun(){printf("CA::Fun()"n");}; static void StaticFun() {printf("CA::StaticFun()"n");} }; |
此时函数声明为:void (*f3)();
f3 = CA::StaticFun; f3(); |
那如果不想变通怎么办呢?当然也不是没有办法滴!只不过函数指针声明和调用的方法都有所不同。这个地方 笨笨同学注意了!因为一般的书很少会涉及到这个问题。
类成员的函数指针声明:
1、比如表示CA类的Fun()方法的函数指针,声明如下
void (CA::*f1)();
2、将函数指针指向该方法成员
f1 = CA::Fun;
类成员的函数指针调用
一般我们很容易写成 f1(); 但这种在编译的时候,编译器会报错“term does not evaluate to a function”
仍然是因为这样的调用,函数指针无法获得this指针的原因。应该写成:
CA a;
(a.*f1)();
笨笨看到此处,说:“这有什么用呢?太复杂了,我不学了。”
其实木老师讲了这么多,也没有打算让笨笨学会,因为笨笨实在是..........太笨了。笨笨这个角色的引入主要还是为了广大的聪明读者。一边去,笨笨。(嘘!不过不能让他知道,因为即使他再笨,也知道“一边去”并不是什么好话)。
咳.....咳.....实际上这样的函数指针的用法是会在面向对象的语言里用的,一个最典型的就是MFC里的消息函数。大家如果有兴趣,先利用VC的搜索工具,搜索“::*”可以发现总共有246条记录。其中pfn_这样的前缀的都是消息函数指针。如果想仔细探究,就等木老师以后讲解MFC里的消息机制,当然大名鼎鼎的《深入浅出MFC》或者《深入解析MFC》两本书都可以看,但是木老师的建议是最好看看书,然后直接看MFC的源码,调试MFC的源码会比较好一点。
函数指针到此,应该说介绍得较为全面了。当然还有一个就是函数指针数组,我们并没有介绍,它和一般的数组区别并不大,只不过数组里存储的是函数指针而已。我们说函数指针有一个重要的特性,即函数指针可以指向任何形参相同的函数,有了这个特性,则可以用统一的调用方式来完成不同的实现。我们知道在面向对象的概念中,多态是一个非常重要的概念,在面向对象的语言里,我们是使用虚函数或者纯虚函数来实现多态这个概念,那么在面向过程的C语言里,则是用函数指针来完成这一概念。下一节里,我们就来介绍C#里的委托机制。