• 第十七章 类和对象——继承


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

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

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

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

    一、继承的基本语法

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

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

    普通实现:

    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.  ​
    65.  void test01()
    66.  {
    67.   //Java页面
    68.   cout << "Java下载视频页面如下: " << endl;
    69.   Java ja;
    70.   ja.header();
    71.   ja.footer();
    72.   ja.left();
    73.   ja.content();
    74.   cout << "--------------------" << endl;
    75.  ​
    76.   //Python页面
    77.   cout << "Python下载视频页面如下: " << endl;
    78.   Python py;
    79.   py.header();
    80.   py.footer();
    81.   py.left();
    82.   py.content();
    83.   cout << "--------------------" << endl;
    84.  ​
    85.   //C++页面
    86.   cout << "C++下载视频页面如下: " << endl;
    87.   CPP cp;
    88.   cp.header();
    89.   cp.footer();
    90.   cp.left();
    91.   cp.content();
    92.  ​
    93.  }
    94.  ​
    95.  int main() {
    96.  ​
    97.   test01();
    98.  ​
    99.   system("pause");
    100.  ​
    101.   return 0;
    102.  }

    继承实现:

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

    总结:

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

    class A : public B;

    A 类称为子类 或 派生类

    B 类称为父类 或 基类

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

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

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

    二、继承方式

    继承的语法:class 子类 : 继承方式 父类

    继承方式一共有三种:

    • 公共继承

    • 保护继承

    • 私有继承

    示例:

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

    三、继承中的对象模型

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

    示例:

    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.  //公共继承
    12.  class Son :public Base
    13.  {
    14.  public:
    15.   int m_D;
    16.  };
    17.  ​
    18.  void test01()
    19.  {
    20.   cout << "sizeof Son = " << sizeof(Son) << endl;
    21.  }
    22.  ​
    23.  int main() {
    24.  ​
    25.   test01();
    26.  ​
    27.   system("pause");
    28.  ​
    29.   return 0;
    30.  }

    利用工具查看:

    打开工具窗口后,定位到当前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.  ​
    14.  class Son : public Base
    15.  {
    16.  public:
    17.   Son()
    18.   {
    19.   cout << "Son构造函数!" << endl;
    20.   }
    21.   ~Son()
    22.   {
    23.   cout << "Son析构函数!" << endl;
    24.   }
    25.  ​
    26.  };
    27.  ​
    28.  ​
    29.  void test01()
    30.  {
    31.   //继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
    32.   Son s;
    33.  }
    34.  ​
    35.  int main() {
    36.  ​
    37.   test01();
    38.  ​
    39.   system("pause");
    40.  ​
    41.   return 0;
    42.  }

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

    五、继承同名成员处理方式

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

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

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

    示例:

    1.  class Base {
    2.  public:
    3.   Base()
    4.   {
    5.   m_A = 100;
    6.   }
    7.  ​
    8.   void func()
    9.   {
    10.   cout << "Base - func()调用" << endl;
    11.   }
    12.  ​
    13.   void func(int a)
    14.   {
    15.   cout << "Base - func(int a)调用" << endl;
    16.   }
    17.  ​
    18.  public:
    19.   int m_A;
    20.  };
    21.  ​
    22.  ​
    23.  class Son : public Base {
    24.  public:
    25.   Son()
    26.   {
    27.   m_A = 200;
    28.   }
    29.  ​
    30.   //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
    31.   //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
    32.   void func()
    33.   {
    34.   cout << "Son - func()调用" << endl;
    35.   }
    36.  public:
    37.   int m_A;
    38.  };
    39.  ​
    40.  void test01()
    41.  {
    42.   Son s;
    43.  ​
    44.   cout << "Son下的m_A = " << s.m_A << endl;
    45.   cout << "Base下的m_A = " << s.Base::m_A << endl;
    46.  ​
    47.   s.func();
    48.   s.Base::func();
    49.   s.Base::func(10);
    50.  ​
    51.  }
    52.  int main() {
    53.  ​
    54.   test01();
    55.  ​
    56.   system("pause");
    57.   return EXIT_SUCCESS;
    58.  }

    总结:

    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.  ​
    17.  class Son : public Base {
    18.  public:
    19.   static void func()
    20.   {
    21.   cout << "Son - static void func()" << endl;
    22.   }
    23.   static int m_A;
    24.  };
    25.  ​
    26.  int Son::m_A = 200;
    27.  ​
    28.  //同名成员属性
    29.  void test01()
    30.  {
    31.   //通过对象访问
    32.   cout << "通过对象访问: " << endl;
    33.   Son s;
    34.   cout << "Son 下 m_A = " << s.m_A << endl;
    35.   cout << "Base 下 m_A = " << s.Base::m_A << endl;
    36.  ​
    37.   //通过类名访问
    38.   cout << "通过类名访问: " << endl;
    39.   cout << "Son 下 m_A = " << Son::m_A << endl;
    40.   cout << "Base 下 m_A = " << Son::Base::m_A << endl;
    41.  }
    42.  ​
    43.  //同名成员函数
    44.  void test02()
    45.  {
    46.   //通过对象访问
    47.   cout << "通过对象访问: " << endl;
    48.   Son s;
    49.   s.func();
    50.   s.Base::func();
    51.  ​
    52.   cout << "通过类名访问: " << endl;
    53.   Son::func();
    54.   Son::Base::func();
    55.   //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
    56.   Son::Base::func(100);
    57.  }
    58.  int main() {
    59.  ​
    60.   //test01();
    61.   test02();
    62.  ​
    63.   system("pause");
    64.  ​
    65.   return 0;
    66.  }

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

    七、多继承语法

    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. //语法:class 子类:继承方式 父类1 ,继承方式 父类2
    20. class Son : public Base2, public Base1
    21. {
    22. public:
    23. Son()
    24. {
    25. m_C = 300;
    26. m_D = 400;
    27. }
    28. public:
    29. int m_C;
    30. int m_D;
    31. };
    32. //多继承容易产生成员同名的情况
    33. //通过使用类名作用域可以区分调用哪一个基类的成员
    34. void test01()
    35. {
    36. Son s;
    37. cout << "sizeof Son = " << sizeof(s) << endl;
    38. cout << s.Base1::m_A << endl;
    39. cout << s.Base2::m_A << endl;
    40. }
    41. int main() {
    42. test01();
    43. system("pause");
    44. return 0;
    45. }

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

    八、菱形继承

    菱形继承概念:

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

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

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

    典型的菱形继承案例:

    ​​​​​​​

    菱形继承问题:

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

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

    示例:

    1. class Animal
    2. {
    3. public:
    4. int m_Age;
    5. };
    6. //继承前加virtual关键字后,变为虚继承
    7. //此时公共的父类Animal称为虚基类
    8. class Sheep : virtual public Animal {};
    9. class Tuo   : virtual public Animal {};
    10. class SheepTuo : public Sheep, public Tuo {};
    11. void test01()
    12. {
    13. SheepTuo st;
    14. st.Sheep::m_Age = 100;
    15. st.Tuo::m_Age = 200;
    16. cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
    17. cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
    18. cout << "st.m_Age = " << st.m_Age << endl;
    19. }
    20. int main() {
    21. test01();
    22. system("pause");
    23. return 0;
    24. }

    总结:

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

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

  • 相关阅读:
    SAP Spartacus UI 通过 HTTP Interceptor 给请求添加 Authorization 字段的源代码分析
    如何调用本地 json 文件数据
    k8s client-go源码分析 informer源码分析(1)-概要分析
    Spring Boot 2.x系列【14】功能篇之@Conditional注解
    JAVA面经整理(5)
    C/C++教程 从入门到精通《第十五章》—— MFC资源详解
    php费尔康框架phalcon(费尔康)框架学习笔记
    ES优化实战 - 小操作节省百分之三十以上的磁盘空间
    设计模式——策略模式
    《HelloGitHub》第 74 期
  • 原文地址:https://blog.csdn.net/qq_26126363/article/details/133715666