C++ 多态与虚函数、与构造函数和析构函数的联系

多态与虚函数

面向对象编程中,多态的含义是“一个接口,多种实现”。
多态分为静态多态和动态多态。静态多态是通过模板化和重载技术来实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。
C++中运行时的多态是指根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类方法称为覆盖或重写(override)。

虚函数:
用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
虚函数的作用就是实现动态绑定,也就是在程序的运行阶段动态地选择合适的成员函数。
具体的实现方式,在基类中定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型,以实现统一的接口,不同定义过程。
如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数,此时派生类也为抽象类,不能实例化对象。

虚函数表:
当一个类含有一个乃至多个虚函数的时候,将在全局数据区(静态区)中存储该类相应的虚函数表vtbl,编译器给类的每个对象添加一个相同隐藏的成员——虚函数表指针vptr,指向自身类的虚函数表。
虚函数表的大小在编译时确定,不必动态分配内存空间进行存储,因此不虚函数表不存在堆中,根据以上特征,虚函数表类似于类中静态成员变量。静态成员变量也是全局共享,大小确定,所以虚函数表和静态成员变量一样,存放在全局数据区。
虚函数表中保存了类对象进行声明的虚函数的地址,即虚函数表的元素是指向类成员函数的指针。也就是说我们可以通过vptr访问虚函数表,进而访问被声明的虚函数的的地址,从而调用相应的虚函数。
同一个类的不同对象的vptr实际上指向同一张虚函数表。vptr的设定和重置都由每一个类的构造函数,析构函数和拷贝赋值运算符自动完成。一般来说,将在构造函数中进行虚表的创建和虚表指针的初始化。
基类的虚函数表和派生类的虚函数表分别为保存在不同位置的两个独立数组,也就是说基类的隐藏成员和派生类的隐藏成员指向不同的地址。
如果派生类没有重新定义基类的某个虚函数A,则派生类的虚函数表vtbl将保存基类的虚函数A的原始地址(此时派生类和基类的虚函数表中保存的虚函数A的地址是一样的)。
如果派生类重写了基类的某个虚函数B,则派生类的虚函数表vtbl将保存新的虚函数B的地址(此时的虚函数B其实有两个版本,分别被基类和派生类的虚函数表分开保存)。

纯虚函数:
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”,即virtual void funtion1() = 0;
含有纯虚函数的类为抽象类,不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类的所有虚函数,否则派生类也是抽象类,不能实例化对象。
抽象类不能定义对象,但是可以作为指针或引用类型使用。

不能声明虚函数:
常见的不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。
普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有意义。
静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,也没有要动态绑定的必要性。
内联函数在编译时被展开,从而可减少函数调用花费的代价,虚函数是在运行时才进行动态绑定,从而使得继承对象能够准确的执行自己的动作。
构造函数目的是为了生成对象时进行对象初始化,虚函数目的是在不同类型的对象中调用不同的方法以产生不同的动作,当对象还没有生成时,虚函数是没有意义的,而构造函数是为了在对象还没有生成时实例化对象。
友元函数不支持继承,对于没有继承特性的函数就没有虚函数的说法。

早绑定和晚绑定:
c++编译器在编译的时候,要确定每个对象调用的函数(非虚函数)的地址,这称为早期绑定,当我们将Son类对象的地址赋给指针pFather时,C++编译器进行了类型转换,此时C++编译器认为指针变量pFather保存的就是Father对象的地址,当在main函数中执行pFather->Say(),调用的是Father对象的Say函数。
从内存角度看:

在这里插入图片描述

前面输出的结果是因为编译器在编译的时候,就已经确定了对象调用的函数地址,要解决这个问题就要使用晚绑定,当编译器使用晚绑定时候,就会在运行时再去确定对象的类型以及正确的调用函数,而要让编译器采用晚绑定,就要在基类中声明函数时使用virtual关键字.一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式地声明为virtual。

编译器为每个对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表,在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数。由于pFather实际指向的对象类型是Son,因此vptr指向的Son类的vtable,当调用pFather->Son()时,根据虚表中的函数地址找到的就是Son类的Say()函数。
从内存角度看:

在这里插入图片描述

构造函数与虚函数

构造函数不能为虚函数。
从C++之父Bjarne的回答我们应该知道C++为什么不支持构造函数是虚函数了,简单讲就是没有意义。
虚函数的作用在于通过子类的指针或引用来调用父类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过子类的指针或引用去调用。

构造函数目的是为了生成对象时进行对象初始化,虚函数目的是在不同类型的对象中调用不同的方法以产生不同的动作,当对象还没有生成时,内存中不存在虚函数指针和虚函数表,此时虚函数是没有意义的,而构造函数是为了在对象还没有生成时实例化对象,如果构造函数被声明为虚函数,内存空间还没有虚函数表,该构造函数将变得没有意义,所以构造函数不能是虚函数。

析构函数与虚函数

当派生类指针指向用new运算符生成的派生类对象时,delete派生类指针,将执行派生类的析构函数,再执行基类的析构函数。因为在实例化派生类对象时,先实例了基类对象。
当基类指针指向用new运算符生成的派生类对象时,delete基类指针,因为编译器又进行了类型转换,默认为基类指针指向基类对象的地址,根据早绑定中内存的关系,如果基类析构函数没有声明为虚函数,将只执行基类的构造函数,如果基类析构函数声明为虚函数,尽管进行类型转换,根据晚绑定中内存的关系,不管基类对象还是派生类对象都有相应的虚函数表指针,因此析构时会先调用派生类的析构函数(vptr指向自身的析构函数),再调用基类的析构函数(声明派生类对象先实例了基类对象)。

#include<iostream>
using namespace std;

class Base {
public:
    Base()
    { 
        cout<<"Base::Base()"<<endl;
        fun();
    }
    virtual ~Base()
    {
        cout<<"Base::~Base()"<<endl;
        fun();
    }
    virtual void fun()
    {
        cout<<"Base::fun() virtual"<<endl;
    }

};

class Derived:public Base
{
public:
    Derived()
    {
        cout<<"Derived::Derived()"<<endl;
        fun();;
    }
    ~Derived()
    {
        cout<<"Derived::~Derived()"<<endl;
        fun();
    }
    virtual void fun()
    {
        cout<<"Derived::fun() virtual"<<endl;
    }
};

int main()
{
	//basa
    Base *b = new Base();
    delete b;
    cout<<endl;
    /*
    Base::Base()
    Base::fun() virtual
    Base::~Base()
    Base::fun() virtual
    */ 

    
	//Derived
    Derived *d = new Derived();
    delete d;
    cout<<endl;
    /*
    Base::Base()
    Base::fun() virtual //派生类还不存在
    Derived::Derived()
    Derived::fun() virtual //派生类已存在
    Derived::~Derived()
    Derived::fun() virtual //派生类还存在
    Base::~Base()
    Base::fun() virtual //派生类不存在
    */


	//Base* Derived,父类指针指向子类对象的实现原理
    Base *bd = new Derived();
    delete bd;
    cout<<endl;
    /*
    Base::Base()
    Base::fun() virtual //派生类不存在
    Derived::Derived()
    Derived::fun() virtual //派生类已存在
    //当基类析构函数没用声明为虚函数时,将不调用派生类的析构函数
    Derived::~Derived()
    Derived::fun() virtual //派生类还存在
    Base::~Base()
    Base::fun() virtual //派生类不存在
    */

    system("pause");
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章