1 何为多重继承
C++允许一个类拥有多个父类,这就是多重继承
- 子类拥有所有父类的成员变量
- 子类继承所有父类的成员函数
- 子类对象可以当作任意父类对象使用
多重继承的语法规则:
// 38-1.cpp
#include<iostream>
using namespace std;
class BaseA
{
int ma;
public:
BaseA(int a) { ma = a; }
int getA() { return ma; }
};
class BaseB
{
int mb;
public:
BaseB(int b) { mb = b; }
int getB() { return mb; }
};
class Derived : public BaseA, public BaseB
{
int mc;
public:
Derived(int a, int b, int c) : BaseA(a), BaseB(b)
{
mc = c;
}
int getC() { return mc; }
void print()
{
cout << "ma = " << getA() << ", "
<<"mb = " << getB() << ", "
<< "mc = " << mc << endl;
}
};
int main()
{
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
Derived d(1, 2, 3);
d.print();
cout << "d.getA() = " << d.getA() << endl;
cout << "d.getB() = " << d.getB() << endl;
cout << "d.getC() = " << d.getC() << endl;
cout << endl;
BaseA* pa = &d;
BaseB* pb = &d;
cout << "pa->getA() = " << pa->getA() << endl;
cout << "pb->getB() = " << pb->getB() << endl;
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
return 0;
}
- 类 Derived 继承了类 BaseA 和类 BaseB,子类是由父类成员叠加子类得到的,所以类 Derived 拥有成员变量 ma,mb,mc。计算类对象大小时,计算的是成员变量的大小,成员函数保存在代码段,不计入对象大小。所以类 Derived 大小为 12。
- Derived 对象可以调用继承来的成员函数 getA() ,getB()
- 子类对象可以转换为任意父类对象使用,所以程序第 46-50 行可以转换为两个父类对象,调用父类函数
- 程序第 53-54行打印指向对象 d 的两个指针来说明多重继承的第一个问题
编译运行:
$ g++ 38-1.cpp -o 38-1
$ ./38-1
sizeof(Derived) = 12
ma = 1, mb = 2, mc = 3
d.getA() = 1
d.getB() = 2
d.getC() = 3
pa->getA() = 1
pb->getB() = 2
pa = 0x7ffee3d7e03c
pb = 0x7ffee3d7e040
运行结果和我们分析的一致,但是 pa,pb 都是指向对象 d 的指针,他们的值却不同。这就是第一个问题。
2 多重继承问题一:对象拥有不同地址
- 通过多重继承得到的对象可能拥有“不同的地址”
如下图所示,两个指针指向一个对象,可能指向的位置不同,且目前没有解决方案。
3 多重继承问题二:成员冗余
- 多重继承可能产生冗余的成员
如下所示,Teacher 和 Student 都是 People 的子类,Doctor 是 Teacher 和 Student 的子类。
我们用代码描述上述关系
// 38-2.cpp
#include<iostream>
using namespace std;
class People
{
string m_name;
int m_age;
public:
People(string name, int age)
{
m_name = name;
m_age = age;
}
void print()
{
cout << "Name = " << m_name << ", "
<< "Age = " << m_age << endl;
}
};
class Teacher : public People
{
public:
Teacher(string name, int age) : People(name, age)
{
}
};
class Student : public People
{
public:
Student(string name, int age) : People(name, age)
{
}
};
class Doctor : public Teacher, public Student
{
public:
Doctor(string name, int age) : Teacher(name, age), Student(name, age)
{
}
};
int main()
{
Doctor d("Tom", 29);
//d.print();
d.Teacher::print();
d.Student::print();
return 0;
}
由于 Teacher 和 Student 都是 People 的子类,所以 Teacher 和 Student 都有成员变量 m_name,int m_age,都有成员函数 print()。
Doctor 是 Teacher 和 Student 的子类,所以 Doctor 中有两个 m_name 变量,两个 m_age 变量,两个 print() 函数,我们使用 d.print() 调用时,编译器并不知道我们调用的是哪一个函数,需要用作用于限定符指明哪一个函数才行。
3.1 虚继承解决数据冗余
当多重继承关系出现闭合时产生数据冗余的问题
解决方案:虚继承
- 虚继承能够解决数据冗问题
- 中间层父类不再关心顶层父类的初始化
- 最终子类必须直接调用顶层父类的构造函数
#include <iostream>
#include <string>
using namespace std;
class People
{
string m_name;
int m_age;
public:
People(string name, int age)
{
m_name = name;
m_age = age;
}
void print()
{
cout << "Name = " << m_name << ", "
<< "Age = " << m_age << endl;
}
};
class Teacher : virtual public People // 虚继承
{
public:
Teacher(string name, int age) : People(name, age)
{
}
};
class Student : virtual public People // 虚继承
{
public:
Student(string name, int age) : People(name, age)
{
}
};
class Doctor : public Teacher, public Student
{
public:
// 最终子类必须直接调用顶层父类的构造函数
Doctor(string name, int age) : Teacher(name, age), Student(name, age), People(name, age)
{
}
};
int main()
{
Doctor d("Tom", 33);
d.print();
return 0;
}
使用虚继承,最终子类直接调用顶层父类的构造函数,解决了数据冗余的问题。
$ g++ 38-2.cpp -o 38-2
$ ./38-2
Name = Tom, Age = 29
在架构设计时我们并不知道后面的代码会不会有多继承,所以我们并不知道是否应该使用虚继承,而且,需要直接调用顶层父类的构造函数,查找顶层父类效率低下。现在软件很少使用多重继承了。
4 小结
1、C++支持多重继承的编程方式
2、多重继承的问题
- 可能出现同一个对象地址不同的情况
- 虚继承可以解决数据冗余的问题
- 虚继承使得架构设计可能出现问题