C++ day26 代码重用(二) 多重继承(MI, multiple inheritance,主要研究多重公有继承)

雨点打透心脏的1/2处 2023-07-24 03:09 22阅读 0赞

文章目录

  • 多重继承
    • 多重继承带来的两个主要问题
      • 从两个不同基类继承同名方法
      • 从两个或者更多相关基类继承同一个类的多个实例
    • 示例 多重公有继承
    • 代码
      • 先不写SingingWaiter类
        • 出现的问题
        • 做的好的地方
        • 学到或加深印象的地方
      • 由SingingWaiter类的问题引出虚基类
  • 虚基类
    • 虚基类的构造函数在派生链条中不可以自动传递信息
    • 用虚基类写SingingWaiter类
  • MI的其他复杂问题
    • 混合使用虚基类和非虚基类会导致派生类对象中有几个基类组件
    • 虚基类略改变了C++解析二义性的方式

多重继承

前面说的都是单继承,即只有一个基类。多重继承是有两个甚至更多的基类。

公有单继承和公有多重继承(基类都用public修饰)都表示is-a关系。

私有继承和保护继承描述的都是has-a关系。

多重继承带来的两个主要问题

反正和单继承相比,多重继承肯定是更难的,很容易出现问题。因此有很多C++程序员希望删除多重继承特性,但也有一部分人觉得MI对于一些比较特殊的工程来说是特别有用的,必不可少的,所以MI被保留了,但是一定要谨慎地使用。。。

从两个不同基类继承同名方法

从两个或者更多相关基类继承同一个类的多个实例

示例 多重公有继承

worker是一个抽象基类,即有纯虚函数的基类。用它派生出singer类和waiter类,再从singer和waiter类中派生出SingingWaiter类
在这里插入图片描述

代码

先不写SingingWaiter类

  1. //Worker.h
  2. #ifndef WORKER_H_
  3. #define WORKER_H_
  4. #include <string>
  5. class Worker// an abstract base class,不可以创建抽象基类的对象
  6. {
  7. private:
  8. std::string fullname;//包含
  9. long id;
  10. public:
  11. Worker():fullname("no one"), id(0L){ }
  12. explicit Worker(const std::string & st):fullname(st), id(0L){ }
  13. explicit Worker(long ID):fullname("no one"), id(ID){ }
  14. Worker(const std::string & st, long ID):fullname(st), id(ID){ }
  15. //纯虚析构函数,基类的析构函数必须是虚的,抽象基类至少有一个纯虚函数,在派生类中抽象基类的纯虚函数自动成为虚函数
  16. virtual ~Worker() = 0;
  17. virtual void Set();
  18. virtual void Show() const;
  19. };
  20. class Waiter:public Worker // 公有继承,继承了一个Worker组件
  21. {
  22. private:
  23. int panache;
  24. public:
  25. //默认参数在构造函数中的好处:一个构造函数相当于两个
  26. explicit Waiter(int pa = 0):Worker(), panache(pa){ }//Worker()是调用基类Worker的默认构造函数,不给参数
  27. explicit Waiter(const std::string & st, int pa = 0):Worker(st), panache(pa){ }
  28. explicit Waiter(long ID, int pa = 0):Worker(ID), panache(pa){ }
  29. //有默认参数就不需要Waiter(const std::string & st, long ID):Worker(st, ID), panache(0){}了
  30. Waiter(const std::string & st, long ID, int pa = 0):Worker(st, ID), panache(pa){ }
  31. Waiter(const Worker & wk, int pa = 0):Worker(wk), panache(pa){ }//虽然抽象基类不可有对象,但是可以在形参这么写,会进行类型转换
  32. ~Waiter(){ }//和不写一样,会自动调用Worker的析构函数
  33. void Set();
  34. void Show() const;
  35. };
  36. class Singer:public Worker//继承了一个Worker组件
  37. {
  38. protected://把一些整型常量定义为保护数据成员,即对于派生链条上的类而言相当于公有数据成员,对于外界相当于私有成员
  39. enum { other, alto, contralto, soprano, bass, baritone, tenor};
  40. enum { Vtypes = 7};
  41. private:
  42. //静态私有成员是类的所有对象共享的,不属于任何一个对象
  43. static const char *pv[Vtypes];//char指针数组,每个char指针指向一个字符串,表示一个声音类型
  44. int voice;
  45. public:
  46. Singer(int vo = 0):Worker(), voice(vo){ }
  47. Singer(const std::string & st, long ID, int vo = other):Worker(st, ID), voice(vo){ }//vo的默认值写0或other都可以,最好写other
  48. Singer(const Worker & wk, int vo = 0):Worker(wk), voice(vo){ }
  49. void Set();
  50. void Show() const;
  51. };
  52. #endif
  53. //Worker.cpp
  54. #include <string>
  55. #include <iostream>
  56. #include "Worker.h"
  57. using std::cin;
  58. using std::cout;
  59. using std::string;
  60. using std::endl;
  61. //Worker methods
  62. //虚析构函数必须实现,即使是纯虚析构
  63. Worker::~Worker(){ }
  64. void Worker::Set()//定义不写virtual关键字
  65. {
  66. cout << "Enter worker's full name:\n";
  67. getline(cin, fullname);//iostream类的函数
  68. cout << "Enter worker's ID:\n";
  69. cin >> id;
  70. //清空输入缓冲
  71. while (cin.get() != '\n')
  72. ;
  73. }
  74. void Worker::Show() const
  75. {
  76. cout << "Worker Name: " << fullname << endl;
  77. cout << "Worker ID: " << id << endl;
  78. }
  79. //Waiter methods
  80. void Waiter::Set()
  81. {
  82. cout << "Worker Category: Waiter\n";
  83. Worker::Set();
  84. cout << "Enter waiter's panache rating:\n";
  85. cin >> panache;
  86. while (cin.get() != '\n')
  87. ;
  88. cout << endl;
  89. }
  90. void Waiter::Show() const
  91. {
  92. /* *错误代码:派生类不可以直接访问基类的私有成员,只可以用基类的公有方法去访问 *cout << "Waiter Name: " << fullname << endl; *cout << "Waiter ID: " << id << endl; */
  93. cout << "Worker Category: Waiter\n";
  94. Worker::Show();
  95. cout << "Panache rating: " << panache << endl;
  96. cout << endl;
  97. }
  98. //Singer methods
  99. //先给静态char指针数组赋值,虽然不是函数,但也可以写在实现文件中
  100. const char * Singer::pv[] = { "other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};//一定要用类名限定!!!像方法一样
  101. void Singer::Set()
  102. {
  103. cout << "Worker Category: Singer\n";
  104. Worker::Set();
  105. cout << "Enter singer's voice type (integer 0 - 6):\n";
  106. //展示给用户看所有可供选择的类型
  107. int i;
  108. for (i = 0; i < Vtypes; ++i)
  109. {
  110. cout << i << ": " << pv[i] << " ";
  111. }
  112. cout << endl;
  113. //保证输入正确
  114. while (cin >> voice && (voice >= Vtypes || voice < 0))
  115. cout << "Please enter a value >= 0 and < " << Vtypes << endl;
  116. //清空输入队列
  117. while (cin.get() != '\n')
  118. ;
  119. cout << endl;
  120. }
  121. void Singer::Show() const
  122. {
  123. cout << "Worker Category: Singer\n";
  124. Worker::Show();
  125. cout << "Singer's voice type: " << pv[voice] << endl;
  126. cout << endl;
  127. }
  128. #include <iostream>
  129. #include "Worker.h"
  130. const int LIM = 4;
  131. using std::cout;
  132. //使用一个多态指针数组调用同名方法
  133. int main()
  134. {
  135. Waiter bob("Bob Apple", 123L, 4);
  136. Singer mary("Mary Smith", 156L, 2);
  137. Waiter anna;
  138. Singer amy;
  139. Worker *pw[LIM] = { &bob, &mary, &anna, &amy};//抽象基类的指针数组,bob和mary只是把他们的Worker组件(基类对象)的地址赋给了指针
  140. int i;
  141. for (i = 2; i < LIM; ++i)
  142. pw[i]->Set();
  143. for (i = 0; i < LIM; ++i)
  144. pw[i]->Show();
  145. return 0;
  146. }

输出

  1. Worker Category: Waiter
  2. Enter worker's full name: Anna Drop Enter worker's ID:
  3. 456
  4. Enter waiter's panache rating: 4 Worker Category: Singer Enter worker's full name:
  5. Sia Taylor
  6. Enter worker's ID: 1268 Enter singer's voice type (integer 0 - 6):
  7. 0: other 1: alto 2: contralto 3: soprano 4: bass 5: baritone 6: tenor
  8. 3
  9. Worker Category: Waiter
  10. Worker Name: Bob Apple
  11. Worker ID: 123
  12. Panache rating: 4
  13. Worker Category: Singer
  14. Worker Name: Mary Smith
  15. Worker ID: 156
  16. Singer's voice type: contralto Worker Category: Waiter Worker Name: Anna Drop Worker ID: 456 Panache rating: 4 Worker Category: Singer Worker Name: Sia Taylor Worker ID: 1268 Singer's voice type: soprano

出现的问题

  • 在派生类中企图使用基类的私有变量。。。必须要用基类公有方法才可以访问到,除非是保护成员变量
  • Singer(const std::string & st, long ID, int vo = other):Worker(st, ID), voice(vo){}中的, voice(vo)忘记写,导致了Singer mary(“Mary Smith”, 156L, 2);没有成功构造出想要的对象,但是这种情况不会在编译时发现,只会在运行时出错,刚开始还不知道为啥,调试发现voice的值打印不出来才想到这边出问题了。
  • 忘记写const char * Singer::pv[] = {“other”, “alto”, “contralto”, “soprano”, “bass”, “baritone”, “tenor”};中的类名限定符,在方法定义文件中定义静态变量,必须和方法一样,用类名限定起来,不然编译器不知道pv到底是哪个类的,尽管头文件包含此信息,但是编译器就是要你写清楚。

做的好的地方

  • const char * Singer::pv[] = {“other”, “alto”, “contralto”, “soprano”, “bass”, “baritone”, “tenor”};中书上代码没有const,于是报7个警告(一个词语一个),说C++禁止把string constant转换为char *类型,我就明白了,这属于const赋给非const的情况,当然会报错,于是这么改了就好了(头文件中也要加const:static const char *pv[Vtypes];)。
    在这里插入图片描述

学到或加深印象的地方

  • 基类指针数组实现多态,其中基类甚至可以是抽象基类。
  • 用enum枚举定义整型常量
  • 指针数组
  • 静态数据成员可以在方法文件中赋值
  • 基类的析构函数必须是虚函数,且虚析构函数必须要实现,即使是纯虚析构函数!
  • 检查输入是否正确(voice的输入必须是0-6)

由SingingWaiter类的问题引出虚基类

SingingWaiter类的问题:将继承两个Worker对象,使多态调用方法时因二义性而不可行

这里并不是真的说有Worker对象,因为毕竟抽象基类是不可以创建对象的,这么说仅仅是为了便于表达,不过还是说组件吧更严谨。

  1. class SingingWorker:public Singer, public Waiter//必须每个基类都用public修饰,因为默认的是private
  2. { ...};

由于 Waiter类和Singer类都继承了一个Worker组件,所以SingingWaiter有两个Worker组件,但是其实只需要一个Worker组件就好了,因为Singer类和Waiter类的Worker组件都一样又没区别。另外,这会在多态调用时造成二义性,使得程序出错:

  1. SingingWaiter ed;
  2. Worker *pw = &ed;//ed包含两个Worker组件,即有两个地址,于是编译器不知道要让pw指向谁

在这里插入图片描述

解决这种二义性要依靠类型转换:

  1. Worker * pw1 = (Waiter *) &ed;//指向Waiter中的Worker组件
  2. Worker * pw2 = (Singer *) &ed;

所以这种多重继承使得利用基类指针引用不同类的对象的多态性变复杂了。要多加一个类型转换才行。

但是光做个类型转换确实解决了这里的二义性问题,但只是解决了皮毛,并不釜底抽薪之举,因为如前所述,根本不需要两个Worker组件啊,再多的Worker组件都是一毛一样的,我们只需要一个!SingingWaiter类的对象应该只拥有一个Worker组件,即只有一个名字和一个ID。

这该怎么办呢?

于是C++迫不得已,专门为多重继承引入一个新技术——虚基类(virtual base class)。

虚基类

虚基类专门针对上面说的这种情况:让两个继承同一个基类(第一代类)的类(第二代类)共同派生的类(第三代类)的对象只有一个基类(第一代类)对象。

  1. class Singer:virtual public Worker{ ...};
  2. class Waiter:public virtual Worker{ ...};
  3. class SingingWaiter: public Singer, public Waiter{ ...};//则SingingWaiter对象只包含一个Worker组件的一个副本,即Singer类和Waiter类实际上共享了同一个Worker组件,而不是各自都引入一个Worker对象副本

public 和 virtual的顺序无所谓

在这里插入图片描述

需要注意的几点:

  • 虚基类中的”虚”和虚函数的“虚”不是一回事,没有关系。由于C++用户墙裂反对再引入新的关键字(已经够多了),所以就用了virtual。这实际上是一种关键字重载。
  • 有时候可能真的需要基类对象的多个副本,于是就不需要使用虚基类。这种情况虽然少,也是有的。

虚基类的构造函数在派生链条中不可以自动传递信息

以前的非虚基类:

  1. class A
  2. {
  3. private:
  4. int a;
  5. public:
  6. A(int m = 0):a(m){ }
  7. };
  8. class B: public A
  9. {
  10. private:
  11. int b;
  12. public:
  13. B(int m = 0, int n = 0):A(m), b(n){ }
  14. };
  15. class C:public B
  16. {
  17. private:
  18. int c;
  19. public:
  20. C(int m = 0, n = 0, q = 0):B(m, n), c(q){ }
  21. };

可以看到,非虚基类中, B继承了A, C继承了B,在这个继承链条中,C只可以调用B的构造函数,不可以调用A的构造函数,因为B会自动调用A的构造函数。其中C把信息传给了B,B又把信息传给了A,相当于B是中间类,C通过中间了自动地把信息传给了基类A。

但是在虚基类中,这种在派生链条中通过中间类自动传递信息将不再行得通:

  1. SingingWaiter(const Worker & wk, int pa = 0, int vo = Singer::other):Waiter(wk, pa), Singer(wk, vo){ };

这句代码中,如果可以通过中间类自动传递信息,则Waiter和Singer的构造函数都会自动调用Worker类的构造函数,所以有2条不同路径都会把wk传给Worker基类,会造成冲突。

所以C++就规定:当基类是虚的时,禁止信息通过中间类自动传递给基类。所以上面的代码只会初始化panache和voice两个参数,那基类对象的那个组件怎么办呢?答案是:编译器调用Worker的默认构造函数去创建。
那如果不想用默认构造函数呢?则:

  1. SingingWaiter(const Worker & wk, int pa = 0, int vo = Singer::other):Worker(wk), Waiter(wk, pa), Singer(wk, vo){ };

注意这里相当于在第三代类的构造函数中使用了第一代类的构造函数,这在非虚基类是绝对不可以的!(非虚基类只允许在构造函数中调用派生链条中相邻的基类的构造函数)。

用虚基类写SingingWaiter类

前面三个类的代码也做了一些改动

  1. //Worker.h
  2. #ifndef WORKER_H_
  3. #define WORKER_H_
  4. #include <string>
  5. class Worker// an abstract base class,不可以创建抽象基类的对象
  6. {
  7. private:
  8. std::string fullname;//包含
  9. long id;
  10. protected:
  11. virtual void Data() const;
  12. virtual void Get();
  13. public:
  14. Worker():fullname("no one"), id(0L){ }
  15. explicit Worker(const std::string & st):fullname(st), id(0L){ }
  16. explicit Worker(long ID):fullname("no one"), id(ID){ }
  17. Worker(const std::string & st, long ID):fullname(st), id(ID){ }
  18. //纯虚析构函数,基类的析构函数必须是虚的,抽象基类至少有一个纯虚函数,在派生类中抽象基类的纯虚函数自动成为虚函数
  19. virtual ~Worker() = 0;
  20. virtual void Set() = 0;
  21. virtual void Show() const = 0;
  22. };
  23. class Waiter:virtual public Worker // 公有继承
  24. {
  25. private:
  26. int panache;
  27. protected:
  28. void Data() const;
  29. void Get();
  30. public:
  31. //默认参数在构造函数中的好处:一个构造函数相当于两个
  32. explicit Waiter(int pa = 0):Worker(), panache(pa){ }//Worker()是调用基类Worker的默认构造函数,不给参数
  33. explicit Waiter(const std::string & st, int pa = 0):Worker(st), panache(pa){ }
  34. explicit Waiter(long ID, int pa = 0):Worker(ID), panache(pa){ }
  35. //有默认参数就不需要Waiter(const std::string & st, long ID):Worker(st, ID), panache(0){}了
  36. Waiter(const std::string & st, long ID, int pa = 0):Worker(st, ID), panache(pa){ }
  37. Waiter(const Worker & wk, int pa = 0):Worker(wk), panache(pa){ }//虽然抽象基类不可有对象,但是可以在形参这么写,会进行类型转换
  38. ~Waiter(){ }//和不写一样,会自动调用Worker的析构函数
  39. void Set();
  40. void Show() const;
  41. };
  42. class Singer: virtual public Worker
  43. {
  44. protected://把一些整型常量定义为保护数据成员,即对于派生链条上的类而言相当于公有数据成员,对于外界相当于私有成员
  45. enum { other, alto, contralto, soprano, bass, baritone, tenor};
  46. enum { Vtypes = 7};
  47. void Data() const;
  48. void Get();
  49. private:
  50. //静态私有成员是类的所有对象共享的,不属于任何一个对象
  51. static const char *pv[Vtypes];//char指针数组,每个char指针指向一个字符串,表示一个声音类型
  52. int voice;
  53. public:
  54. Singer(int vo = 0):Worker(), voice(vo){ }
  55. Singer(const std::string & st, long ID, int vo = other):Worker(st, ID), voice(vo){ }//vo的默认值写0或other都可以,最好写other
  56. Singer(const Worker & wk, int vo = 0):Worker(wk), voice(vo){ }
  57. void Set();
  58. void Show() const;
  59. };
  60. class SingingWaiter: public Singer, public Waiter
  61. {
  62. protected:
  63. void Data() const;
  64. void Get();
  65. public:
  66. SingingWaiter(){ }
  67. SingingWaiter(const std::string & st, long ID, int vo = Singer::other, int pa = 0)
  68. : Worker(st, ID), Singer(st, ID, vo), Waiter(st, ID, pa){ }
  69. SingingWaiter(const Worker & wk, int vo = Singer::other, int pa = 0)
  70. : Worker(wk), Singer(wk, vo), Waiter(wk, pa){ }
  71. SingingWaiter(const Waiter & wa, int vo = other):Singer(vo), Waiter(wa){ }//必须先写Singer,否则警告(这个顺序必须按照声明类时的public Waiter, public Singer来)
  72. SingingWaiter(const Singer & si, int pa = 0):Singer(si), Waiter(pa){ }
  73. void Show() const;
  74. void Set();
  75. };
  76. #endif
  77. //Worker.cpp
  78. #include <string>
  79. #include <iostream>
  80. #include "Worker.h"
  81. using std::cin;
  82. using std::cout;
  83. using std::string;
  84. using std::endl;
  85. //Worker methods
  86. //虚析构函数必须实现,即使是纯虚析构
  87. Worker::~Worker(){ }
  88. void Worker::Data() const
  89. {
  90. cout << "Worker Name: " << fullname << endl;
  91. cout << "Worker ID: " << id << endl;
  92. }
  93. void Worker::Get()
  94. {
  95. cout << "Enter worker's full name:\n";
  96. getline(cin, fullname);//iostream类的函数
  97. cout << "Enter worker's ID:\n";
  98. cin >> id;
  99. //清空输入缓冲
  100. while (cin.get() != '\n')
  101. ;
  102. }
  103. //Waiter methods
  104. void Waiter::Data() const
  105. {
  106. cout << "panache rating: " << panache << endl;
  107. }
  108. void Waiter::Get()
  109. {
  110. cout << "Enter waiter's panache rating:\n";
  111. cin >> panache;
  112. while (cin.get() != '\n')
  113. ;
  114. }
  115. void Waiter::Set()
  116. {
  117. cout << "Worker Category: Waiter\n";
  118. Worker::Get();
  119. Get();
  120. cout << endl;
  121. }
  122. void Waiter::Show() const
  123. {
  124. cout << "Worker Category: Waiter\n";
  125. Worker::Data();
  126. Data();
  127. cout << endl;
  128. }
  129. //Singer methods
  130. //先给静态char指针数组赋值,虽然不是函数,但也可以写在实现文件中
  131. const char * Singer::pv[] = { "other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};//一定要用类名限定!!!像方法一样
  132. void Singer::Data() const
  133. {
  134. cout << "Singer's voice type: " << pv[voice] << endl;
  135. }
  136. void Singer::Get()
  137. {
  138. cout << "Enter singer's voice type (integer 0 - 6):\n";
  139. //展示给用户看所有可供选择的类型
  140. int i;
  141. for (i = 0; i < Vtypes; ++i)
  142. {
  143. cout << i << ": " << pv[i] << " ";
  144. }
  145. cout << endl;
  146. //保证输入正确
  147. while (cin >> voice && (voice >= Vtypes || voice < 0))
  148. cout << "Please enter a value >= 0 and < " << Vtypes << endl;
  149. //清空输入队列
  150. while (cin.get() != '\n')
  151. ;
  152. }
  153. void Singer::Set()
  154. {
  155. cout << "Worker Category: Singer\n";
  156. Worker::Get();
  157. Get();
  158. cout << endl;
  159. }
  160. void Singer::Show() const
  161. {
  162. cout << "Worker Category: Singer\n";
  163. Worker::Data();
  164. Data();
  165. cout << endl;
  166. }
  167. //SingingWaiter methods
  168. void SingingWaiter::Data() const
  169. {
  170. Singer::Data();
  171. Waiter::Data();
  172. }
  173. void SingingWaiter::Get()
  174. {
  175. Singer::Get();
  176. Waiter::Get();
  177. }
  178. void SingingWaiter::Set()
  179. {
  180. cout << "Worker Category: Singing Waiter\n";
  181. Worker::Get();
  182. Get();
  183. }
  184. void SingingWaiter::Show() const
  185. {
  186. cout << "Worker Category: Singing Waiter\n";
  187. Worker::Data();
  188. Data();
  189. }
  190. #include <iostream>
  191. #include <cstring>
  192. #include "Worker.h"
  193. const int SIZE = 5;
  194. using std::cout;
  195. using std::cin;
  196. //使用一个多态指针数组调用同名方法
  197. int main()
  198. {
  199. SingingWaiter amy("Amy Smith", 152L, 2, 3);
  200. amy.Waiter::Show();//不用类名限定会导致二义性,因为SingingWaiter的两个基类Singer类和Waiter类都有Show()方法,自己又没定义Show()方法
  201. amy.Singer::Show();
  202. Worker *pw[SIZE];
  203. int ct;
  204. for (ct = 0;ct < SIZE; ++ct)
  205. {
  206. char choice;
  207. cout << "Enter the employee category:\n";
  208. cout << "w: waiter s: singer\n"
  209. << "t: singing waiter q: quit\n";
  210. while (cin >> choice && (std::strchr("wstq", choice) == NULL))
  211. {
  212. cout << "Please enter w, s, t, or q: ";
  213. }
  214. if (choice == 'q')
  215. break;
  216. switch(choice)
  217. {
  218. case 'w':
  219. pw[ct] = new Waiter; break;
  220. case 's':
  221. pw[ct] = new Singer; break;
  222. case 't':
  223. pw[ct] = new SingingWaiter; break;
  224. }
  225. cin.get();//这句必不可少!!!cin >> choice把输入队列的字母给了choice,但是仍然还在输入队列中,要用cin.get()才可以读掉,否则后面没法输入名字
  226. pw[ct]->Set();
  227. }
  228. cout << "Here is your staff:\n";
  229. int i;
  230. for (i = 0; i < ct; ++i)
  231. {
  232. pw[i]->Show();
  233. cout << '\n';
  234. }
  235. for (i = 0; i < ct; ++i)
  236. delete pw[i];
  237. return 0;
  238. }

输出

  1. Worker Category: Waiter
  2. Worker Name: Amy Smith
  3. Worker ID: 152
  4. Singer's voice type: contralto panache rating: 3 Worker Category: Singer Worker Name: Amy Smith Worker ID: 152 Singer's voice type: contralto
  5. panache rating: 3
  6. Enter the employee category:
  7. w: waiter s: singer
  8. t: singing waiter q: quit
  9. w
  10. Worker Category: Waiter
  11. Enter worker's full name: Anna Smith Enter worker's ID:
  12. 343
  13. Enter waiter's panache rating: 2 Enter the employee category: w: waiter s: singer t: singing waiter q: quit s Worker Category: Singer Enter worker's full name:
  14. Joy Sue
  15. Enter worker's ID: 4245 Enter singer's voice type (integer 0 - 6):
  16. 0: other 1: alto 2: contralto 3: soprano 4: bass 5: baritone 6: tenor
  17. 5
  18. Enter the employee category:
  19. w: waiter s: singer
  20. t: singing waiter q: quit
  21. t
  22. Worker Category: Singing Waiter
  23. Enter worker's full name: Mary Hellon Enter worker's ID:
  24. 125
  25. Enter singer's voice type (integer 0 - 6): 0: other 1: alto 2: contralto 3: soprano 4: bass 5: baritone 6: tenor 4 Enter waiter's panache rating:
  26. 6
  27. Enter the employee category:
  28. w: waiter s: singer
  29. t: singing waiter q: quit
  30. q
  31. Here is your staff:
  32. Worker Category: Waiter
  33. Worker Name: Anna Smith
  34. Worker ID: 343
  35. panache rating: 2
  36. Worker Category: Singer
  37. Worker Name: Joy Sue
  38. Worker ID: 4245
  39. Singer's voice type: baritone Worker Category: Singing Waiter Worker Name: Mary Hellon Worker ID: 125 Singer's voice type: bass
  40. panache rating: 6

MI的其他复杂问题

混合使用虚基类和非虚基类会导致派生类对象中有几个基类组件

比如类A被作为虚基类派生出类B和C,又被作为非虚基类派生出类D和E,然后类B,C,D,E共同派生了类F。那么F类的对象中有几个A类的组件(对象)呢?

答案是3个。因为B和C由于把A当做虚基类继承,所以F中从BC这里只会得到一个A的对象组件,BC共享这一个组件;而DE是非虚基类继承,则每人都有一个A类对象组件,于是在F类中一共就有三个A类的对象组件。

反正看有几条路径。每个非虚基类都提供一条路径,所有虚基类一起提供一条路径。

一般情况下,这种基类的多个对象组件副本都是棘手的问题,而不是所希望的;只有极少数的特殊编程问题中才需要多个基类对象副本,以后遇到再说。

虚基类略改变了C++解析二义性的方式

怎么改变的呢?之前是只有类名限定,现在是先用优先顺序,优先顺序无法消除二义性才上类名限定

如果是只用非虚基类,那么如果类从不同的类中继承了两个或更多的同名方法或同名数据,那么只需要使用类名限定就可以消除调用这个方法的二义性了。

如果在非虚基类中,其实问题可以更简单一丁点:即有时候不需要类名限定也没有二义性的问题,这是因为虚基类会使得同名方法之间有个优先顺序!只要优先顺序可以消除二义性就不需要类名限定了;有时候(优先顺序一样,消除不了二义性)则还是和非虚基类一样,需要类名限定以消除二义性。

注意只有虚基类才有同名方法之间的优先顺序哦,非虚基类没有这个说法。并且虽然用优先顺序可以消除二义性的时候不需要类名限定,但是这需要自己很清楚优先顺序的判断,别搞错了。

什么优先顺序?一个成员名如何优先于另一个成员名?
答案:派生类的名称优先于直接或间接祖先类(隔代亲,隔一代或多代)中的相同名称。

举个栗子:

  1. class B
  2. {
  3. public:
  4. short q();
  5. };
  6. class C: virtual public B//把B当做虚基类继承,只要这里使用virtual,则基类B成为虚基类
  7. {
  8. public:
  9. long q();
  10. int omg();
  11. };
  12. class D: public C
  13. { };
  14. class E: virtual public B
  15. {
  16. private:
  17. int omg();
  18. };
  19. class F: public D, public E
  20. { };

草画了一个继承图
在这里插入图片描述
F类的q()方法: F类从D,E两条路径都集继承到了名为q的方法,我们要分析优先性。先看D路径,D类没有新方法,其q()方法来自于公有继承C类的公有q()方法;再看E路径,E的q()来自于虚公有继承B类的公有q()方法。看继承图发现,C类的q()优先于B类的q(),所以F类中调用q()方法没有二义性,相当于C::q()。但是如果你真的想用B类的,则用B::q()即可。

F类的omg()方法:F类从D,E两条路径都集继承到了名为omg的方法,我们要分析优先性。先看D路径,D类没有新方法,其omg()方法来自于公有继承C类的公有omg()方法;再看E路径,E的omg()是自己的私有方法。看继承图发现,C类和E类不是派生和被派生的关系,所以他们的同名方法没有优先顺序一说,所以F类中调用omg()方法有二义性,虽然不可以在F类中使用E的私有方法,但使用omg()还是要必须明确指出C::omg()。

总之,使用虚基类会增加一点复杂性,多判断一个优先顺序,主要是多重继承中的最终派生类会通过多条途径继承同一个基类引起的。总之,使用MI要慎之又慎,一定要注意类名限定和虚基类的优先规则。

发表评论

表情:
评论列表 (有 0 条评论,22人围观)

还没有评论,来说两句吧...

相关阅读