• c++继承


    目录

    为什么要使用继承

    继承的概念

    派生类定义方法

    派生类访问权限控制

    继承中的析构和构造

    继承中的对象模型

    对象构造和析构的调用原则

    子类和父类同名成员的处理方法

    非自动继承的函数

    静态成员在继承中的特点

    多继承

    多继承概念

    菱形继承和虚继承

    虚继承实现原理


    为什么要使用继承

    一个类继承另一个类,这样类中可以少定义一些成员

    如果直接定于职工类 代码重复比较严重

    继承的概念

    c++ 最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类 的成员,还拥有新定义的成员。
    一个 B 类继承于 A 类,或称从类 A 派生类 B 。这样的话,类 A 成为基类(父类),类 B 成为派生类(子类)。 派生类中的成员,包含两大部分: 一类是从基类继承过来的,一类是自己增加的成员。 从基类继承过过来的表现其共性,而新增的成员体现了其个性

    派生类定义方法

    派生类定义格式:
    Class 派生类名 : 继承方式 基类名 {
            //派生类新增的数据成员和成员函数
    }
    三种继承方式:
    public : 公有继承
    private : 私有继承
    protected : 保护继承
    从继承源上分:
    单继承:指每个派生类只直接继承了一个基类的特征
    多继承:指多个基类派生出一个派生类的继承关系 , 多继承的派生类直接继承了不止一个基类的特征
    1. #include
    2. #include
    3. using namespace std;
    4. class Animal
    5. {
    6. public:
    7. int age;
    8. void printf()
    9. {
    10. cout << age << endl;
    11. }
    12. };
    13. class Dog : public Animal
    14. {
    15. public:
    16. int tail_len;
    17. /*相当于拷贝代码
    18. int age;
    19. void printf()
    20. {
    21. cout << << endl;
    22. }
    23. */
    24. };
    25. void test01()
    26. {
    27. Dog d;
    28. d.age = 10;
    29. d.printf();
    30. }

    派生类访问权限控制

    派生类继承基类,派生类拥有基类中全部成员变量和成员方法(除了构造和析构之外的成员方法),但是在派生 类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。 派生类的访问权限规则如下:
    1. ​​//基类
    2. class A{
    3. public:
    4. int mA;
    5. protected:
    6. int mB;
    7. private:
    8. int mC;
    9. };
    10. //1. 公有(public)继承
    11. class B : public A{
    12. public:
    13. void PrintB(){
    14. cout << mA << endl; //可访问基类 public 属性
    15. cout << mB << endl; //可访问基类 protected 属性
    16. //cout << mC << endl; //不可访问基类 private 属性
    17. }
    18. };
    19. class SubB : public B{
    20. void PrintSubB(){
    21. cout << mA << endl; //可访问基类 public 属性
    22. cout << mB << endl; //可访问基类 protected 属性
    23. //cout << mC << endl; //不可访问基类 private 属性
    24. }
    25. };
    26. void test01(){
    27. B b;
    28. cout << b.mA << endl; //可访问基类 public 属性
    29. //cout << b.mB << endl; //不可访问基类 protected 属性
    30. //cout << b.mC << endl; //不可访问基类 private 属性
    31. }
    32. //2. 私有(private)继承
    33. class C : private A{
    34. public:
    35. void PrintC(){
    36. cout << mA << endl; //可访问基类 public 属性
    37. cout << mB << endl; //可访问基类 protected 属性
    38. //cout << mC << endl; //不可访问基类 private 属性
    39. }
    40. };
    41. class SubC : public C{
    42. void PrintSubC(){
    43. //cout << mA << endl; //不可访问基类 public 属性
    44. //cout << mB << endl; //不可访问基类 protected 属性
    45. //cout << mC << endl; //不可访问基类 private 属性
    46. }
    47. };
    48. void test02(){
    49. C c;
    50. //cout << c.mA << endl; //不可访问基类 public 属性
    51. //cout << c.mB << endl; //不可访问基类 protected 属性
    52. //cout << c.mC << endl; //不可访问基类 private 属性
    53. }
    54. //3. 保护(protected)继承
    55. class D : protected A{
    56. public:
    57. void PrintD(){
    58. cout << mA << endl; //可访问基类 public 属性
    59. cout << mB << endl; //可访问基类 protected 属性
    60. //cout << mC << endl; //不可访问基类 private 属性
    61. }
    62. };
    63. class SubD : public D{
    64. void PrintD(){
    65. cout << mA << endl; //可访问基类 public 属性
    66. cout << mB << endl; //可访问基类 protected 属性
    67. //cout << mC << endl; //不可访问基类 private 属性
    68. }
    69. };
    70. void test03(){
    71. D d;
    72. //cout << d.mA << endl; //不可访问基类 public 属性
    73. //cout << d.mB << endl; //不可访问基类 protected 属性
    74. //cout << d.mC << endl; //不可访问基类 private 属性
    75. }

    继承中的析构和构造

    继承中的对象模型

    C++ 编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成
    1. #include
    2. using namespace std;
    3. class Aclass
    4. {
    5. public:
    6. int mA;
    7. int mB;
    8. };
    9. class Bclass : public Aclass
    10. {
    11. public:
    12. int mC;
    13. };
    14. class Cclass : public Bclass
    15. {
    16. public:
    17. int mD;
    18. };
    19. void test()
    20. {
    21. cout << "A size:" << sizeof(Aclass) << endl;
    22. cout << "B size:" << sizeof(Bclass) << endl;
    23. cout << "C size:" << sizeof(Cclass) << endl;
    24. }
    25. int main()
    26. {
    27. test();
    28. return 0;
    29. }

    编译运行

    对象构造和析构的调用原则

    继承中的构造和析构
    子类对象在创建时会首先调用父类的构造函数
    父类构造函数执行完毕后,才会调用子类的构造函数
    当父类构造函数有参数时,需要在子类初始化列表 ( 参数列表 ) 中显示调用父类构造函数
    析构函数调用顺序和构造函数相反
    1. #include
    2. using namespace std;
    3. class Base
    4. {
    5. public:
    6. Base(int age,string name)
    7. {
    8. this->age = age;
    9. this->name = name;
    10. cout << "Base构造函数" << endl;
    11. }
    12. ~Base()
    13. {
    14. cout << "Base析构函数" << endl;
    15. }
    16. int age;
    17. string name;
    18. };
    19. //创建子类对象时,必须先构造父类 需要调用父类的构造函数
    20. class Son:public Base
    21. {
    22. public:
    23. Son(int id,int age,string name):Base(age,name)
    24. {
    25. this->id = id;
    26. cout << "Son构造函数" << endl;
    27. }
    28. ~Son()
    29. {
    30. cout << "Son析构函数" << endl;
    31. }
    32. int id;
    33. };
    34. void test()
    35. {
    36. Son p(10,8,"lucy");
    37. }
    38. int main()
    39. {
    40. test();
    41. return 0;
    42. }

    编译运行

    建的时候先建在里边的父类 再建外边的子类 拆的时候先拆外边的子类 再拆里边的父类 很好理解

    子类和父类同名成员的处理方法

    如果子类和父类由同名的成员变量,父类的变量会被隐藏,访问的是子类变量

    如果子类和父类由同名的成员函数,父类的函数会被隐藏,访问的是子类函数

    1. #include
    2. using namespace std;
    3. class Base
    4. {
    5. public:
    6. Base(int a)
    7. {
    8. }
    9. int a;
    10. };
    11. class Son:public Base
    12. {
    13. public:
    14. Son(int a1,int a2):Base(a1),a(a2)
    15. {
    16. }
    17. int a;
    18. };
    19. void test()
    20. {
    21. Son p(10,20);
    22. cout << p.a << endl;//输出20
    23. }
    24. int main()
    25. {
    26. test();
    27. return 0;
    28. }

    非自动继承的函数

    不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说 构造函数和析构函数 不能被继承,必须为每一个特定的派生类分别创建。
    另外 operator= 也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由 = 右边的对象如何初始化= 左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。
    在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。

    静态成员在继承中的特点

    如果子类和父类有同名的静态成员变量,父类中的静态成员变量会被隐藏
    如果子类和父类有同名的静态成员函数,父类中的静态成员函数都会被隐藏
    class Base{
        public:
            static int getNum()
            { 
                return sNum; 
            }
            static int getNum(int param)
            {
                return sNum + param;
            }
        public:
            static int sNum;
    };
    int Base::sNum = 10;
    class Derived : public Base
    {
        public:
            static int sNum;
    //基类静态成员属性将被隐藏
    #if 0
           
    //重定义一个函数,基类中重载的函数被隐藏
            static int getNum(int param1, int param2)
            {
                return sNum + param1 + param2;
            }
    #else
           
    //改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数
            static void getNum(int param1, int param2)
            {
                cout << sNum + param1 + param2 << endl;
            }
    #endif
    };
    int Derived::sNum = 20

    多继承

    多继承概念

    一个类继承了多个类

    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. int a;
    7. };
    8. class B
    9. {
    10. public:
    11. int a;
    12. };
    13. class C:public A,public B
    14. {
    15. public:
    16. int a;
    17. };
    18. void test()
    19. {
    20. C c;
    21. c.a = 10;
    22. c.A::a= 20;
    23. c.B::a = 30;
    24. }
    25. int main()
    26. {
    27. test();
    28. return 0;
    29. }

    菱形继承和虚继承

    两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。
    这种继承所带来的问题:
    1. 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。
    2. 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
    上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解决,那么重复继承怎么解决?
    对于这种菱形继承所带来的两个问题,c++为我们提供了一种方式,采用虚基类(virtual )。

    1. #include
    2. using namespace std;
    3. class BigBase{
    4. public:
    5. BigBase(){ mParam = 0; }
    6. void func(){ cout << "BigBase::func" << endl; }
    7. public:
    8. int mParam;
    9. };
    10. class Base1 : public BigBase{};
    11. class Base2 : public BigBase{};
    12. class Derived : public Base1, public Base2{};
    13. int main(){
    14. Derived derived;
    15. //1. 对“func”的访问不明确
    16. //derived.func();
    17. //cout << derived.mParam << endl;
    18. cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;
    19. cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;
    20. //2. 重复继承
    21. cout << "Derived size:" << sizeof(Derived) << endl; //8
    22. return 0;
    23. }
    上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解决,那么重复继承怎么解决?
    对于这种菱形继承所带来的两个问题, c++ 为我们提供了一种方式,采用虚基类。那么我们采用虚基类方式将代码 修改如下:
    1. #include
    2. using namespace std;
    3. class BigBase{
    4. public:
    5. BigBase(){ mParam = 0; }
    6. void func(){ cout << "BigBase::func" << endl; }
    7. public:
    8. int mParam;
    9. };
    10. class Base1 : virtual public BigBase{};
    11. class Base2 : virtual public BigBase{};
    12. class Derived : public Base1, public Base2{};
    13. int main(){
    14. Derived derived;
    15. //二义性问题解决
    16. derived.func();
    17. cout << derived.mParam << endl;
    18. //输出结果:12
    19. cout << "Derived size:" << sizeof(Derived) << endl;
    20. return 0;
    21. }

    虚继承实现原理

    以上程序 Base1 Base2 采用虚继承方式继承 BigBase, 那么 BigBase 被称为虚基类。
    通过虚继承解决了菱形继承所带来的二义性问题。
    但是虚基类是如何解决二义性的呢?并且 derived 大小为 12 字节,这是怎么回事?
    通过内存图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜测到编译器肯定对我们编写的程序做了一些手脚。
    BigBase 菱形最顶层的类,内存布局图没有发生改变。Base1和 Base2 通过虚继承的方式派生自 BigBase, 这两个对象的布局图中可以看出编译器为我们的对象中增加了一 个vbptr (virtual base pointer),vbptr 指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。Derived 派生于 Base1 Base2, 继承了两个基类的 vbptr 指针,并调整了 vbptr 与虚基类的首地址的偏移量。
    由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承一份数据,并且也解决了二义 性的问题。现在模型就变成了 Base1 Base2 Derived 三个类对象共享了一份 BigBase 数据。
    当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个 虚基类的子对象(这和多继承是完全不同的)。即使共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化 呢?C++ 标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),
    但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用
    class BigBase{
        public:
            BigBase(int x){mParam = x;}
            void func(){cout << "BigBase::func" << endl;}
        public:
            int mParam;
    };
    class Base1 : virtual public BigBase{
        public:
            Base1() :BigBase(10){}
    //不调用 BigBase 构造
    };
    class Base2 : virtual public BigBase{
        public:
            Base2() :BigBase(10){}
    //不调用 BigBase 构造
    };
    class Derived : public Base1, public Base2{
        public:
            Derived() :BigBase(10){}
    //调用 BigBase 构造
    };
    //每一次继承子类中都必须书写初始化语句
    int main(){
        Derived derived;
        return 0;
    }
    注意:
    虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的 .
    工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继 承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。
  • 相关阅读:
    Dockerfile 构建命令 及 dockerfile 设置默认时区
    [SpringMVC笔记] SpringMVC-14-SSM整合-异常处理器
    explainable machine learning(机器学习的可解释性)
    uni-app H5使用 tabbars切换,echartst图表变小 宽度只有100px问题解决
    C语言内存函数
    中国成人脑白质分区与脑功能图谱
    每天五分钟机器学习:如何解决欠拟合问题
    Python图像处理丨图像的灰度线性变换
    RestCloud ETL实践之无标识位实现增量数据同步
    十年老程序员的职场教训,很受用
  • 原文地址:https://blog.csdn.net/2301_77164542/article/details/132897073