• 继承是面向对象的三大特性之一
  • 有些类与类之间存在特殊的关系,我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候我们就可以考虑利用继承的技术了,减少重复代码。

    继承的基本语法

    例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同
    接下来我们分别利用普通写法和继承写法来实现网页中的内容,看一下继承存在的意义以及好处
  1. #include <iostream>
  2. using namespace std;
  3. // 基础页面
  4. class BasePage {
  5. public:
  6. void header() {
  7. cout << "头部" << endl;
  8. }
  9. void footer() {
  10. cout << "底部" << endl;
  11. }
  12. void aside() {
  13. cout << "侧部" << endl;
  14. }
  15. };
  16. // 继承的好处:减少重复代码
  17. // 语法:class 子类 : 继承方式 父类
  18. // 子类 也称为 派生类
  19. // 父类 也称为 基类
  20. // Java页面
  21. class Java :public BasePage {
  22. public:
  23. void content() {
  24. cout << "Java学科视频" << endl;
  25. }
  26. };
  27. // Python页面
  28. class Python :public BasePage {
  29. public:
  30. void content() {
  31. cout << "Python学科视频" << endl;
  32. }
  33. };
  34. // C++页面
  35. class CPP : public BasePage {
  36. public:
  37. void content() {
  38. cout << "C++学科视频" << endl;
  39. }
  40. };
  41. void test01() {
  42. Java ja;
  43. ja.header();
  44. ja.aside();
  45. ja.content();
  46. ja.footer();
  47. Python py;
  48. py.header();
  49. py.aside();
  50. py.content();
  51. py.footer();
  52. CPP cpp;
  53. cpp.header();
  54. cpp.aside();
  55. cpp.content();
  56. cpp.footer();
  57. }
  58. int main(void) {
  59. test01();
  60. return 0;
  61. }

总结:

继承的好处:可以减少重复代码
class A : public B;
A类称为子类 或 派生类
B 类称为父类 或 基类

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

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

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

继承方式

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

继承方式一共有三种:(父类私有成员,子类无法继承)

  • 公共继承,原来父类除了私有成员的其他成员权限不变
  • 保护继承,public变为protected
  • 私有继承,public和protected变为private
  1. #include <iostream>
  2. using namespace std;
  3. // 继承方式
  4. // 公共继承
  5. class Base1 {
  6. public:
  7. int m_A;
  8. protected:
  9. int m_B;
  10. private:
  11. int m_C;
  12. };
  13. class Son1 : public Base1 {
  14. public:
  15. void func() {
  16. m_A = 10; // 父类中的公共权限成员,到子类中依然是公共权限
  17. m_B = 10; // 父类中的保护权限成员,到子类中依然是保护权限
  18. // m_C = 10; // 父类中的私有权限成员,子类访问不到
  19. }
  20. };
  21. void test01() {
  22. Son1 s;
  23. s.func();
  24. cout << "m_A = " << s.m_A << endl;
  25. //cout << "m_B = " << s.m_B << endl; 错误,m_B是保护权限,无法访问
  26. }
  27. // 保护继承
  28. class Son2 : protected Base1 {
  29. public:
  30. void func() {
  31. m_A = 10; // 父类中的公共权限成员,到子类中是保护权限
  32. m_B = 20; // 父类中的保护权限成员,到子类中依然是保护权限
  33. }
  34. };
  35. void test02() {
  36. Son2 s;
  37. s.func();
  38. //cout << "m_A = " << s.m_A << endl; 保护权限无法访问
  39. //cout << "m_B = " << s.m_B << endl; 保护权限无法访问
  40. }
  41. // 私有继承
  42. class Son3 : private Base1 {
  43. public:
  44. void func() {
  45. m_A = 10; // 父类中的公共权限成员,到子类中变成私有权限
  46. m_B = 20; // 父类中的保护权限成员,到子类中变成也私有权限
  47. }
  48. };
  49. class GrandSon3 : public Son3 {
  50. void func() {
  51. //m_A = 100; // 到了Son3中,m_A变为了私有成员,即使是儿子,也是访问不到的
  52. //m_b = 100; // 到了Son3中,m_B变为了私有成员,即使是儿子,也是访问不到的
  53. }
  54. };
  55. void test03() {
  56. Son3 s;
  57. s.func();
  58. // cout << "m_A = " << s.m_A << endl; 私有权限无法访问
  59. // cout << "m_B = " << s.m_B << endl; 私有权限无法访问
  60. }
  61. int main(void) {
  62. test01();
  63. test02();
  64. test03();
  65. return 0;
  66. }

继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?
// 利用开发人员命令提示工具查看对象模型
// 跳转盘符 E:
// 跳转文件路径 cd 具体路径下
// 查看命令:
// cl d1 reportSingleClassLayout类名 文件名

  1. #include <iostream>
  2. using namespace std;
  3. class Base {
  4. public:
  5. int m_A;
  6. protected:
  7. int m_B;
  8. private:
  9. int m_C;
  10. };
  11. class Son : public Base {
  12. public:
  13. int m_D;
  14. };
  15. // 利用开发人员命令提示工具查看对象模型
  16. // 跳转盘符 E:
  17. // 跳转文件路径 cd 具体路径下
  18. // 查看命令:
  19. // cl d1 reportSingleClassLayout类名 文件名
  20. void test01() {
  21. // 16
  22. // 父类中所有非静态成员属性都会被子类继承下去
  23. // 父类中私有成员属性 是被编译器给隐藏了,因此是访问不到的,但是确实被继承我去了。
  24. cout << "size of Son = " << sizeof(Son) << endl;
  25. }
  26. int main(void) {
  27. test01();
  28. return 0;
  29. }

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

继承中构造和析构顺序

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

  1. #include <iostream>
  2. using namespace std;
  3. // 继承中的构造和析构顺序
  4. class Base {
  5. public:
  6. Base() {
  7. cout << "Base构造函数!" << endl;
  8. }
  9. ~Base() {
  10. cout << "Base析构函数!" << endl;
  11. }
  12. };
  13. class Son : public Base {
  14. public:
  15. Son() {
  16. cout << "Son构造函数!" << endl;
  17. }
  18. ~Son() {
  19. cout << "Son析构函数!" << endl;
  20. }
  21. };
  22. void test01() {
  23. // 继承中的构造和析构顺序如下:
  24. // 先构造父类,再构造子类,析构的顺序与构造的顺序相反,栈模型
  25. Son s;
  26. }
  27. int main(void) {
  28. test01();
  29. return 0;
  30. }

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

继承同名成员处理方式

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

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
  1. #include <iostream>
  2. using namespace std;
  3. // 继承中同名成员的处理
  4. class Base {
  5. public:
  6. Base() {
  7. m_A = 100;
  8. }
  9. void func() {
  10. cout << "Base - func()函数调用" << endl;
  11. }
  12. void func(int a) {
  13. cout << "Base - func(int a)函数调用" << endl;
  14. }
  15. int m_A;
  16. };
  17. class Son : public Base {
  18. public:
  19. Son() {
  20. m_A = 200;
  21. }
  22. void func() {
  23. cout << "Son - func()函数调用" << endl;
  24. }
  25. int m_A;
  26. };
  27. // 同名成员属性处理
  28. void test01() {
  29. Son s;
  30. cout << "Son下的 m_A = " << s.m_A << endl;
  31. // 如果通过子类对象 访问到父类中的同名成员,需要加作用域
  32. cout << "Base下的 m_A = " << s.Base::m_A << endl;
  33. }
  34. // 同名成员函数处理
  35. void test02() {
  36. Son s;
  37. s.func(); // 直接调用 调用的是子类中的同名成员
  38. // 如何调用父类中的同名成员函数呢?
  39. s.Base::func();
  40. // 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
  41. // 如果想访问到父类中被隐藏的同名成员函数,需要加作用域
  42. s.Base::func(100);
  43. }
  44. int main(void) {
  45. //test01();
  46. test02();
  47. return 0;
  48. }

总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数时,子类会隐藏父类中所有的同名成员函数,加作用域可以访问到父类中的同名函数

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

问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
  1. #include <iostream>
  2. using namespace std;
  3. // 继承中的同名静态成员处理方式
  4. class Base {
  5. public:
  6. static int m_A;
  7. static void func() {
  8. cout << "Base - static void func()" << endl;
  9. }
  10. static void func(int a) {
  11. cout << "Base - static void func(int a)" << endl;
  12. }
  13. };
  14. // 类外初始化
  15. int Base::m_A = 100;
  16. class Son :public Base {
  17. public:
  18. Son() {
  19. Base::m_A = 300;
  20. }
  21. static int m_A;
  22. static void func() {
  23. cout << "Son - static void func()" << endl;
  24. }
  25. };
  26. int Son::m_A = 200;
  27. // 同名静态成员属性
  28. void test01() {
  29. // 通过对象访问
  30. cout << "通过对象访问:" << endl;
  31. Son s;
  32. cout << "Son 下 m_A = " << s.m_A << endl;
  33. cout << "Base 下 m_A = " << s.Base::m_A << endl;
  34. // 通过类名访问
  35. cout << "通过类名访问:" << endl;
  36. cout << "Son 下 m_A = " << Son::m_A << endl;
  37. // 第一个::代表通过类名方式访问 第二个::代表访问父类作用域下的
  38. cout << "Base 下 m_A = " << Son::Base::m_A << endl;
  39. cout << "Base 下 m_A = " << Base::m_A << endl;
  40. }
  41. // 同名静态成员函数
  42. void test02() {
  43. // 1.通过对象访问
  44. cout << "通过对象访问:" << endl;
  45. Son s;
  46. s.func();
  47. s.Base::func();
  48. // 2.通过类名访问
  49. cout << "通过类名访问:" << endl;
  50. Son::func();
  51. Son::Base::func();
  52. // 子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
  53. // 如果想要访问父类中被隐藏同名成员,需要加作用域
  54. Son::Base::func(10);
  55. }
  56. int main(void) {
  57. test01();
  58. test02();
  59. return 0;
  60. }

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

多继承语法

C++允许一个类继承多个类
语法:class 子类 : 继承方式 父类1, 继承方式 父类2......

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

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

  1. #include <iostream>
  2. using namespace std;
  3. // 多继承语法
  4. class Base1 {
  5. public:
  6. Base1() {
  7. m_A = 100;
  8. m_E = 500;
  9. }
  10. int m_A;
  11. int m_E;
  12. };
  13. class Base2 {
  14. public:
  15. Base2() {
  16. m_B = 100;
  17. m_E = 600;
  18. }
  19. int m_B;
  20. int m_E;
  21. };
  22. // 子类 需要继承Base1和Base2
  23. // 语法:class 子类 : 继承方式 父类1, 继承方式 父类2...
  24. class Son : public Base1, public Base2 {
  25. public:
  26. Son() {
  27. m_C = 300;
  28. m_D = 400;
  29. }
  30. int m_C;
  31. int m_D;
  32. };
  33. void test01() {
  34. Son s;
  35. cout << "m_A = " << s.m_A << endl;
  36. cout << "m_B = " << s.m_B << endl;
  37. cout << "m_C = " << s.m_C << endl;
  38. cout << "m_D = " << s.m_D << endl;
  39. // 当父类中出现同名成员,需要加入作用域区分
  40. cout << "Base1下 m_E = " << s.Base1::m_E << endl;
  41. cout << "Base2下 m_E = " << s.Base2::m_E << endl;
  42. }
  43. int main(void) {
  44. test01();
  45. return 0;
  46. }

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

菱形继承

菱形继承概念:

两个派生类继承同一个基类
又有某个类同时继承两个派生类
这种继承称为菱形继承

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。 ```cpp

    include

    using namespace std;

// 动物类 class Animal { public: int m_Age; }; // 利用虚继承 可以解决菱形继承的问题 // 在继承之前加上关键字 virtual 变为虚继承 // Animal类称为 虚基类

// 羊类 class Sheep :virtual public Animal {

};

// 驼类 class Tuo :virtual public Animal {

};

// 羊驼类 class SheepTuo :public Sheep, public Tuo {

};

void test01() { SheepTuo st;

  1. st.Sheep::m_Age = 18;
  2. st.Tuo::m_Age = 28;
  3. // 当菱形继承,两个父类拥有相同数据,需要加以作用域区分
  4. cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
  5. cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
  6. cout << "st.m_Age = " << st.m_Age << endl;
  7. // 这份数据我们知道 只要有一份就可以,菱形继承导致数据有两份,资源浪费

}

int main() { test01();

return 0;

} ``` 总结:

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