• 《C++》继承


    一、本篇介绍

    1、继承基础介绍

    2、基类与派生类对象赋值转换

    3、隐藏

    4、派生类的默认成员函数

    5、继承与友元

    6、继承与静态成员

    7、菱形继承

    8、继承与组合

    二、继承基础

    继承概念:继承属于面向对象三大特性之一,是代码复用的重要手段,它允许程序员在原有类特性的基础上进行拓展,增加功能,这样产生的新类叫做派生类,继承呈现了面向对象设计的层次结构,体现了由简单到复杂的认知过程,继承是类设计层次的复用。

    继承的方式有三种:

    公有继承、保护继承、私有继承,一般常用的是公有继承。

    ~提问~

    1、父类的私有成员在子类不可见,是不是没有继承下来?

     答:继承下来了,不可见的意思是在B的类内和类外都不能访问A的私有成员。

    2、需要知道继承下来的不只有成员变量,还有成员函数,但访问都收到访问限定符与继承方式的限制。

     

    3、class和struct默认的继承方式分别是什么?

     

     答:class默认是私有继承,struct默认是公有继承。

    三、基类与派生类对象赋值转换

    派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用反之一般的父类对象不能赋值给派生类对象。这种做法就像切割、切片一样,因此基类与派生类对象赋值转换也称作切片、切割。

    ~提问~

    一、以下代码输出什么?

    1. struct A
    2. {
    3. public:
    4. void Print(int x)
    5. {
    6. _a = x;
    7. cout << _a << endl;
    8. }
    9. public:
    10. int _a = 10;
    11. };
    12. class B : public A
    13. {
    14. private:
    15. int _b;
    16. };
    17. int main()
    18. {
    19. A a;
    20. a._a = 30;
    21. B b;
    22. b._a = 10;
    23. a = b;
    24. cout << a._a << endl;
    25. cout << b._a << endl;
    26. return 0;
    27. }

    答:输出10,b对象切片给a,即把b中父类部分赋值给a。引用、指针同理。

    试验一

    四、隐藏

    子类和父类中有相同的函数名或者变量名,子类会屏蔽父类同名成员的直接访问,这种现象称作隐藏,也叫重定义。

    ~提问~

    一、以下代码输出什么?

    1. struct A
    2. {
    3. public:
    4. void Print(int x)
    5. {
    6. cout << "Print(int x)" << endl;
    7. }
    8. public:
    9. int _a = 10;
    10. };
    11. class B : public A
    12. {
    13. public:
    14. void Print()
    15. {
    16. cout << "Print()" << endl;
    17. }
    18. public:
    19. int _a = 3;
    20. };
    21. int main()
    22. {
    23. B b;
    24. cout << b._a << endl;
    25. cout << b.A::_a << endl;
    26. b.Print();
    27. b.A::Print(2);
    28. return 0;
    29. }

     总结:成员名相同就构成隐藏,导致子类就不能直接访问父类的同名成员,如要访问需要加作用域。

    五、派生类的默认成员函数

    之前我们介绍过一个类有6个默认成员函数,分别为:

    1、编译器默认生成无参的构造函数

    2、编译器会默认生成一个浅拷贝的拷贝构造

    3、编译器会默认重载一个浅拷贝的赋值构造运算符

    4、编译器默认生成的析构函数

    5、编译器默认重载的&操作符。//一般忽略它的存在

    6、编译器默认重载的+const版本的&操作符。//一般忽略它的存在

    我们一般知道前4个就行,5和6基本不怎么会去用。

    对于派生类来说,前四个默认成员函数要做的事情多了一点。

    1、无参构造函数:对于自定义类型调用它的默认构造函数,对于继承过来的成员变量也会调用父类的默认构造函数进行初始化。

    2、浅拷贝的拷贝构造:对于自定义类型调用它的拷贝构造,对于内置类型完成浅拷贝,于继承过来的成员变量调用父类的拷贝构造完成拷贝。

    3、浅拷贝的赋值构造运算符:对于自定义类型调用它的赋值拷贝,对于内置类型完成浅拷贝,于继承过来的成员变量调用父类的赋值拷贝完成拷贝。

    4、析构函数:对于自定义类型调用它的析构函数,内置类型不处理,调用完毕后会自动调用父类的析构函数。

    1. class Person
    2. {
    3. public:
    4. Person(const char* name = "张三")
    5. : _name(name)
    6. {
    7. cout << "Person()" << endl;
    8. }
    9. Person(const Person& p)
    10. : _name(p._name)
    11. {
    12. cout << "Person(const Person& p)" << endl;
    13. }
    14. Person& operator=(const Person& p)
    15. {
    16. if (this != &p)
    17. {
    18. _name = p._name;
    19. }
    20. cout << "Person operator=(const Person& p)" << endl;
    21. return *this;
    22. }
    23. ~Person()
    24. {
    25. cout << "~Person()" << endl;
    26. }
    27. protected:
    28. string _name; // 姓名
    29. };
    30. class Student : public Person
    31. {
    32. public:
    33. Student(const char* name, int num)
    34. : Person(name)
    35. , _num(num)
    36. {
    37. cout << "Student()" << endl;
    38. }
    39. Student(const Student& s)
    40. : Person(s)
    41. , _num(s._num)
    42. {
    43. cout << "Student(const Student& s)" << endl;
    44. }
    45. Student& operator = (const Student& s)
    46. {
    47. if (this != &s)
    48. {
    49. Person::operator =(s);
    50. _num = s._num;
    51. }
    52. cout << "Student& operator= (const Student& s)" << endl;
    53. return *this;
    54. }
    55. ~Student()
    56. {
    57. cout << "~Student()" << endl;
    58. }
    59. protected:
    60. int _num; //学号
    61. };
    62. int main()
    63. {
    64. Student s1("张三",20203292);
    65. Student s2("李四", 20207532);
    66. Student s3 = s1;
    67. s3 = s2;
    68. return 0;
    69. }

    六、继承与友元

    友元关系不能继承 ,也就是说基类友元不能访问子类私有和保护成员。

    七、继承与静态成员

    基类定义的静态成员整个继承体系共享,只有一份。

    1. class Person
    2. {
    3. public:
    4. Person()
    5. {
    6. ++_count;
    7. }
    8. protected:
    9. string _name; // 姓名
    10. public:
    11. static int _count; // 统计人的个数。
    12. };
    13. int Person::_count = 0;
    14. class Student : public Person
    15. {
    16. protected:
    17. int _stuNum; // 学号
    18. };
    19. class PostGraduate : public Student
    20. {
    21. protected:
    22. string _seminarCourse; // 研究科目
    23. };
    24. int main()
    25. {
    26. Person p1;
    27. Person p2;
    28. Person p3;
    29. Student s1;
    30. Student s2;
    31. Student s3;
    32. PostGraduate s4;
    33. PostGraduate s5;
    34. cout << Person::_count << endl;
    35. cout << Student::_count << endl;
    36. cout << PostGraduate::_count << endl;
    37. PostGraduate::_count = 10;
    38. cout << Person::_count << endl;
    39. cout << Student::_count << endl;
    40. cout << PostGraduate::_count << endl;
    41. return 0;
    42. }

    八、菱形继承

    单继承:

     

    多继承:

     

    菱形继承:

    1. class A
    2. {
    3. public:
    4. int _a;
    5. };
    6. class B : public A
    7. {
    8. public:
    9. int _b;
    10. };
    11. class C : public A
    12. {
    13. public:
    14. int _c;
    15. };
    16. class D : public B, public C
    17. {
    18. public:
    19. int _d;
    20. };
    21. int main()
    22. {
    23. D d;
    24. d.B::_a = 1;
    25. d.C::_a = 2;
    26. d._b = 3;
    27. d._c = 4;
    28. d._d = 5;
    29. return 0;
    30. }

     菱形继承带来的问题:

    1、二义性

    2、数据冗余

    如何解决?

    答:使用虚继承。

    1. class A
    2. {
    3. public:
    4. int _a;
    5. };
    6. class B :virtual public A
    7. {
    8. public:
    9. int _b;
    10. };
    11. class C : virtual public A
    12. {
    13. public:
    14. int _c;
    15. };
    16. class D : public B, public C
    17. {
    18. public:
    19. int _d;
    20. };
    21. int main()
    22. {
    23. D d;
    24. d.B::_a = 1;
    25. d.C::_a = 2;
    26. d._b = 3;
    27. d._c = 4;
    28. d._d = 5;
    29. return 0;
    30. }

     九、继承与组合

    继承:

     组合:

     如果是has-a的关系就用组合,如果是is-a的关系就用继承。

    如果关系不是很明确,建议用组合。

  • 相关阅读:
    Bioinformatics2019 | FP2VEC+:基于新分子特征的分子性质预测
    springcloudalibaba架构(2):Sentinel服务容错与使用入门
    算法刷题打卡第28天:省份数量---广度优先搜索
    【C语言刷LeetCode】729. 我的日程安排表 I(M)
    pdfium三方库源码windows android编译
    visual studio code导入自定义模块(pycharm中能够运行的文件,vs code报错:未找到指定模块)
    IP组播基础
    echart简单组件封装
    Arm 架构 Ubuntu 使用 Docker 安装 Gitlab 并使用
    多台电脑之间共享、传输文件数据:不借助数据线与软件的方法
  • 原文地址:https://blog.csdn.net/m0_62171658/article/details/125956290