当前位置:   article > 正文

面向对象三大特性---继承_面向对象程序设计继承性

面向对象程序设计继承性

一.继承的概念

  继承(inheritance)机制是面向对象程序设计使代码可以复用 的最重要的手段,它允许程序员在 保持原有类特 性的基础上进行扩展, 增加功能,这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。 
格式方法:

二.继承的继承方式与作用域

继承和访问方式都有public,protected和private这三类.

其关系如下图:

总结:

1.基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它.

2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能 访问,就定义为protected可以看出保护成员限定符是因继承才出现的。

3.class默认继承方式是private,struct默认继承方式是public,一般最好显示写出继承方式

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

切片:派生类对象给基类对象/指针/引用赋值.也叫做切割

基类对象不能赋值给派生类对象.基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的.

四.继承中的作用域

1. 在继承体系中 基类 派生类 都有 独立的作用域。
2. 子类和父类中有同名成员, 子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定 义。 (在子类成员函数中,可以 使用 基类 :: 基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在 继承体系里 面最好 不要定义同名的成员。

五.派生类中的默认成员函数

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的 operator= 必须要调用基类的 operator= 完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类 对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。

总之:派生类必须对基类进行初始化,派生类的拷贝构造和赋值运算符重载必须完成对基类的对应函数的调用,构造函数和析构函数顺序问题(先构造基类,后析构基类).

  1. #include<iostream>
  2. using namespace std;
  3. class Person
  4. {
  5. public:
  6. Person(const char* name = "peter")
  7. : _name(name)
  8. {
  9. cout << "Person()" << endl;
  10. }
  11. Person(const Person& p)
  12. : _name(p._name)
  13. {
  14. cout << "Person(const Person& p)" << endl;
  15. }
  16. Person& operator=(const Person& p)
  17. {
  18. cout << "Person operator=(const Person& p)" << endl;
  19. if (this != &p)
  20. _name = p._name;
  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. cout << "Student& operator= (const Student& s)" << endl;
  48. if (this != &s)
  49. {
  50. Person::operator =(s);
  51. _num = s._num;
  52. }
  53. return *this;
  54. }
  55. ~Student()
  56. {
  57. cout << "~Student()" << endl;
  58. }
  59. protected:
  60. int _num; //学号
  61. };
  62. void main()
  63. {
  64. Student s1("jack", 18);
  65. Student s2(s1);
  66. }

注意:

友元关系不能被继承.比如你父亲的朋友不一定是你的朋友.也就是说基类友元不能访问子类私有和保护成员

基类中有静态成员,继承后也只有一份静态成员

菱形继承会有数据冗余和二义性的问题.

六.菱形继承问题与虚继承

C++中继承有单继承和多继承.

多继承里面会出现菱形继承比如:

菱形继承会出现数据二义性,如上图,Assistant类中会存储两份Person中的数据,这样会造成数据的二义性,解决的方法就是虚拟继承.

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在 Student Teacher 的继承 Person 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

  1. #include<cstdio>
  2. #include<iostream>
  3. using namespace std;
  4. class A {
  5. public:
  6. int _a;
  7. };
  8. //class B : virtual public A {
  9. class B : public A{
  10. public:
  11. int _b;
  12. };
  13. // class C : public A
  14. class C : public A {
  15. public:
  16. int _c;
  17. };
  18. class D : public B, public C {
  19. public:
  20. int _d;
  21. };
  22. int main()
  23. {
  24. D d;
  25. d.B::_a = 1;
  26. d.C::_a = 2;
  27. d._b = 3;
  28. d._c = 4;
  29. d._d = 5;
  30. printf("B->a: %p\n", &(d.B::_a));
  31. printf("C->a: %p\n\n", &(d.C::_a));
  32. printf("D->b: %p\n", &(d._b));
  33. printf("D->c: %p\n", &(d._c));
  34. printf("D->d: %p\n", &(d._d));
  35. return 0;
  36. }

  1. #include<cstdio>
  2. #include<iostream>
  3. using namespace std;
  4. class A {
  5. public:
  6. int _a;
  7. };
  8. // class B : public A
  9. class B : virtual public A {
  10. public:
  11. int _b;
  12. };
  13. // class C : public A
  14. class C : virtual public A {
  15. public:
  16. int _c;
  17. };
  18. class D : public B, public C {
  19. public:
  20. int _d;
  21. };
  22. int main()
  23. {
  24. D d;
  25. d.B::_a = 1;
  26. d.C::_a = 2;
  27. d._b = 3;
  28. d._c = 4;
  29. d._d = 5;
  30. printf("B->a: %p\n", &(d.B::_a));
  31. printf("C->a: %p\n\n", &(d.C::_a));
  32. printf("D->a: %p\n", &(d._a));
  33. printf("D->b: %p\n", &(d._b));
  34. printf("D->c: %p\n", &(d._c));
  35. printf("D->d: %p\n", &(d._d));
  36. return 0;
  37. }

D对象中将A放到的了对象组成的最下面,这个A同时属于B和C

这里通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A.

七.组合和继承问题

继承是一种代码复用的手段,但是多继承会出现菱形继承,因此我们在使用集成时尽量避免菱形继承,少用多继承.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。

组合与继承类似却又不同,在我们平常程序设计中,也可以使用继承和组合一起设计,不要一味使用继承,而继承在一定程度上破坏了封装性,增加了程序的耦合.

继承优缺点:

优点:容易去复用代码,适合扩展,增加程序复杂程度

缺点:

(1)菱形继承是继承的缺点之一,并且程序耦合性较高

 (2)代码白盒复用,父类实现暴露给了子类,破坏封装

(3)父类代码修改时,子类可能也得修改,维护难度增加

(4)不支持动态扩展,父类在编译期就已经确定

组合:

优点:

(1)代码黑盒复用,耦合度低,代码维护性好

(2)整体类与局部类之间松耦合,相互独立且,支持扩展
(3)支持动态扩展,可在运行时根据具体对象选择不同类型的组合对象

缺点:在设计类时需要设计所有局部类,导致系统的对象很多

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/43415
推荐阅读
  

闽ICP备14008679号