zoukankan      html  css  js  c++  java
  • C++的继承与多态

    ◆ 概念介绍

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

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

    ◆ 继承:

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

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

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

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

    ◆ 多态:

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

    动态多态:在运行时期才能确定调用的行为。例如虚函数调用机制。本部分主要讨论的是动态多态。虚函数是实现动态多态的机制,其核心理念就是通过基类指针来访问派生类定义的成员。成员函数在基类为虚函数时,在派生类同样也是虚函数。纯虚函数是指不希望基类对象调用的成员函数,需要派生类覆盖实现这样的纯虚函数。(注:如果某个成员函数在基类中没有用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]
    //基类指针指向的是基类对象,不能强制转化为子类指针
  • 相关阅读:
    Ex 6_20 最优二叉搜索树..._第六次作业
    Ex 6_12 凸多边形的最优三角剖分..._第六次作业
    Ex 6_9 某个字符串处理语言提供了一个将字符串一分为二的基本操作..._第六次作业
    Ex 6_4 判断序列是否由合法单词组成..._第六次作业
    maven配置阿里云镜像时(私服设置~JEECG)
    node、npm、webpack、vue-cli傻傻分不清?
    设计模式~观察者模式和发布订阅模式的比较:
    前端~定位属性position(relative、absolute、fixed)的分析
    debounce防抖函数减少函数调用的逻辑分析(包裹上时间的外衣,在时间还没来时,kill)
    js原生滚动与使用插件better-scroll不起作用原因
  • 原文地址:https://www.cnblogs.com/ChenZhongzhou/p/5682776.html
Copyright © 2011-2022 走看看