博主首页: 有趣的中国人
专栏首页: C++进阶
本篇文章主要讲解 菱形继承 的相关内容
目录
1. 继承与友元
2. 继承与静态成员
3. 复杂的菱形继承及菱形虚拟继承
3.1 继承分类
3.2 菱形继承导致的问题
3.3 虚拟继承解决数据冗余的原理
4. 继承和组合的区别
1. 继承与友元
友元关系不能继承,也就是说,基类的友元函数不能访问子类的保护和私有成员。
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name = "Jason"; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
// 此处不能访问
//cout << s._stuNum << endl;
}
void main()
{
Person p;
Student s;
Display(p, s);
}
2. 继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例
我们可以利用这个特性计算构造出了多少个继承的对象,只要在构造函数中每次将静态成员变量自增1即可。
class Person
{
public:
Person() { ++_count; }
protected:
string _name; // 姓名
public:
static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
string _seminarCourse; // 研究科目
};
void TestPerson()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout << " 人数 :" << Person::_count << endl;
Student::_count = 0;
cout << " 人数 :" << Person::_count << endl;
}
int main()
{
TestPerson();
return 0;
}
3. 复杂的菱形继承及菱形虚拟继承
3.1 继承分类
单继承
一个子类只有一个直接父类叫做做单继承。
多继承
一个子类有两个即以上的直接父类叫做多继承。
菱形继承
菱形继承是特殊的多继承。
3.2 菱形继承导致的问题
菱形继承可能导致两个问题:
1. 在上面的菱形继承中,假设Person中有一个成员变量_name,那么在Student和Teacher中也会有一个成员变量_name,然而Assistant继承了Student和Teacher,在Assistant中就会有两份_name,会导致数据冗余。
2. 在访问Assistant中的_name时,由于不知道要访问的到底是哪个_name,是Student还是Teacher呢?就会有二义性的问题。
class Person
{
public:
string _name;
};
class Student : public Person
{
protected:
int _stuid;
};
class Teacher : public Person
{
protected:
int _teacherid;
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse;
};
void test()
{
Assistant ast;
// 这里会有二义性的问题,不知道访问的是哪一个类中的
ast._name = "a";
// 指定作用域可以解决二义性的问题,但是无法解决数据冗余的问题
ast.Teacher::_name = "xxx";
ast.Student::_name = "yyy";
}
可以用虚拟继承来解决数据冗余的问题,只需要在Student和Teacher的继承Person时使用虚拟继承,加virtual关键字。
class Student : virtual public Person { protected: int _stuid; }; class Teacher : virtual public Person { protected: int _teacherid; };
3.3 虚拟继承解决数据冗余的原理
我们可以用内存窗口调试下面这段代码:
class A
{
public:
int _a;
};
class B : public A
//class B : virtual public A
{
public:
int _b;
};
class C : public A
//class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
如果不加virtual:
很明显_a有两份
如果加了virtual:
那么在B类和C类中,除了存储了继承下来的成员变量,头部存储的是个地址,我们可以看一下:
这个地址存储的含义是这个位置距离共同继承的A的偏移量,虚拟继承之后共同继承的部分就只有一份了,就解决的数据冗余的问题,我们可以用计算器验证以下:
所以我们可以看出虚拟继承在内存中的存储方式:
4. 继承和组合的区别
1. public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
2. 合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
3. 优先使用对象组合,而不是类继承 。
4. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称
为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的
内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很
大的影响。派生类和基类间的依赖关系很强,耦合度高。
5. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复
用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
封装。
6. 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。