首先回顾一下C++面向对象的三大特性:封装、继承和多态。先解释前两个
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
下面这张图
继承方式有三类:public、protected和private。
访问限定符也有三类:public、protected和private。
派生类继承基类成员访问方式的变化是怎样的呢?
首先,我们可以总结出基类的成员在子类的访问方式:
基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。
从上述代码我们可以得出以下结论:
了解了继承的隐藏后,观察下面这段代码,fun()和fun(int i)是重载还是隐藏?
首先回顾一下C++中的6个默认成员函数,即不写,编译器默认帮我们生成一个
那么,在派生类中是怎样生成的呢?
派生类的默认成员函数必须先调用基类的默认成员函数,再调用派生类的默认成员函数。
但是,派生类的析构函数是一个特例,派生类的析构函数先调用派生类的析构函数再调用基类的析构函数,并且派生类的析构函数会在调用完成后自动调用基类的析构函数清理基类成员。 原因可以从上图看到基类析构函数先入栈,因此顺序和前5个默认成员函数相反!
一个子类有多个父类,且这个子类的多个父类作为子类时,又有共同的一个父类。菱形继承时多继承的特殊情况
来看菱形继承的下面这段代码,我们会发现菱形继承的问题
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";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
从上图不难看出,菱形继承存在的两个问题:
- 数据冗余
- 二义性
如何解决上述的两个问题呢?
C++通过引入虚拟继承来解决上述问题,其操作是在Student类和Teacher类继承Person类时使用虚拟继承,加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";
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
注意:
使用virtual
关键字加虚拟继承的位置是在共同的父类上,即Person类
我们写一个简化版本的代码,透过内存观察虚拟继承的本质,首先看没有虚拟继承
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;
};
void Test()
{
D d;
cout << sizeof(d) << endl;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}
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;
};
void Test()
{
D d;
cout << sizeof(d) << endl;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}
这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
virtual
关键字,使其只有一份公共的虚基类成员。