【C++】-- 继承(三)

简介: 【C++】-- 继承

五、继承和友元

(1)友元关系不能被继承

(2)根据友元性质,父类友元可以直接访问父类的所有成员,也可以访问子类的共有成员,但不能访问子类私有成员和保护成员

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include<iostream>
3. using namespace std;
4. 
5. class Rectangle;//前置声明Rectangle类
6. class Shape
7. {
8. public:
9.  friend void fun(Shape& s, Rectangle& r);//声明fun是Shape类的友元
10. 
11.   //构造函数
12.   Shape(float width, float height)
13.     :_width(width),
14.     _height(height)
15.   {
16.     cout << "Shape()" << endl;
17.   }
18. 
19.   //拷贝构造函数
20.   Shape(const Shape& s)
21.     :_width(s._width),
22.     _height(s._height)
23.   {
24.     cout << "Shape(const Shape& s)" << endl;
25.   }
26. 
27.   //赋值运算符重载函数
28.   Shape& operator=(const Shape& p)
29.   {
30.     cout << "Shape& operator=(const Shape& p)" << endl;
31.     if (&p != this)
32.     {
33.       _width = p._width;
34.       _height = p._height;
35.     }
36. 
37.     return *this;
38.   }
39. 
40.   //析构函数
41.   ~Shape()
42.   {
43.     cout << "~Shape()" << endl;
44.   }
45. 
46.   void setWidth(float w)
47.   {
48.     _width = w;
49.   }
50. 
51.   void setHeight(float h)
52.   {
53.     _height = h;
54.   }
55. 
56.   void ShapePrintf()
57.   {
58.     cout << "Shape : _width" << ":" << _width << endl;
59.     cout << "Shape : _height" << ":" << _height << endl;
60.   }
61. 
62. public:
63.   float _width;
64.   float _height;
65. };
66. 
67. class Rectangle : public Shape
68. {
69. public:
70. 
71.   //构造函数
72.   Rectangle(float width = 10, float height = 10, int rightAngleCounts = 4)
73.     :Shape(width, height)
74.     , _rightAngleCounts(rightAngleCounts)
75.   {
76.     cout << "Rectangle()" << endl;
77.   }
78. 
79.   //拷贝构造函数
80.   Rectangle(const Rectangle& r)
81.     :Shape(r)//切片,子类对象赋值给父类引用
82.     , _rightAngleCounts(r._rightAngleCounts)//子类自己的成员初始化为拷贝对象的子类成员值
83.   {
84.     cout << "Rectangle(const Rectangle& r)" << endl;
85.   }
86. 
87.   //赋值运算符重载函数
88.   Rectangle& operator=(const Rectangle& r)
89.   {
90.     if (this != &r)
91.     {
92.       Shape::operator=(r);
93.       _rightAngleCounts = r._rightAngleCounts;
94.     }
95.     cout << "Rectangle& operator=(const Rectangle& r)" << endl;
96. 
97.     return *this;
98.   }
99. 
100.  //析构函数
101.  ~Rectangle()
102.  {
103.    //Shape::~Shape();
104. 
105.    //清理自己的空间
106.    //delete ptr;
107. 
108.    cout << "~Rectangle()" << endl;
109.  }
110. 
111.  float Area()
112.  {
113.    return _width * _height;
114.  }
115. 
116.  void RectanglePrintf(float width, float height)
117.  {
118.    cout << "Rectangle : _width" << ":" << width << endl;
119.    cout << "Rectangle : _height" << ":" << height << endl;
120.  }
121. 
122. public://将子类自己的成员变量定义为公有
123.  int _rightAngleCounts;//直角个数
124. };
125. 
126. //友元函数
127. void fun(Shape& s, Rectangle& r)
128. {
129.  cout << "fun:s._width = " << s._width << endl;
130.  cout << "fun:r._rightAngleCounts = " << r._rightAngleCounts << endl;//子类自己的成员变量为公有时,友元可以访问
131. }
132. 
133. int main()
134. {
135.  Shape s(10, 10);
136.  Rectangle r(6, 6, 4);
137. 
138.  fun(s, r);
139. 
140.  return 0;
141. }

将子类自己的成员变量定义为公有时,友元可以访问:

但是将子类自己的成员变量定义为保护或私有时,友元不可以访问,将上述代码中的子类自己的成员变量的访问限定符:

1. public://将子类自己的成员变量定义为公有
2.  int _rightAngleCounts;//直角个数

由公有改为保护:

1. private://将子类自己的成员变量定义为保护
2.  int _rightAngleCounts;//直角个数

编译就会报错:

六、继承和静态成员

父类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include<iostream>
3. using namespace std;
4. 
5. class Shape
6. {
7. public:
8.  //构造函数
9.  Shape(float width = 10, float height = 10)
10.     :_width(width),
11.     _height(height)
12.   {
13.     ++_count;
14.   }
15. 
16. public:
17.   float _width;
18.   float _height;
19.   static int _count;
20. };
21. 
22. int Shape::_count = 0;
23. 
24. //长方形
25. class Rectangle : public Shape
26. {
27. public:
28.   int _rightAngleCounts;//直角个数
29. };
30. 
31. //三角形
32. class Triangle : public Shape
33. {
34. public:
35.   float _acuteAngleCounts;//锐角个数
36. };
37. 
38. 
39. int main()
40. {
41.   Shape s;
42.   Rectangle r;
43.   Triangle c;
44. 
45.   cout << Shape::_count << endl;
46.   cout << Rectangle::_count << endl;
47.   cout << Triangle::_count << endl;
48. 
49.   Triangle::_count = 20;
50. 
51.   cout << Shape::_count << endl;
52.   cout << Rectangle::_count << endl;
53.   cout << Triangle::_count << endl;
54. 
55.   return 0;
56. }

_count在整份代码中只有一份:

定义了3个类对象,_count就会自增3次变为3;重置了Circle类中的_count,父类中的_count也就被重置了:

静态成员可以统计整个代码一共产生了多少个对象,由于子类无论构造还是拷贝构造都会调用父类的构造函数(父类的拷贝构造对象没有被统计在内,因为父类的拷贝构造函数没有调用父类的构造函数)。

七、菱形继承

继承关系分为:单继承、多继承、菱形继承。

1.单继承

一个子类只有一个直接父类时称这个继承关系为单继承

2.多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承

多继承会导致菱形继承。

3.菱形继承

是多继承的一种特殊情况:

菱形继承会存在两个问题

(1)数据冗余

(2)二义性

如上面的TriangleEmbeddedRectangle对象中会存在两份Shape成员:

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include<iostream>
3. using namespace std;
4. 
5. class Shape
6. {
7. public:
8.  //构造函数
9.  Shape(float width = 10, float height = 10)
10.     :_width(width),
11.     _height(height)
12.   {}
13. 
14. public:
15.   float _width;
16.   float _height;
17. };
18. 
19. 
20. //长方形
21. class Rectangle : public Shape
22. {
23. public:
24.   int _rightAngleCounts;//直角个数
25. };
26. 
27. //三角形
28. class Triangle : public Shape
29. {
30. public:
31.   int _acuteAngleCounts;//锐角个数
32. };
33. 
34. //三角形内嵌于长方形
35. class TriangleEmbeddedRectangle : public Rectangle, public Triangle
36. {
37. public:
38.   int _sideCounts;//边数
39. };
40. int main()
41. {
42.   TriangleEmbeddedRectangle t;
43.   t._width = 60;//报错,指向不明
44. 
45.   return 0;
46. }

数据冗余:Rectangle和Triangle都有_width和_height,TriangleEmbeddedRectangle也只有一个,_width和_height,但是编译错误却表示有两个_width:

二义性:访问_width时,不知道要访问谁的_width:

指定作用域可以解决二义性的问题,即指定访问谁的_width:

1. int main()
2. {
3.  TriangleEmbeddedRectangle t;
4.  t.Triangle::_width = 60;//指定访问Triangle的_width
5. 
6.  return 0;
7. }

编译OK。

但是这只解决了二义性的问题,并不能解决数据冗余的问题,TriangleEmbeddedRectangle的对象t中还是有两份_width和_height。菱形虚拟继承既可以解决数据冗余也可以解决二义性的问题。

八、菱形虚拟继承

1.虚继承

在两个直接父类的继承方式访问限定符前加vitual:

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include<iostream>
3. using namespace std;
4. 
5. class Shape
6. {
7. public:
8.  //构造函数
9.  Shape(float width = 10, float height = 10)
10.     :_width(width),
11.     _height(height)
12.   {}
13. 
14. public:
15.   float _width;
16.   float _height;
17. };
18. 
19. 
20. //长方形
21. class Rectangle : virtual public Shape//虚继承
22. {
23. public:
24.   int _rightAngleCounts;//直角个数
25. };
26. 
27. //三角形
28. class Triangle : virtual public Shape//虚继承
29. {
30. public:
31.   int _acuteAngleCounts;//锐角个数
32. };
33. 
34. //三角形内嵌于长方形
35. class TriangleEmbeddedRectangle : public Rectangle, public Triangle
36. {
37. public:
38.   int _sideCounts;//边数
39. };
40. 
41. int main()
42. {
43.   TriangleEmbeddedRectangle t;
44.   t.Triangle::_width = 60;
45. 
46.   return 0;
47. }

监视发现现在改变的是同一个_width,Rectangle、Triangle和Shape中的_width是同一个_width:

现在可以不用指定作用域了。

那么C++编译器是如何通过虚继承解决数据冗余和二义性的问题的呢?

由于监视窗口被编译器处理过,看不到真实过程。可以使用内存窗口来查看。

2.虚继承的解决数据冗余和二义性的原理

(1)普通继承

对于Shape、Rectangle、Triangle、TriangleEmbeddedRectangle类,如果没有虚继承,仅仅只是菱形继承

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #define  _CRT_SECURE_NO_WARNINGS  1
3. #include<iostream>
4. using namespace std;
5. 
6. class Shape
7. {
8. public:
9.  //构造函数
10.   Shape(float width = 10,float height = 10)
11.     :_width(width)
12.     ,_height(height)
13.   {}
14. 
15. public:
16.   float _width;
17.   float _height;
18. };
19. 
20. 
21. //长方形
22. class Rectangle : public Shape//非虚继承
23. {
24. public:
25.   int _rightAngleCounts;//直角个数
26. };
27. 
28. //三角形
29. class Triangle : public Shape//非虚继承
30. {
31. public:
32.   int _acuteAngleCounts;//锐角个数
33. };
34. 
35. //三角形内嵌于长方形
36. class TriangleEmbeddedRectangle : public Rectangle, public Triangle
37. {
38. public:
39.   int _sideCounts;//边数
40. };
41. 
42. int main()
43. {
44.   TriangleEmbeddedRectangle t;
45.   t.Rectangle::_width = 1;
46.   t.Rectangle::_height = 2;
47.   t.Triangle::_width = 3;
48.   t.Triangle::_height = 4;
49.   t._rightAngleCounts = 5;
50.   t._acuteAngleCounts = 6;
51.   t._sideCounts = 7;
52. 
53.   return 0;
54. }

F10-调试-窗口-内存,输入&t,enter,将列改为4:

由于_width和_height的类型为float型,因此内存中存放的是浮点数的十六进制表示形式,VS编译器内存以小端形式存储。F10走到return 0:

可以看到内存中,蓝色为Rectangle的成员,绿色为Triangle的成员,Rectangle中存了一份Shape的成员,Triangle中存了一份Shape的成员,这就存在数据冗余,本来只需要存一份Shape就够了,但是内存中存了两份。

(2)虚继承

如果改成虚继承

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #define  _CRT_SECURE_NO_WARNINGS  1
3. #include<iostream>
4. using namespace std;
5. 
6. class Shape
7. {
8. public:
9.  //构造函数
10.   Shape(float width = 10,float height = 10)
11.     :_width(width)
12.     ,_height(height)
13.   {}
14. 
15. public:
16.   float _width;
17.   float _height;
18. };
19. 
20. 
21. //长方形
22. class Rectangle : virtual public Shape//虚继承
23. {
24. public:
25.   int _rightAngleCounts;//直角个数
26. };
27. 
28. //三角形
29. class Triangle : virtual public Shape//虚继承
30. {
31. public:
32.   int _acuteAngleCounts;//锐角个数
33. };
34. 
35. //三角形内嵌于长方形
36. class TriangleEmbeddedRectangle : public Rectangle, public Triangle
37. {
38. public:
39.   int _sideCounts;//边数
40. };
41. 
42. int main()
43. {
44.   TriangleEmbeddedRectangle t;
45.   t.Rectangle::_width = 1;
46.   t.Rectangle::_height = 2;
47.   t.Triangle::_width = 3;
48.   t.Triangle::_height = 4;
49.   t._rightAngleCounts = 5;
50.   t._acuteAngleCounts = 6;
51.   t._sideCounts = 7;
52. 
53.   return 0;
54. }

会发现Rectangle指针中存了地址0x00D39B54,Triangle的指针中也存了地址0x00D39B30,这两个地址是用来干嘛的呢?

新开两个内存分别输入0x00D39B54和0x00D39B30,在它们的下一个位置分别存放了0x14和0x0C,这两个都是偏移量,0x00B9FCDC+0x14=0x00B9FCF0,0x00B9FCE4+0x0C=0x00B9FCF0,都指向了同一地址0x00B9FCF0:这个地址是Shape成员的起始地址:

普通继承存放两份Shape成员,而虚继承只需要存在一份Shape成员即可,通过指针找到虚基表,通过虚基表指针的偏移量计算出Shape成员的起始地址。

菱形虚拟继承原理图:

虚拟菱形继承相比较于菱形继承,TriangleEmbeddedRectangle对象t的直接父类Rectangle和Triangle中存储的不再是Shape的成员,而是Shape的偏移量地址,通过该指针找到虚基表之后,计算偏移,就能知道Shape成员存放的地址,位于Rectangle和Triangle成员的下边,不再像普通继承那样位于Rectangle和Triangle成员的上边,这时候Shape既不属于Rectangle也不属于Triangle。

无论Shape类有多大,付出的代价只有2个指针,即8个字节,解决了数据冗余问题;整个内存中只需要存一份Shape的成员,解决了二义性。

一般不建议设计多继承,这样就不会出现菱形继承,也就不会有数据冗余和二义性的问题。

九.继承和组合

(1) public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。如Shape和Rectangle。他们之间是强关联关系。

(2)组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。如Room和Door。

他们之间是弱关联关系

1. #include<iostream>
2. using namespace std;
3. 
4. class Room
5. {
6. public:
7.  float _roomHegiht;//房间高度
8.  int layers;//层数
9. };
10. 
11. class Door
12. {
13. public:
14.   float _doorHegiht;//门高度
15.   string brand = "梦天木门";//品牌
16. };

 

(3)优先使用对象组合,而不是类继承 。为了降低对象之间的关联度

(4)继承允许根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。父类所有非私有成员对子类都可见,父类的改变会影响子类,父类的封装对子类不太起作用。

(5)继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

(6)对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于保持每个类被封装。子类只能使用父类的共有成员 ,子类和父类关联度低。

(7)实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

相关文章
|
2天前
|
编译器 数据安全/隐私保护 C++
c++primer plus 6 读书笔记 第十三章 类继承
c++primer plus 6 读书笔记 第十三章 类继承
|
15天前
|
C++
C++中的封装、继承与多态:深入理解与应用
C++中的封装、继承与多态:深入理解与应用
24 1
|
20天前
|
C++
c++继承
c++继承
25 0
|
26天前
|
C++
C++程序中的继承与组合
C++程序中的继承与组合
31 1
|
1月前
|
安全 前端开发 Java
【C++】从零开始认识继承二)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
19 1
|
1月前
|
安全 程序员 编译器
【C++】从零开始认识继承(一)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
27 3
【C++】从零开始认识继承(一)
|
1天前
|
存储 编译器 程序员
【C++高阶】C++继承学习手册:全面解析继承的各个方面
【C++高阶】C++继承学习手册:全面解析继承的各个方面
|
2天前
|
程序员 C语言 C++
【C++语言】继承:类特性的扩展,重要的类复用!
【C++语言】继承:类特性的扩展,重要的类复用!
|
8天前
|
C++
C++继承的相关知识点
C++继承的相关知识点
5 0
|
15天前
|
数据安全/隐私保护 C++
C++中类的继承技术详解
C++中类的继承技术详解
30 1