• 黑马程序员C++类和对象【6】—— 继承(一文万字带你搞懂C++继承 —— 你还不知道怎么学C++继承吗?不知道虚继承底层原理吗?)


    目录

    😎继承的基本语法

    普通实现:

    继承实现:

     总结:

    派生类中的成员,包含两大部分:

    😎继承方式 

    继承方式一共有三种:

    示例:

     😎继承中的对象模型

    通过工具验证:

    结论:

     😎继承中构造和析构顺序

    问题:

    总结:

    😎继承同名成员处理方式

    问题:

    总结:

    😎继承同名静态成员处理方式

    问题:

    示例:

    总结: 

    😎多继承语法

    语法:

    示例:

     总结:

    😎菱形继承(C++特色) 

    菱形继承概念:

    典型的菱形继承案例(草泥马):

    菱形继承问题:

    菱形继承的问题二: 

    总结:

    虚继承解决菱形继承问题的底层原理图解:

     结语:

            博客制作不易,画图有点费眼,要是觉得博客对您有帮助,请一键三连嗷!

                                                                                                          —— By CSDN码雨


     

    继承是面向对象三大特性之一

    有些类与类之间存在特殊的关系,例如下图中:

     我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。

    这个时候我们就可以考虑利用继承的技术,减少重复代码。


    😎继承的基本语法

    例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同

    接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处

    普通实现:

    1. //Java页面
    2. class Java
    3. {
    4. public:
    5. void header()
    6. {
    7. cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    8. }
    9. void footer()
    10. {
    11. cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    12. }
    13. void left()
    14. {
    15. cout << "Java,Python,C++...(公共分类列表)" << endl;
    16. }
    17. void content()
    18. {
    19. cout << "JAVA学科视频" << endl;
    20. }
    21. };
    22. //Python页面
    23. class Python
    24. {
    25. public:
    26. void header()
    27. {
    28. cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    29. }
    30. void footer()
    31. {
    32. cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    33. }
    34. void left()
    35. {
    36. cout << "Java,Python,C++...(公共分类列表)" << endl;
    37. }
    38. void content()
    39. {
    40. cout << "Python学科视频" << endl;
    41. }
    42. };
    43. //C++页面
    44. class CPP
    45. {
    46. public:
    47. void header()
    48. {
    49. cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    50. }
    51. void footer()
    52. {
    53. cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    54. }
    55. void left()
    56. {
    57. cout << "Java,Python,C++...(公共分类列表)" << endl;
    58. }
    59. void content()
    60. {
    61. cout << "C++学科视频" << endl;
    62. }
    63. };
    64. void test01()
    65. {
    66. //Java页面
    67. cout << "Java下载视频页面如下: " << endl;
    68. Java ja;
    69. ja.header();
    70. ja.footer();
    71. ja.left();
    72. ja.content();
    73. cout << "--------------------" << endl;
    74. //Python页面
    75. cout << "Python下载视频页面如下: " << endl;
    76. Python py;
    77. py.header();
    78. py.footer();
    79. py.left();
    80. py.content();
    81. cout << "--------------------" << endl;
    82. //C++页面
    83. cout << "C++下载视频页面如下: " << endl;
    84. CPP cp;
    85. cp.header();
    86. cp.footer();
    87. cp.left();
    88. cp.content();
    89. }
    90. int main() {
    91. test01();
    92. system("pause");
    93. return 0;
    94. }

    如上面代码所示:程序中出现了大量重复的代码。我们现在就需要想办法解决这个问题。

    继承实现:

    1. //公共页面
    2. class BasePage
    3. {
    4. public:
    5. void header()
    6. {
    7. cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    8. }
    9. void footer()
    10. {
    11. cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    12. }
    13. void left()
    14. {
    15. cout << "Java,Python,C++...(公共分类列表)" << endl;
    16. }
    17. };
    18. //Java页面
    19. //继承写法: class 子类名:继承方式 父类名
    20. class Java : public BasePage
    21. {
    22. public:
    23. void content()
    24. {
    25. cout << "JAVA学科视频" << endl;
    26. }
    27. };
    28. //Python页面
    29. class Python : public BasePage
    30. {
    31. public:
    32. void content()
    33. {
    34. cout << "Python学科视频" << endl;
    35. }
    36. };
    37. //C++页面
    38. class CPP : public BasePage
    39. {
    40. public:
    41. void content()
    42. {
    43. cout << "C++学科视频" << endl;
    44. }
    45. };
    46. void test01()
    47. {
    48. //Java页面
    49. cout << "Java下载视频页面如下: " << endl;
    50. Java ja;
    51. ja.header();
    52. ja.footer();
    53. ja.left();
    54. ja.content();
    55. cout << "--------------------" << endl;
    56. //Python页面
    57. cout << "Python下载视频页面如下: " << endl;
    58. Python py;
    59. py.header();
    60. py.footer();
    61. py.left();
    62. py.content();
    63. cout << "--------------------" << endl;
    64. //C++页面
    65. cout << "C++下载视频页面如下: " << endl;
    66. CPP cp;
    67. cp.header();
    68. cp.footer();
    69. cp.left();
    70. cp.content();
    71. }
    72. int main() {
    73. test01();
    74. system("pause");
    75. return 0;
    76. }

     总结:

    继承的好处:可以减少重复的代码

    class A : public B;

    A 类称为子类 或 派生类

    B 类称为父类 或 基类

    派生类中的成员,包含两大部分

    一类是从基类继承过来的,一类是自己增加的成员。

    从基类继承过过来的表现其共性,而新增的成员体现了其个性


    😎继承方式 

    根据上面的过程我们已经知道了继承的语法:class 子类 : 继承方式 父类

    继承方式一共有三种:

    • 公共继承
    • 保护继承
    • 私有继承

     父类里面private的内容,子类不管哪种方式继承都拿不到。

    如果是公有继承:那么父类除私有外,其他的都是原来的权限不变。

    如果是保护继承:除私有外都变为了protected保护权限。

    如果是私有继承:除父类私有的以外,其他的都变成了private私有权限。

    示例:

    1. class Base1
    2. {
    3. public:
    4. int m_A;
    5. protected:
    6. int m_B;
    7. private:
    8. int m_C;
    9. };
    10. //公共继承
    11. class Son1 :public Base1
    12. {
    13. public:
    14. void func()
    15. {
    16. m_A; //可访问 public权限
    17. m_B; //可访问 protected权限
    18. //m_C; //不可访问父类的私有权限
    19. }
    20. };
    21. void myClass()
    22. {
    23. Son1 s1;
    24. s1.m_A; //其他类只能访问到公共权限
    25. //s1.m_B;//到子类Son1中,m_B变成了保护权限,类外访问不到。
    26. }
    27. //保护继承
    28. class Base2
    29. {
    30. public:
    31. int m_A;
    32. protected:
    33. int m_B;
    34. private:
    35. int m_C;
    36. };
    37. class Son2:protected Base2
    38. {
    39. public:
    40. void func()
    41. {
    42. m_A; //可访问 protected权限
    43. m_B; //可访问 protected权限
    44. //m_C; //不可访问
    45. }
    46. };
    47. void myClass2()
    48. {
    49. Son2 s;
    50. //s.m_A; //不可访问,保护继承,父类的公有属性来到Son2中变为保护权限,类外访问不到
    51. //私有继承
    52. class Base3
    53. {
    54. public:
    55. int m_A;
    56. protected:
    57. int m_B;
    58. private:
    59. int m_C;
    60. };
    61. class Son3:private Base3
    62. {
    63. public:
    64. void func()
    65. {
    66. m_A; //可访问 private权限
    67. m_B; //可访问 private权限
    68. //m_C; //不可访问
    69. }
    70. };
    71. class GrandSon3 :public Son3
    72. {
    73. public:
    74. void func()
    75. {
    76. //Son3是私有继承,所以继承Son3的属性(私有权限)在GrandSon3中都无法访问到
    77. //m_A;
    78. //m_B;
    79. //m_C;
    80. }
    81. };

    回顾一下继承方式不同,子类权限的表示:

    首先,对于父类而言,父类中隐私的东西,子类不管什么方式继承都无法访问;

    如果是公共继承,父类中的公有和保护属性的访问权限没有变化,如果是保护继承,公有和保护权限变成了保护,如果是私有继承,这两个权限就都变成了私有。


     😎继承中的对象模型

    问题:从父类继承过来的成员,哪些属于子类对象中?

    1. class Base
    2. {
    3. public:
    4. int m_A;
    5. protected:
    6. int m_B;
    7. private:
    8. int m_C; //私有成员只是被隐藏了,但是还是会继承下去
    9. };
    10. //公共继承
    11. class Son :public Base
    12. {
    13. public:
    14. int m_D;
    15. };
    16. void test01()
    17. {
    18. //16
    19. //父类中所有非静态成员属性都会被子类继承下去
    20. //父类中私有成员属性 是被编译器给隐藏了,因此是访问不到的,但是确实是被继承了
    21. cout << "sizeof Son = " << sizeof(Son) << endl;
    22. }
    23. int main() {
    24. test01();
    25. system("pause");
    26. return 0;
    27. }

    通过工具验证:

    打开开发人员命令提示符工具。

     如何找到文件位置,复制,然后在工具里面跳转到项目文件所在盘,“cd 文件路径”,跳转之后再输入dir,定位到当前CPP文件的盘符

    然后输入: cl /d1 reportSingleClassLayout查看的类名 所属文件名

    上面一串英文的意思就是报告单个类的布局。

    结论:

            父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到


     😎继承中构造和析构顺序

    子类继承父类后,当创建子类对象,也会调用父类的构造函数

    问题:

    父类和子类的构造和析构顺序是谁先谁后?

    1. class Base
    2. {
    3. public:
    4. Base()
    5. {
    6. cout << "Base构造函数!" << endl;
    7. }
    8. ~Base()
    9. {
    10. cout << "Base析构函数!" << endl;
    11. }
    12. };
    13. class Son : public Base
    14. {
    15. public:
    16. Son()
    17. {
    18. cout << "Son构造函数!" << endl;
    19. }
    20. ~Son()
    21. {
    22. cout << "Son析构函数!" << endl;
    23. }
    24. };
    25. void test01()
    26. {
    27. //继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
    28. Son s;
    29. }
    30. int main() {
    31. test01();
    32. system("pause");
    33. return 0;
    34. }

    说明是先构造父类,再子类,析构相反

    总结:

            继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反  


    😎继承同名成员处理方式

    问题:

    当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

    • 访问子类同名成员 直接访问即可(就近原则,正常访问的是子类的)
    • 访问父类同名成员 需要加作用域
    1. class Base {
    2. public:
    3. Base()
    4. {
    5. m_A = 100;
    6. }
    7. void func()
    8. {
    9. cout << "Base - func()调用" << endl;
    10. }
    11. void func(int a)
    12. {
    13. cout << "Base - func(int a)调用" << endl;
    14. }
    15. public:
    16. int m_A;
    17. };
    18. class Son : public Base {
    19. public:
    20. Son()
    21. {
    22. m_A = 200;
    23. }
    24. //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
    25. void func()
    26. {
    27. cout << "Son - func()调用" << endl;
    28. }
    29. public:
    30. int m_A;
    31. };
    32. void test01()
    33. {
    34. Son s;
    35. cout << "Son下的m_A = " << s.m_A << endl;
    36. cout << "Base下的m_A = " << s.Base::m_A << endl;
    37. s.func();
    38. s.Base::func();
    39. //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数(只要是func的全部隐藏,不能直接访问),想要访问只能加作用域。
    40. s.func(100); //报错
    41. s.Base::func(10);
    42. }
    43. int main() {
    44. test01();
    45. system("pause");
    46. return EXIT_SUCCESS;
    47. }

    总结:

    1. 子类对象可以直接访问到子类中同名成员

    2. 子类对象加作用域可以访问到父类同名成员

    3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数


    😎继承同名静态成员处理方式

    问题:

            继承中同名的静态成员在子类对象上如何进行访问?

    静态成员和非静态成员出现同名,处理方式一致

    • 访问子类同名成员 直接访问即可

    • 访问父类同名成员 需要加作用域

    示例:

    1. class Base {
    2. public:
    3. static void func()
    4. {
    5. cout << "Base - static void func()" << endl;
    6. }
    7. static void func(int a)
    8. {
    9. cout << "Base - static void func(int a)" << endl;
    10. }
    11. //静态成员属性(编译阶段分配到内存,所有对象共享同一份数据)
    12. static int m_A;
    13. };
    14. //类内声明,类外初始化
    15. int Base::m_A = 100;
    16. class Son : public Base {
    17. public:
    18. static void func()
    19. {
    20. cout << "Son - static void func()" << endl;
    21. }
    22. static int m_A;
    23. };
    24. int Son::m_A = 200;
    25. //同名成员属性
    26. void test01()
    27. {
    28. //通过对象访问
    29. cout << "通过对象访问: " << endl;
    30. Son s;
    31. cout << "Son 下 m_A = " << s.m_A << endl;
    32. cout << "Base 下 m_A = " << s.Base::m_A << endl;
    33. //通过类名访问(静态成员变量专属福利)
    34. cout << "通过类名访问: " << endl;
    35. cout << "Son 下 m_A = " << Son::m_A << endl;
    36. //第一个::代表通过类目方式访问,第二个::代表访问父类作用域下
    37. cout << "Base 下 m_A = " << Son::Base::m_A << endl;
    38. }
    39. //同名成员函数
    40. void test02()
    41. {
    42. //通过对象访问
    43. cout << "通过对象访问: " << endl;
    44. Son s;
    45. s.func();
    46. s.Base::func();
    47. cout << "通过类名访问: " << endl;
    48. Son::func();
    49. Son::Base::func();
    50. //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
    51. Son::Base::func(100);
    52. }
    53. int main() {
    54. //test01();
    55. test02();
    56. system("pause");
    57. return 0;
    58. }

    总结: 

    同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)


    😎多继承语法

    C++允许一个类继承多个类

    语法:

        class 子类 :继承方式 父类1 , 继承方式 父类2...

    多继承可能会引发父类中有同名成员出现,需要加作用域区分

            C++实际开发中不建议用多继承

    示例:

    1. class Base1 {
    2. public:
    3. Base1()
    4. {
    5. m_A = 100;
    6. }
    7. public:
    8. int m_A;
    9. };
    10. class Base2 {
    11. public:
    12. Base2()
    13. {
    14. m_A = 200; //开始是m_B 不会出问题,但是改为mA就会出现不明确
    15. }
    16. public:
    17. int m_A;
    18. };
    19. //子类需要继承Base1和Base2
    20. //语法:class 子类:继承方式 父类1 ,继承方式 父类2 …
    21. class Son : public Base2, public Base1
    22. {
    23. public:
    24. Son()
    25. {
    26. m_C = 300;
    27. m_D = 400;
    28. }
    29. public:
    30. int m_C;
    31. int m_D;
    32. };
    33. //多继承容易产生成员同名的情况
    34. //通过使用类名作用域可以区分调用哪一个基类的成员
    35. void test01()
    36. {
    37. Son s;
    38. //16 也可以使用开发者工具验证(前面已经说了)
    39. cout << "sizeof Son = " << sizeof(s) << endl;
    40. //当父类中出现同名成员,需要加作用域区分
    41. cout << s.Base1::m_A << endl;
    42. cout << s.Base2::m_A << endl;
    43. }
    44. int main() {
    45. test01();
    46. system("pause");
    47. return 0;
    48. }

    sizeof的结果: 

     

     总结:

            多继承中如果父类中出现了同名情况,子类使用时候要加作用域


    😎菱形继承(C++特色) 

    菱形继承概念:

            两个派生类继承同一个基类

            又有某个类同时继承者两个派生类

            这种继承被称为菱形继承,或者钻石继承

    典型的菱形继承案例(草泥马):

     

    菱形继承问题:

    1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。

    2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

    1. //动物类
    2. class Animal
    3. {
    4. public:
    5. int m_Age;
    6. };
    7. //利用虚继承,解决菱形继承问题。
    8. //继承前加virtual关键字后,变为虚继承
    9. //此时公共的父类Animal称为虚基类
    10. class Sheep : virtual public Animal {}; //羊类
    11. class Tuo : virtual public Animal {}; //驼类
    12. class SheepTuo : public Sheep, public Tuo {}; //羊驼类
    13. void test01()
    14. {
    15. SheepTuo st;
    16. st.Sheep::m_Age = 10;
    17. st.Tuo::m_Age = 20;
    18. //当出现菱形继承的时候,两个父类拥有系统数据,需要加以作用域区分
    19. cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
    20. cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
    21. //这份数据我们知道,只要有一份就行了,菱形继承导致数据有两份,资源浪费。
    22. cout << "st.m_Age = " << st.m_Age << endl;
    23. }
    24. int main() {
    25. test01();
    26. system("pause");
    27. return 0;
    28. }

    菱形继承的问题二: 

    加virtual虚继承之前:

    利用虚继承,解决菱形继承的问题。

    总结:

    • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义

    • 利用虚继承可以解决菱形继承问题


    虚继承解决菱形继承问题的底层原理图解:

                                            虚继承解决菱形继承问题的底层剖析                      —— By CSDN码雨


     

     

     


     结语:

            博客制作不易,画图有点费眼,要是觉得博客对您有帮助,请一键三连嗷!

                                                                                                          —— By CSDN码雨

  • 相关阅读:
    Jquery 老项目引入vue,elementui
    《杂七杂八》:Linux(Kali)终端快捷键
    技术分享 | MySQL Shell 定制化部署 MySQL 实例
    (附源码)基于Springboot智慧园区管理系统-计算机毕设 88160
    计算机毕业设计(附源码)python众筹平台
    使用openssl工具生成CSR文件
    [翻译] 使用 TensorFlow 进行分布式训练
    图文带你彻底弄懂MySQL事务原子性之UndoLog
    Ajax:异步的 JavaScript 和 XML
    【服务器 | 宝塔】宝塔面板卸载重装教程:清理删除宝塔面板并重新开始
  • 原文地址:https://blog.csdn.net/m0_62853489/article/details/127639391