天天看点

第53课 被遗弃的多重继承(上)

问题:

  C++中是否允许一个类继承自多个父类呢?答案是肯定的,这种现象就是多重继承。

  多重继承是C++中一个特有的特性,因为在其他的程序设计语言里面,如C#、java等语言只支持单重继承

  • C++支持编写多重继承的代码
    • 一个子类可以拥有多个父类
    • 子类拥有所有父类的成员变量 
    • 子类继承所有父类的成员函数
    • 子类对象可以当作任意父类对象使用---赋值兼容也适用于此

多重继承的语法规则:

第53课 被遗弃的多重继承(上)

             多重继承的本质与单继承相同!!!

实验:多重继承问题一

1 #include<iostream>
  2 #include<string>
  3 
  4 //多重继承带来的问题:(1)多重继承得到的对象可以有“不同的地址”
  5 //                 (2)多重继承可能产生多余的成员
  6 
  7 using namespace std;
  8 
  9 class BaseA
 10 {
 11     int ma;
 12 
 13 public:
 14     BaseA(int a) //构造函数
 15     {
 16         ma = a;
 17     }
 18     int getA()  //成员函数
 19     {
 20         return ma;
 21     }
 22 };
 23 
 24 class BaseB
 25 {
 26     int mb;
 27 
 28 public:
 29     BaseB(int b)
 30     {
 31         mb = b;
 32     }
 33     int getB()
 34     {
 35         return mb;
 36     }
 37 };
 38 
 39 class Derived : public BaseA, public BaseB
 40 {
 41     int mc;
 42 
 43 public:
 44 
 45     //单继承------子类的构造函数------初始化列表构造父类的构造函数
 46 
 47     Derived(int a, int b, int c) : BaseA(a), BaseB(b)   //多继承--子类的构造函数--初始化列表同时调用两个父类的构造函数
 48     {
 49         mc = c;
 50     }
 51 
 52     int getC()
 53     {
 54         return mc;
 55     }
 56 
 57     void print()
 58     {
 59         //cout << "ma=" << ma << ","     //子类无法直接访问父类的private私有成员-----通过调用父类成员函数(公有的)访问私有成员变量
 60         //       << "mb=" << mb <<","
 61         //     << "mc=" << mc <<endl;
 62 
 63 
 64          //调用父类成员函数--------访问成员变量
 65         cout << "ma=" << getA() << ","  
 66               << "mb=" << getB() << ","
 67               << "mc" << mc << endl;
 68     }
 69 };
 70 
 71 int main()
 72 {
 73     cout << "sizeof(Derived)= " << sizeof(Derived) << endl;  // 3*4=12  子类内存空间大小:父类与子类数据成员的叠加ma mb mc
 74                                                              //增加子类成员函数后,内存空间大小还是12
 75                                                              //类里面成员函数和成员变量是分开存放的,
 76                                                              //每个类有自己的成员变量,都有类对象共享类的成员函数
 77     
 78     //继承构造函数调用顺序:先父母(BaseA(a), BaseB(b) ),再客人(成员变量是其他类的对象--调用成员对象的构造函数),最后自己mc = c;
 79     Derived d(1, 2, 3);       
 80 
 81     d.print();  //ma =1 ,mb =2 ,mc=3
 82 
 83     //继承两个父类成员函数
 84     cout << "d.getA()=" << d.getA() << endl;  //d.getA()=1
 85     cout << "d.getB()=" << d.getB() << endl;  //d.getB()=2
 86     cout << "d.getC()=" << d.getC() << endl;  //d.getC()=3
 87 
 88     cout << endl;
 89 
 90     //赋值兼容---父类指针pa pb同时指向子类对象d(合法)
 91     BaseA* pa = &d;   
 92     BaseB* pb = &d;
 93 
 94     cout << "pa->getA()=" << pa->getA() << endl;    //pa->getA()=1
 95     cout << "pb->getB()=" << pb->getB() << endl;    //pb->getB()=2
 96     cout << endl;
 97 
 98 
 99     //新定义指针paa pbb同时指向相同对象d
100     void* paa = pa;  
101     void* pbb = pb;
102 
103     if (paa == pbb)       //指针保存的地址值,
104     {
105         cout << "pointer to the same object! " << endl;
106     }
107     else
108     {
109         cout << "error" << endl;   //error
110     }
111          
112     cout << "pa= " << pa << endl;     //pa=0xbfe7e304
113     cout << "pb= " << pb << endl;     //pb=0xbfe7e308
114     cout << "paa= " << paa << endl;     //paa=0xbfe7e304
115     cout << "pbb= " << pbb << endl;     //pbb=0xbfe7e308
116 
117     //父类指针指向子类变量  pa,pb保存的地址值为什么不同??????整个期间也没有改变指针值??
118     //答:多重继承得到的对象可以有“不同的地址”
119 
120 
121     return 0;
122 }      

分析多重继承:

多重继承的问题一:

第53课 被遗弃的多重继承(上)

pa、pb指针还是指向一个对象,只是指向的位置不同----pa指向头部,pb可能指向尾部------不方便程序开发

实际开发中,判断两个指针是不是指向同一个对象,是根据比较两个指针保存的地址值判断。

现在是两个指针保持的地址值不同,但是确实是指向同一个对象--------没有方案处理这个问题

 多重继承的问题二:

第53课 被遗弃的多重继承(上)

现实生活中一个人可能扮演两个角色-------(老师,学生)

一个Doctor继承Teacher和Student (Teacher和Student分别拥有m_name、m_age),那么此时出现Doctor有两个m_name、m_age---多余成员产生

实验:代码描述上面类图

1 #include<iostream>
 2 #include<string>
 3 
 4 using namespace std;
 5 
 6 class people 
 7 {
 8     string m_name;
 9     int m_age;
10 public:
11     people(string name, int age) 
12     {
13         m_name = name;
14         m_age = age;
15     }
16     void print()
17     {
18         cout << "name= " << m_name << ","
19              << "age= " <<  m_age  << endl;
20     }
21 };
22 
23 class teacher : public people
24 {
25 public:
26     teacher(string name, int age) : people(name, age)  //调用父类构造函数
27     {
28     }
29 };
30 
31 class student : public people           
32 {
33 public:
34     student(string name, int age) : people(name, age)
35     {
36     }
37 };
38 
39 
40 //多重继承---------带来的数据冗余
41 class doctor : public student, public teacher
42 {
43 public:
44     //doctor(string name, int age) : student(name, age), teacher(name, age)
45     doctor(string name, int age) : student(name+"1", age+1), teacher(name+"2", age+2)
46     {
47     }
48 };
49 
50 int main()
51 {
52     doctor d("mimi", 26);
53 
54     //d.print();      //由于doctor继承了两个类student和teacher,所以对应doctor有两个print函数,编译器不知道调用哪个
55                       //使用类作用域分辨符调用print();手动调用
56     
57 
58     //多重继承---------带来的数据冗余
59     d.teacher::print();   // name =mimi,age =26
60     d.student::print();   // name =mimi,age =26
61 
62     //修改后
63     d.teacher::print();   // name =mimi1,age =27
64     d.student::print();   // name =mimi1,age =27
65 
66     return 0;
67 }       
第53课 被遗弃的多重继承(上)

 中间父类加Virtual关键字:

  • 虚继承能够解决数据冗余问题
  • 中间层父类不再关心顶层父类的初始化------顶层父类的初始化由子类完成
  • 最终子类必须直接调用顶层父类的构造函数

 虚继承发生----Teacher和Student都不再调用父类People的构造函数,而是让Doctor调用

程序说明:

1 #include<iostream>
 2 #include<string>
 3 
 4 //解决多重继承带来的数据冗余--------->>虚继承--------中间父类加virtual关键字
 5 using namespace std;
 6 
 7 class people
 8 {
 9     string m_name;
10     int m_age;
11 public:
12     people(string name, int age)
13     {
14         m_name = name;
15         m_age = age;
16     }
17     void print()
18     {
19         cout << "name= " << m_name << ","
20              << "age= "  << m_age  << endl;
21     }
22 };
23 
24 class teacher : virtual public people
25 {
26 public:
27     teacher(string name, int age) : people(name, age)  //调用父类构造函数
28     {
29     }
30 };
31 
32 class student :virtual public people
33 {
34 public:
35     student(string name, int age) : people(name, age)
36     {
37     }
38 };
39 
40 class doctor : public student, public teacher            
41 {
42 public:      
43     doctor(string name, int age) : student(name, age), teacher(name, age) ,people(name,age)  //项目工程大的时候找顶层父类是很困难的
44     {
45     }
46 };
47 
48 int main()
49 {
50     doctor d("mimi", 26);
51 
52     d.print();           // name =mimi,age =26  技术上解决多重继承带来的数据冗余,但是项目工程大的时候找顶层父类是很困难的
53 
54 
55     //d.teacher::print();  // name =mimi,age =26
56     //d.student::print();  // name =mimi,age =26
57 
58     return 0;
59 }      
技术上解决多重继承带来的数据冗余,但是项目工程大的时候找顶层父类是很困难的-------增加了项目开发时间,代码复杂性增加      

     虚继承在技术方面确实解决了数据冗余问题,但是它使程序变得过于复杂,不利于工程方面的应用。

  如果在项目中引进多重继承,当架构设计中需要继承时,无法确定使用直接继承还是虚继承。

  注意:多继承只适合做学术研究,不适合做工程开发

小结:

  • C++支持多重继承的编程方式
  • 多重继承容易带来问题
    • 可能出现 “同一个对象的地址不同” 的情况 。
    • 虚继承可以解决数据冗余问题
    • 虚继承使得架构设计可能出现问题