(C++)继承、菱形继承和虚继承的那些事儿

来源:互联网 发布:win10系统安装mac os 编辑:程序博客网 时间:2024/05/17 08:18

什么是继承?

继承是C++语言的一种重要机制,该机制自动地为一个类提供来自另一个类的操作和数据结构,这使得程序员只需在新类中定义已有类中没有的成分来建立新类。
这里写图片描述
* 继承使得我们得以用一种简单的方式来描述事物
* 面向对象程序设计可以让你声明一个新类作为另一个类的派生。
* 派生类/子类继承它父类的属性和操作。
* 子类同时也声明了新的属性和新的操作,剔除了那些不适合于其用途的继承下来的操作。

继承的工作方式

这里写图片描述

* 实现一个简单的继承

class Person{public:    void Display()    {        cout << "LuLaLaLuLaLa" << endl;    }protected:    string _name;};class Student:public Person{protected:    int _num;};

代码实现如图:

这里写图片描述

事实上,继承的内存布局如下所示:

Alt text

继承与转换

  • 子类对象可以赋值给父类对象(切割/切片)。
  • 父类对象不能赋值给子类对象。
  • 父类的指针/引用可以指向子类对象。
  • 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)。

派生类的构造

在上述简单实现继承的代码中,可以看出我们并没有声明派生类Student的构造函数,根据类的实现机制,派生类对象创建时,将执行其默认的构造函数。
该默认构造函数首先会调用基类的默认构造函数,而如果基类没有默认构造函数的话,但正好匹配默认参数的构造函数,则会调用该构造函数。

  • 派生类可以直接访问基类的保护数据成员,甚至在构造时初始化他们,但一般并不这样做。
  • 派生类会通过基类的接口(成员函数)去访问他们,初始化也是通过基类的构造函数。
  • 这样的好处有:一旦基类的实现有错误,只要不涉及接口,那么基类的修改不会影响派生类的操作。
  • 类与类之间,你做你的,我做我的,以接口作沟通。

单继承 & 多继承

  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承

    这里写图片描述

  • 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

    这里写图片描述

菱形继承

这里写图片描述

  • 菱形继承内存布局示意图如下所示:

这里写图片描述

  • 由上图可知,在Assistant中有两份Person成员,故产生了二义性的问题。
  • 同时,二义性也称数据冗余。
  • 为了解决二义性和数据冗余的问题,C++引入了虚继承。

    虚继承

    在被继承的类前加一个virtual关键字即可。
    代码示例:

class Person{public:    Person()    {        cout << "Person()" << endl;    }    ~Person()    {        cout << "~Person()" << endl;    }public:    string _name;};class Student :virtual public Person{public:    Student()    {        cout << "Student()" << endl;    }    ~Student()    {        cout << "~Student()" << endl;    }public:    int _id;};class Teacher : virtual public Person{public:    Teacher()    {        cout << "Teacher()" << endl;    }    ~Teacher()    {        cout << "~Teacher()" << endl;    }public:    int _num;};class Assistant : public Student, public Teacher{public:    Assistant()    {        cout << "Assistant()" << endl;    }    ~Assistant()    {        cout << "~Assistant()" << endl;    }protected:    string _ClassName;};void Test(){    Assistant a;    a._name = "Evey";}

运行结果如下所示:

这里写图片描述

值得注意的是,若没有使用虚继承,还可以制定类进行调用,示例如下:

void Test(){    Assistant a;    a.Student::_name = "Evey";}

运行结果如下所示:

这里写图片描述

观察以上两种方法各自的运行结果图可知,虚继承的效率更高,能更好的解决数据冗余的问题。

综上所述,在解决菱形继承的二义性和数据冗余性问题时,虚继承是最优的解决方法。