12月3日笔记C++8_继承,虚继承

9 篇文章 0 订阅

面向对象的基本特征:
    封装
    继承
    多态

    在实现世界中,任何一个概念都不是孤立存在的,都有与之相关的概念,这些概念之间会存在各种关系
    面向对象模拟现实世界,我们用类来表示概念,所以,概念之间的关系,就转换成了类与类之间的关系。

    例:
        计算机
            CPU、显卡、内存、硬盘、键盘、鼠标、显示屏幕、主板...
            笔记本、台式机、平板、手机...

        学生
            学号、姓名、年龄、性别、出生年月...
            小学生、中学生、大学生、研究生...


        汽车
            轮胎、发动机、变速箱、车灯、车门...
            小汽车、公交车、货车...

    类与类之间主要存在两种关系
        组合:has-a, 例如计算机有内存、硬盘等
        继承:is-a  ,例如笔记本是计算机、大学生是学生等     


    所谓继承,就是在已有类型的基础上,创建新的类型。
    继承可以实现 代码复用减少代码冗余提高开发速度
    继承主要用来描述那些非常相似,只有细微差异的类之间的关系。


    通过继承联系在一起的类构成一种层次关系,处于层次关系根部的类,称为基础类(Base Class), 简称基类,也称为父类
    层次关系中其它的类 或直接 或间接从基类派生而来,这些类称为派生类(Derived Class),也称为子类.

    基类负责定义在层次关系中所有类 共同的 属性和行为。
    派生类自动获取基类的 特性,并负责定义各派生类自己特性。

    派生类的定义:
    class 派生类名: 继承方式 基类1, 继承方式 基类2, ...
    {
        // 派生类新增的成员
    };

    根据基类的数量,把继承又分成两种:
    单继承:    只有一个基类
    多重继承:  两个或两个以上的基类

    继承方式:决定了基类的成员在派生类内部 和 派生类外部的访问权限。
    public            公有继承
    private           私有继承
    protected       保护继承

    class的默认继承方式为private继承,struct的默认继承方式为public继承。
    C++中class与Struct的区别?

    公有继承:

        基类的公有成员,通过公有继承,派生类可直接访问
        基类的私有成员,通过公有继承,派生类不可直接访问
        基类的保护成员,通过公有继承,派生类内部可直接访问,派生类外部不能直接访问

    私有继承:

        基类的公有成员,通过私有继承,派生类内部可直接访问,派生类外部不能直接访问
        基类的私有成员,通过私有继承,派生类不可直接访问
        基类的保护成员,通过私有继承,派生类内部可直接访问,派生类外部不能直接访问

    保护继承:

        基类的公有成员,通过保护继承,派生类内部可直接访问,派生类外部不能直接访问
        基类的私有成员,通过保护继承,派生类不可直接访问
        基类的保护成员,通过保护继承,派生类内部可直接访问,派生类外部不能直接访问 

[父的私有,子皆不可;公有继承公有部分,子随意;其余皆是子有内部访问,子外部不可问]


派生类与基类的关系
    通过测试,可以发现,派生类由两部分组成:
        从基类继承来的成员
        派生类新增的成员

    因为在派生类对象中包含有基类对应的功能,所以,可以把 公有派生类 当成 基类 用。
    具体:
    1、可以用 派生对象 初始化 基类对象
        Derived d; // 派生类对象
        Base b = d;// 实例化基类对象

    2、可以用 派生类对象 给 基类对象 赋值
        b = d;
    3、基类指针 可以指向 派生类对象
        Base* p = &d;
    4、基类引用 可以 绑定 到 派生类对象
        Base& r = d;

    说明:
        虽然 公有派生类 可以当成 基类 用,但是用 派生类 初始化的 基类对象/指针/引用, 都无法访问 派生类自己 新增加的成员,只能访问派生类从基类继承的来的成员。


继承中 构造函数 与 析构函数
    一般 构造函数 负责 初始化本类的 成员变量
    所以,当初始化一个派生类对象时,得先调用基类的构造函数,以初始化 从基类继承来的成员,再调用成员对象的构造函数
       最后再执行派生类自己的构造函数。

    如果 基类有默认构造函数,则在 初始化 派生类对象时,可以自动调用基类的默认构造函数
    如果基类没有默认构造函数或需要调用基类其它的构造函数,
        则只能在 派生类构造函数的初始化列表中 显式的调用基类的构造函数

    例: 

        class Base 
        {
        public:
            Base(形参);

        } ;
        class Derived: public Base 
        {
        public:
            Derived(形参): Base(形参) {}
        } ;  

    当派生类对象 销毁时,会自动 先调用 派生类的析构函数,然后调用成员对象的析构函数,最后调用 基类的析构函数。
    如果对象的作用域相同,则 
        先构造的,后析构
        后构造的,先析构
    练习:
        如果派生类新增的成员是另一个类类型的对象,那么 构造函数 与 析构函数 的调用顺序又是怎样的?
        例: 

        class A {};
        class B {};

        class C : public B 
        {
            A a;
        };
        C c;

//顺序是B A C  先是父,再成员,最后是本身的构造函数。

继承中的同名成员:
    派生类可以在基类的基础上新增成员,那么这些新增加的成员可能会与基类的某些成员同名。

    例: 

    class Base 
    {
    public:
        void print()
        {
            cout << x << endl;
        }
    private:
        int x;
    };

    class Derived: public Base
    {
    public:
        void print()
        {
            print(); // 调用的哪个print()?  会调用派生类的print函数,形成递归
            cout << x << endl; // 访问的又是哪个x?  本身的
            cout << y << endl;
        }
    private:
        int x;
        int y;
    };

    当在 派生类内部 或使用 派生类对象,去访问那些同名成员时,默认情况下访问的是派生类自己新增加的成员
    也就是说,基类的那些同名成员被隐藏了。
    如果需要使用从基类继承来的那些同名成员,指定作用域即可:
    void print()
    {
        Base::print(); // 调用基类的那个同名函数
    }
多重继承:
    所谓多重继承,指一个派生类拥有多个基类的情况,多重继承下的派生类会继承它所有基类的属性和行为。
    例: 

    class Base1
    {
    public:
        int getX();
    private:
        int x;
    };

    class Base2 
    {
    public:
        int getY();
    private:
        int y;
    };

    class Derived: public Base1, public Base2
    {
    };
    Derived d; // 对象d有两个成员变量,有两个成员函数


多重继承中 同名成员
    多重继承的情况下,多个基类中的成员可能同名,基类中的成员可能与派生类中的成员同名
    当用派生类对象去访问某个成员时,首先从派生类的作用域查找该成员,如果找到则使用
    如果没找到,则继承从它的所有的基类中同时查找
        如果没找到,则继续沿着继承层次结构往上查找,找完整个继承层次都没找到 则报错
        如果在同一层次的多个基类中找到了多个同名成员,则产生二义性问题  

    多重继承中的二义性问题,解决方式与单继承的同名成员情况类似,也只需要指定 作用域 即可:
    例:
        d.Base1::getX()  


    如果底层派生类的两个直接基类又继承自同一个基类,则称这种情况为"菱形继承或钻石继承" :
    例: 
    class A {}
    class B: public A {}
    class C: public A {}
    class D: public B, public C {}

    上述继承方式,存在两个问题:
        1、A中的成员,通过继承关系,到达派生类D的时候,会出现多份。
        2、初始化派生类D的对象时,会调用基类的构造函数,A中的成员可能会被初始化多次。

    解决方法:虚继承
        所谓的虚继承,指在直接基类B和C继承A的时候,在继承方式前面加一个关键字virtual
        被继承的A称为虚基类
        虚继承使得B和C共享虚基类A的成员。
    例: 
    class A {} // 虚基类 
    class B: virtual public A {} // 虚继承 
    class C: virtual public A {} // 虚继承
    class D: public B, public C {}

    虚基类A的成员存放在派生类D对象的存储空间的最后面
    同时,要保证B类和C类能正常访问被共享的A的成员
    所以,得记录被共享A的成员距离B和C的偏移量,大多数编译器这些偏移量存放在对象之外,一般把保存这些偏移量的那个数据结构称为 虚基类表(vtable)
    也就是偏移量是存储在其它地方的,那么B和C得保存这些偏移量的地址,所以,虚继承的派生类B和C中都会多出一个虚指针,保存偏移量的存储位置。
    结论:
        当计算底层派生类对象大小时,需要加上虚指针,如果对象成员的大小不一致,还得考虑字节对齐。
        在初始化底层派生类D的对象时,需要在初始化列表中调用所有直接基类的构造函数 和 虚基类的构造函数

        



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值