C++的继承与多态

◆ 概念介绍ios

继承:为了代码的重用,保留基类的本来结构,并新增派生类的部分,同时可能覆盖(overide)基类的某些成员。ide

多态:一种将不一样的特殊行为和单个泛化记号相关联的能力,分为静态多态和动态多态。函数

◆ 继承:spa

一个派生类能够经过继承得到基类的全部成员,而无需再次定义它们。分为publicprotectedprivate三种继承方式,前两种方式保持基类的全部成员的属性不变,且派生类能够访问基类的publicprotected成员,但仍然不能访问基类的private成员;private继承将使得基类的全部成员在派生类中表现为private属性。3d

声明一个派生类对象,即在构造派生类对象时,遵循基类的接口,构造基类子对象,构造派生类增长的部分。其中的组成由下图所示:指针

当出现菱形继承时,例以下图所示:code

要构造一个SleepSofa对象,就要构造一个Sofa和一个Bed子对象,这其中又同时构造了两次Furniture对象,这是不合理的。所以Bed和Sofa类要对Furniture类进行虚继承(virtual public Furniture)来避免这种情况。orm

 

◆ 多态:对象

静态多态:在编译时期就已经肯定了的行为,例如带变量的宏,模板,函数重载,运算符重载,拷贝构造等。blog

动态多态:在运行时期才能肯定调用的行为。例如虚函数调用机制。本部分主要讨论的是动态多态。虚函数是实现动态多态的机制,其核心理念就是经过基类指针来访问派生类定义的成员。成员函数在基类为虚函数时,在派生类一样也是虚函数。纯虚函数是指不但愿基类对象调用的成员函数,须要派生类覆盖实现这样的纯虚函数。(注:若是某个成员函数在基类中没有用virtual关键字修饰,即普通函数,而在派生类中却又有彻底相同的成员函数声明,两个函数即便有相同的名字和相同的参数类型与数量,这两个函数也是彻底不一样的函数,由于类的做用域不一样)

虚函数表(vtable):每一个类都拥有一个虚函数表,虚函数表中罗列了该类中全部虚函数的地址,排列顺序按声明顺序排列,例如这样两个类

class Base
{
    virtual void f() {}
    virtual void g() {}
    //其余成员
};
Base b;

class Derive : public Base
{
    void f() {}
    virtual void d() {}
    //其余成员
};
Derive d;

 

虚表指针(vptr):每一个类有一个虚表指针,当利用一个基类的指针绑定基类或者派生类对象时,程序运行时调用某个虚函数成员,会根据对象的类型去初始化虚指针,从而虚表指针会从正确的虚函数表中寻找对应的函数进行动态绑定,所以能够达到从基类指针调用派生类成员的效果。

那么为何须要虚指针和虚函数表来实现动态多态呢?由于不管是什么函数,包括类内的虚函数和非虚函数,都会储存在内存中的代码段。可是当编译器在编译时,就能够肯定普通函数和非虚函数的入口地址,以及其调用的信息,因此这指的是常量指针。当遇到动态多态时,虚函数真正的入口地址的指针要在运行时根据对象的类型才能肯定,因此要经过虚指针从虚函数表中找虚函数对应的入口地址。

固然,用基类指针绑定的子类对象,只能经过这个基类指针调用基类中的成员,由于做用域仅限于基类的子对象,子类新增的部分是看不见的。

总结为下面这个例程:

#include <iostream>

using std::cout;
using std::endl;

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

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


int main()
{
    Base* bp = new Base();
    Base* dp = new Derive();
    
    bp->fun();
    bp->vfun();
    
    dp->fun();
    dp->vfun();
    //dp->dfun(); //编译错误:基类指针指向子类中基类的子对象
                  //不能看到子类的成员 
    
    delete bp;
    delete dp;
    
    
    return 0;
}

输出为:

能够看出,bp绑定一个基类对象,调用本身的成员无异议;dp绑定的是一个子类对象,所以调用fun()时,因为dp是一个基类指针,做用域在于基类中,因此调用的是基类的fun(),而调用vfun()是经过动态绑定调用虚函数表中被子类覆盖的Derive::vfun(),而若是要调用dfun()时则会出现编译错误,由于子类独有成员基类指针不可见。

注:在解有关动态多态的题时,只要把握住一点:这个指针指向的究竟是基类对象仍是子类对象,若是是基类对象,则调用基类的成员函数,若是是子类对象,则要考虑到这个虚成员函数是否被子类中的成员覆盖掉,便是否产生了动态绑定。另外还有一点,从子类对象强制类型转换为基类对象是容许的,而相反地要从基类对象强制转换成子类对象是错误的(编译不经过)。

Base* dp1 = new Derive(); 
Derive* dp2 = (Derive*) dp1; //基类指针指向的是子类对象,能够强制转化为子类指针

Base* bp1 = new Base();
Derive* bp2 = (Base*) bp1; //错误,[Error] invalid conversion from 'Base*' to 'Derive*' [-fpermissive]                           //基类指针指向的是基类对象,不能强制转化为子类指针
相关文章
相关标签/搜索