当前位置:C++技术网 > 资讯 > 普通函数指针、类静态函数指针、类成员函数指针全面分析

普通函数指针、类静态函数指针、类成员函数指针全面分析

更新时间:2017-07-11 00:58:20浏览次数:1+次

    函数指针作为进阶的知识,是非常有用的,可以让程序变得非常灵活高效。今天用到这部分的知识,结果发现对于指针的使用有所淡忘了。所以研究了一个晚上,思考为什么这些指针要这样使用,为什么是这样的语法,以及如何理解这些函数指针的使用规则。
    所幸,通过看书和深入细致冷静的分析,还是收获挺多,在此总结一下,让会员也参考学习一下。在STL、Boost、QT等各种项目代码里,到处都充斥这函数指针,如果不会的话,或者迷迷糊糊的话,终究得不到这些代码的精髓。
    按照我的理解,我将这些函数指针对应的函数进行一个分类,分类的方法就是:静态函数和动态函数。注意,这里是我自己叫的名称,方便理解。

1.静态函数
    所谓的静态函数,是在编译期间就确定的。不仅知道函数的声明,还知道函数的具体实现。这类函数有最普通的函数,如main,以及C语言各种库函数,还有你自己写的各种C语言函数。简单来说,就是C风格的函数,也可以叫做全局函数,作用范围是代码文件。另一种就是类静态函数。类静态函数有作用域范围,在类内使用。当然,你可以通过类名来使用类静态函数。除了作用范围和C风格的函数不一样,其他基本一样。他们都是编译期间就确定好了的。

2.动态函数
    所谓的动态函数,指的是函数并不是编译期间就固定的。这类函数就是类成员函数。虽然这些函数是我们预先写好的,但是这些函数不属于类,而是属于对象。也就是创建了对象之后,这些函数才可用。如果没有对象,这些函数相当于不存在,更别说让你去调用了。所以类成员函数随对象的创建而可用,由对象析构而不可用。当然,成员变量也是如此。

一、函数使用
    好了,说完这两类函数,现在就可以说说函数指针了。我们都知道,函数名也就是函数指针。所以我们一般对于普通C函数的使用,也就是函数名加上()。而类静态函数则只需要加一个类作用域就行了。也就是这样的一个用法,C函数可以当做全局函数来看待,因为不需要写所谓的类作用域。例如:
show();
A::show();
    那么类成员函数如何使用呢?当然,我们需要对象的支持,所以先要创建一个对象,然后再使用。如:
A a;
a.show();

二、函数指针的声明
    函数指针的声明,我们只需要将普通函数声明的函数名替换为指针声明,就达到效果了。如:
int show();
int (*pshow)();
    我们将show替换为(*pshow)就达到效果了。为什么要加()呢?反过来想一下,如果不要(),你看看是什么效果:
int *pshow1();
    这不就是一个返回int*的函数嘛!哪来的函数指针了呢?所以,括号就是为了实现声明函数指针的。那么为什么要声明函数指针呢?可不可以不要*呢?如果你不要*号,就是这样的效果:
int (pshow2)();//即 int pshow2();
    这不就是声明普通的函数嘛。所以函数指针是可以指向函数的。所以,
pshow = show;
    这样就顺理成章了。这就是C++语法的一套。当你不理解的时候,你可以反过来拆,反过来想,结果也就得到了一致的效果。语法的实现过程,也就是要让不同的规则有不同的表示,然后拆来拆去最后还是统一的。
    不过呢,在来看看这样的代码:
pshow();
(*pshow)();
    这两句函数调用,是否有错误呢?第一个是指针可以直接调用,和指针使用变量规则相似。而第二个呢?因为pshow是函数指针,所以取值之后,就得到函数,这样再调用函数,也说得过去。所以C++中这两种用法都是可以的。不同的人有不同的看法,只是C++语言进行了折中,婆说婆有理公说公有理,那就保持和睦共存好了。

三、函数指针的赋值
    对于普通的函数指针,也就是指向C风格函数的函数指针,赋值很简单:
int test();
int (*ptest)();
ptest = test;
    就将函数名当做变量赋值就行了。因为函数名就是函数地址,所以可以直接给指针,而不需要取地址了。那么对于类静态函数呢?既然类静态函数与C函数只是在于作用域不同而已,所以在用法也就是作用域的差异而已,只要在函数前面带上类作用域就行了。如:
ptest = A::show();
    当然,你需要事先声明类和类静态函数。对于普通函数指针来说,不需要做任何作用域的声明,就是全局的。所以对普通函数指针来说,可以指向普通的C函数,还可以指向各个类的静态函数。
    类中的静态函数和普通C函数一样,那么我们将普通函数指针声明在类外和声明在类内也是差不多的,只是一个作用域范围的差异。
    我们需要注意的就是动态函数的函数指针的使用。我们在定义动态函数指针的时候,也需要注意。因为动态函数指针是属于对象的,而对象是类创建出来的实例,所以我们在声明动态函数指针的时候,就需要指定类作用域了。普通函数指针不管是在类外类内,都无需指定。指定类作用域的仅仅是用来声明动态函数指针的。如:
void (A::*PFUN_CLASS)();//动态函数指针
void (*PFUN)();//静态函数指针
     而在用函数指针传参的时候,静态函数指针和动态函数指针也是有差异的。我们来看一个函数的声明:
void show2(PFUN_CLASS pfun_class,PFUN pfun,PFUN_B pfun_b);
    第一个参数是动态函数指针,第二个是普通函数指针,第三个是指向B类的静态函数指针。而他们的传参是这样的:
show2(&A::show3,show,B::showb);
    而动态函数指针的直接赋值也就是这样的:
pfun_class = &A::show3;
    第二个我们很好理解,第三个和第一个的差别就在于一个&符号。我们来想想这个差别是为什么?对于类静态函数的使用,我们是不是都是直接使用类作用域来使用的!所以呢,B::showb的写法也就很自然而然的了。既然如此,我们当然不能再用这样的语法来支持动态函数指针的赋值了吧,不然还怎么区分呢?而使用&也是有原因的。对于类的成员函数,可以进行函数重载,也就是说,同一个函数名可以有很多个参数列表的版本,这样一来,一个函数名可以列出一大堆的函数。那么此时,你用A::show3也无法指定是某一个成员函数吧。那要如何最终确定赋值给动态函数指针的值的呢?当然就是动态确定的啦。当传参进去的时候,自然就可以知道被传入的函数的类型了,然后就可以在成员函数重载版本列表里寻找一个匹配的版本咯。如果没有匹配的版本,自然也就类型不匹配。所以,这个参数我们使用取地址符来表示得到重载成员函数列表的地址,在需要的时候查询列表得到匹配的版本,这样是不是就顺理成章了呢!!
    这样一说,你是不是也就理解了呢!其实搞懂语法背后的意义,是一件非常有趣而有意义的事情,可以让你了解的更加深刻,更不用说牢牢掌握语法知识了。
    然后再要说的一个就是,动态函数指针得到了赋值之后,如何调用函数呢?因为类成员函数是随对象的,所以我们在类成员函数中写动态函数指针的函数调用,是不是可以直接和普通函数一样使用呢?如:
void (A::*PFUN_CLASS)();//动态函数指针
void (*PFUN)();//静态函数指针

void show(){}
void A::test(){}

void B::T()
{
    PFUN = show;//普通函数指针
    PFUN();

    PFUN_CLASS=&A::test();//动态函数指针
    PFUN_CLASS();//???这样对吗??
}
    我们先不直接讨论上面这个对不对,先来看看普通的成员函数的调用。如:
void A::show2(PFUN_CLASS pfun_class,PFUN pfun,PFUN_B pfun_b)
{
    test();
    this->test();
    (this->test)();
}
    以上三种写法都是对的吗?当然是对的。在类成员函数中都隐藏这一个this对象指针,也就是此时函数中的代码能够执行,也就是在对象指针的支持下实现的。前面我们说了,对象指针只有在对象创建之后才有的,我们在类里声明,类都没有声明完,对象又如何存在呢?那么这样我们还如何定义类,如何定义成员函数呢?然而有了this指针,我们也就依托于对象指针,成员函数里调用其他成员函数和变量也就都通过指针,也就有了合理的依据。而this指针在对象创建之后,就可以指向当前对象,然后实现各种成员变量和成员函数的访问了。
    所以上面三种test函数的调用是一样的,只是写法不同。第一种隐含了this指针,第三种只是将this和test的关系括起来看起来更加明显了。第二种也就是最常见的写法了。
    那么动态函数指针调用类成员函数呢?既然类成员函数是依托于对象的,那么动态函数指针调用类成员函数一样也要依托于对象。依托于对象,就需要借助this指针。而PFUN_CLASS()的写法,就和test()类似。然后编译器对于指针并不会自动附加this指针的,而是会对类成员函数(都是指的非静态成员函数)进行this附加。所以PFUN_CLASS()也就无法实现类对象的成员函数的作用,也就不合法。所以我们需要手动添加this指针来实现这一个过程。这样的意义就在于,我们告诉编译器,我这个指针是动态成员函数指针,存储的是成员函数,是可以在此直接运行的。只不过编译器默认是不支持的,毕竟指针变数太大,无法预先确定,所以还是手动添加this指定比较好。
    那么是这样写吗:
this->PFUN_CLASS();
    但是这样写的话,是不是和指针直接调用成员函数语法一样了,这样也就提示不存在成员函数PFUN_CLASS了。既然PFUN_CLASS是成员函数指针,那我们对指针取值一下,这样得到函数名,然后用this指针来操作,不就符合了普通的成员函数调用的规则了嘛。当然我们需要注意优先级,所以先将左边一部分括起来,就是这样的:
(this->*PFUN_CLASS)();
    这样的写法再也不会和之前的语法撞上了。对,就这么写了。看起来别扭,但是意义推理都说的通呀。
    如果是在类成员函数外调用动态函数指针,则需要使用对象:

struct C {  
   void func1(){}  
   void func2(){}  
};  
  
typedef void (C::*pFunc)();  
  
int main() {  
   C c;  
   pFunc funcArray[2] = {&C::func1, &C::func2};  
   (funcArray[0])();    // C2064   
   (c.*funcArray[0])(); // OK - function called in instance context  
}  
    所以,对于类内的非静态成员函数的函数指针的使用,在函数指针声明时要加类作用域、在赋值传参的时候要加取值地址符号,然后在函数调用的时候,需要显式的使用this指针和*取值操作符。这样也就万事大吉了。而对于静态函数指针(普通C函数指针和类静态函数指针),就简单的不用说了。

四、函数指针类型重定义typedef
void (A::*PFUN_CLASS)();
    这样的定义,就是定义一个指针变量,即函数指针变量。指针如何使用,我们已经说了。
typedef void (A::*PFUN_CLASS)();
    而这样的,在前面加一个typedef,就是定义了一个类型,即函数指针类型。我们可以用类型再去声明一个函数指针变量,而不是直接拿类型当做指针变量使用哦。也就是加了typedef而已,你只要知道,typedef是重定义类型的,那么后面的自然就是类型咯。这样是不是和函数指针变量的定义一下就区分开来了。