1. 继承与友元
友元关系不能继承,基类的友元不能访问子类的私有和保护成员。
2. 继承与静态成员
基类定义static静态成员,则整个继承体系里面只有这样一个成员。无论派生出多少个子类,都有只有一个static成员实例。
class Person
{
public:
Person() { ++_count; }
protected:
string _name; // 姓名
public:
static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum; // 学号
};
int main()
{
Person p;
Student st();
cout << &Person::_count << endl;
cout << &Student::_count << endl;
return 0;
}
3. 多种继承方式
单继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承
多继承:一个子类有两个或者以上的直接父类
菱形继承
菱形继承: 菱形继承是多继承的一种特殊情况
但是我们观察到,菱形继承的继承方式存在数据冗余和二义性的问题。
根据菱形继承,我们实例化一个Assistant对象,访问对象的成员时就会出现实例化的问题。
class Person
{
public :
string _name ; // 姓名
};
class Student : public Person
{
protected :
int _num ; //学号
};
class Teacher : public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a ;
a._name = "peter";
}
Assistant类继承Student和Teacher类,但是Student和Teacher都继承了Person类,因此Student和Teacher中都有_nam成员变量。所以直接访问Assistant中的_name会出现报错。
我们可以使用作用域限定符显示访问Student和Teacher类中的_name。
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
即使是这样数据冗余也没有办法解决,Assistant类中始终都会又两份_name。
4. 菱形虚拟继承
菱形继承的出现解决菱形继承时出现的数据冗余和二义性的问题。对前面所说的Assistant的问题使用虚拟继承就能够解决,在Assistant的两个直接父类使用virtual。
例如:
class Person
{
public :
string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
Assistant a ;
a._name = "peter";
}
菱形虚拟继承解决数据冗余和二义性的原理
运行以下代码观察菱形继承模型中的数据冗余:
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
class C : 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;
}
我们通过内存窗口可以看到,造成数据冗余的原因就是D类对象中有两个_a成员变量。
我们再来看菱形虚拟继承中各个成员在内存中的分布情况。
代码:
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
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;
}
上面图中可以发现D对象中A被放在对象组成最下面,A同时也属于B和C。那么B和C怎样才能找到共有的A呢?
这里是通过B和C的指针,指向一张表。BC中的指针叫虚表指针,指向的表叫虚基表。虚基表中存有偏移量,B和C通过偏移量找到A。
下面是上面的Person关系菱形虚拟继承的原理解释:
5. 菱形继承相关笔试面试题
-
什么是菱形继承?菱形继承的问题是什么?
菱形继承是一种特殊的多继承,两个派生类继承同一个基类,另一个类有同时继承两个派生类。菱形继承的问题在于数据冗余和二义性,就是子类中有两个父类成员。 -
什么是菱形虚拟继承?如何解决数据冗余和二义性的
菱形虚拟继承就是在中间的类使用虚拟继承的方式继承父类。虚拟继承在类中只存储一份基类成员,然后采用虚表指针和虚基表的方式是继承中的其他类可以通过偏移量找到基类成员,从而解决二义性的问题。 -
继承和组合的区别?什么时候用继承?什么时候用组合?
继承的关系就是is-a,而组合是has-a。若是两个类之间的关系为A是B(is-a),则使用继承。若是两个类之间的关系为A包含B(has-a)关系,则使用组合。