类的继承

来源:互联网 发布:数据资产管理 编辑:程序博客网 时间:2024/06/06 10:38

首先让我来先介绍一下继承的概念:
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。简而言之就是可以讲已有的类进行一定功能的延续。被继承的类称之为基类(父类),继承了功能的类叫做派生类(子类),在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
继承的定义格式及方式:
class 派生类的名字:继承方式 基类的名字(继承的基类的数目不定,每一个基类的继承都要加上继承方式)
继承方式有3种,分别为公有继承,保护继承,私有继承,根据继承方式的不同,基类成员在派生类中的访问情况也是不同的:
这里写图片描述
总结:
1. 基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3. protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,
是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函
数时它就是合理的。
4. 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
5. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
6. 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.
7.可以用子类为基类赋值,但是不能用基类给子类赋值,同样的可以用基类指针或者引用指向派生类,但是不能用派生类的指针或者引用指向基类。(赋值兼容规则)
这里写图片描述
继承关系中构造函数的调用:
实际上,函数在调用时,依旧先是进到派生类的构造函数,在派生类的构造函数调用时,由于要对初始化列表进行初始化,而继承前一部分是继承自子类的对象,后一部分才是派生类自己真正创建的对象,因此在运行结果中先调用了基类的构造函数,而后才调用了派生类的构造函数;在析构时,由于先创建的后被销毁,后创建的先被销毁,这也和对象实际存在的生命有密切关系。

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>#include <stdlib.h>using namespace std;class Base{public:    Base()    {        cout << "Base()" << endl;    }    ~Base()    {        cout << "~Base()" << endl;    }};class Derived :public Base{public:    Derived()    {        cout << "Derived()" << endl;    }    ~Derived()    {        cout << "~Derived()" << endl;    }};class DDerived:public Derived{public:    DDerived()    {        cout <<"DDerived()" << endl;    }    ~DDerived()    {        cout << "~DDerived()" << endl;    }};void Funtest(){    Base b;//基类    Derived d;//派生类    DDerived dd;//以Derived为基类的派生类}int main(){    Funtest();    system("pause");    return 0;}

调用的先后顺序为:
这里写图片描述
从运行的结果我们可以看出,调用构造函数时是从基类开始,一层一层向下调用构造函数,析构函数的次序刚好就与之相反。这是单继承的情况,如果是多继承,则按照对继承基类的顺序调用构造函数。
【说明】
1、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
2、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
3、基类定义了带有形参表构造函数,派生类就一定定义构造函数。
友元与继承
友元函数是不能继承的 哦!也就是说基类友元不能访问子类私有和保护成员。
这里写图片描述

继承体系中的作用域
1. 在继承体系中基类和派生类是两个不同作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以
使用 基类::基类成员 访问)–隐藏 –重定义
3. 注意在实际中在继承体系里面最好不要定义同名的成员。

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>#include <stdlib.h>using namespace std;class Base{public:    void fun()    {        cout << "Base" << endl;    }private:    int pri;};class Derived :public Base{public:    void fun()    {        cout << "Derived" << endl;    }private:    int ppri;};void Funtest(){    Base b;//基类    Derived d;//派生类    d.fun();    d.Base::fun();}int main(){    Funtest();    system("pause");    return 0;}

这里写图片描述
单继承(箭头表示继承方向)
一个子类只有一个直接父类时称这个继承关系为单继承。
这里写图片描述
这里写图片描述
多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承
这里写图片描述
这里写图片描述
菱形继承
这里写图片描述
这里写图片描述
从基类的成员分布可以看到,其中基类1和2继承的最基类成员都出现在派生类的成员中,那么当我们赋值给最基类成员时,便会由于不知道访问谁而出错
虚继承–解决菱形继承的二义性和数据冗余的问题
1. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
2. 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的损耗。
3.当我们在菱形继承的中间一环添加虚继承则可以解决这个问题菱形继承二义性和数据冗余的问题。

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>#include <stdlib.h>using namespace std;class Base{public:    Base()    {        cout << "Base()" << endl;    }    ~Base()    {        cout << "~Base()" << endl;    }public:    int pri;};class Base1:virtual public Base{public:    Base1()    {        cout << "Base1()" << endl;    }    ~Base1()    {        cout << "~Base1()" << endl;    }public:    int pri1;};class Base2:virtual public Base{public:    Base2()    {        cout << "Base2()" << endl;    }    ~Base2()    {        cout << "~Base2()" << endl;    }public:    int pri2;};class Derived :public Base1,public Base2{public:    Derived()    {        cout << "Derived()" << endl;    }    ~Derived()    {        cout << "~Derived()" << endl;    }public:    int ppri;};void Funtest(){    //Base1 b1;//基类    //Base2 b2;    Derived d;    d.pri = 1;    d.pri1 = 2;    d.pri2 = 3;    d.ppri = 4;//派生类    cout << sizeof(Derived) << endl;}int main(){    Funtest();    system("pause");    return 0;}

派生类Derived的大小为24个字节,查看运行结果的内存为:

这里写图片描述
前四个字节看起来似乎还是一个指针,接下来是基类1的成员数据,再下来又是一个指针,接着是基类2成员数据,派生类的成员数据,最后是最基类的成员数据。让我们来查看一下第一个指针在内存中所存储的东西:
这里写图片描述
这样我们画图来解释一下虚继承中的继承关系:
这里写图片描述

0 0