一点一滴成长

导航

虚函数的作用及应用

 1、虚函数的作用

  ①、子类重写父类的虚函数后,在子类、父类中调用的虚函数都是子类的虚函数。(有一个特殊情况就是在父类的构造函数中调用的虚函数是父类中的虚函数,因为此时子类还没有构造完成。)

  ②、子类重写父类的虚函数后,父类指针指向父类对象的话,通过这个父类指针调用的是父类中的虚函数;子类指针指向子类对象的话,通过这个子类指针调用的是子类的虚函数;父类指针指向子类对象的话,通过这个父类指针调用的是子类的虚函数。

  ③、父类指针可以指向父类对象和子类对象,当指向子类对象的时候我们一般是想实现对于一些虚函数的自定义。

  ④、子类指针可以指向子类对象,但子类指针不可以指向父类对象,除非使用dynamic_cast 或 static_cast进行强制转换,但我们一般不会这样做,因为是很危险的。

  应用1: 比如在MFC中,我们想要实现一个自定义的按钮(改变按钮背景、文本颜色),这时候我们就可以生成一个继承自CButton类的子类CMyButton来使用,在子类中重写父类的虚函数DrawItem,因为DrawItem是父类CButton的绘制虚函数。当我们在子类中重写父类的DrawItem虚函数后,父类中调用的就是子类的函数,这样就实现了我们对于CButton的订制,即CButton的自绘。

           再比如MFC的对话框类中,右击属性可以看到重写下面有很多函数,比如OnInitDialog、OnCancel、OnOK等,这些函数都是父类的虚函数,当特定事件发生时会被自动调用,我们可以对这些函数进行重写,然后父类中到时候调用的就是我们在子类中自定义的函数,从而实现对于特定事件的自定义。

  应用2:比如有两个类CServer和CTcpServer,CServer中包含了CTcpServer类型的成员变量,而CTcpServer中也要包含CServer对象指针以便回调CServer中的成员函数。为了避免头文件的重复包含,我们在CTcpServer中只保存CServer的父类CListener指针,在CListener中将回调函数声明为虚函数,在CServer中重写这个虚函数。这样在CTcpServer中通过CListener指针调用的虚函数就是CServer中重写的虚函数。

//Listener.h
class CListener
{
public:
    virtual void VirtualFun1() {}
    virtual void VirtualFun2() {}
};


//Server.h
#include "Listener.h"
#include "TcpServer.h"
using namespace std;

class CServer : public CListener
{
public:
    CServer():m_TcpSvr(this){}
    ~CServer();

    virtual void VirtualFun1()
    {
        cout << "CServer::VirtualFun1() called" << endl;
    }
    virtual void VirtualFun2()
    {
        cout << "CServer::VirtualFun2() called" << endl;
    }
private:
    CTcpServer m_TcpSvr;
};


//TcpServer.h
#include "Listener.h"

class CTcpServer
{
public:
    CTcpServer(CListener* pSvr):m_pSvr(pSvr){}
    ~CTcpServer();
private:
    CListener* m_pSvr;
};
View Code

  上面代码中的CServer的父类CListener可以不单独在一个文件中声明,而是在CTcpServer类中声明,这样更加方便:

//Server.h
#include "TcpServer.h"
using namespace std;

class CServer : public CTcpServer::CListener
{
public:
    CServer():m_TcpSvr(this){}
    ~CServer();

    virtual void VirtualFun1()
    {
        cout << "CServer::VirtualFun1() called" << endl;
    }
    virtual void VirtualFun2()
    {
        cout << "CServer::VirtualFun2() called" << endl;
    }
private:
    CTcpServer m_TcpSvr;
};


//TcpServer.h
class CTcpServer
{
public:
    class CListener
    {
    public:
        virtual void VirtualFun1() {}
        virtual void VirtualFun2() {}
    };
public:
    CTcpServer(CListener* pSvr):m_pSvr(pSvr){}
    ~CTcpServer();
private:
    CListener* m_pSvr;
};
View Code

   界面编程中也经常用到这种方法,比如一个主窗口包含一个或多个子窗口(子控件),在子窗口(子控件)类中需要回调主窗口的一些函数。这种情况就可以使用上面所说的方法来实现。

   当然上面所说的子控件一般都是主窗口的成员对象,如果子控件只是一个临时展示的对象即函数栈上的一个对象的话也可以在子控件中通过dynamic_cast<>将主窗口的父类指针转换成子类指针来使用,这样只需要在源文件中包含主窗口的头文件,避免了头文件重复包含。

   不过因为C++11中有了function可以很方便的进行两个对象间的通信,所以推荐使用function来实现应用2中使用虚函数来进行两个对象的通信。

2、虚函数表

  虚函数是C++多态性的表现,具体为子类重写(或者叫覆盖override)父类的虚函数,可以实现通过父类指针调用子类的虚函数。函数的重载不是多态性的表现,因为这些函数拥有不同的参数类型或参数个数。

  虚函数是通过虚函数表来实现的,虚函数表实际上是一个函数指针数组,它保存了本类中的虚函数的地址。虚函数表属于类中而不属于类的某个实例,所以不会为每个实例专门生成一个虚函数表,但每个类的实例中保存指向了这个虚函数表的指针(所以包含虚函数的对象的大小会增加一个指针的大小),而且这个指针保存在对象实例空间的最前面。

  比如对于一个Base基类(b为其实例),其包含三个虚函数f、g和h,虚函数表就是这样的:

  而Base的派生类Derive(d为其实例),也包含三个虚函数f1、g1和h1,它的虚函数表是这样的:

  而如果派生类重写了基类的f函数的话就造成了在派生类的虚函数表中,派生类的虚函数覆盖了基类的虚函数,在实际调用的时候就会调用Derive::f():

 

  转载及参考出处:log.csdn.net/sanfengshou/article/details/4574604

posted on 2017-02-28 22:22  整鬼专家  阅读(8985)  评论(0编辑  收藏  举报