c++类的特性之继承

来源:互联网 发布:php成绩管理系统 编辑:程序博客网 时间:2024/06/05 07:08

继承:
c++类的三大特性,封装,继承,多态。继承的作用是代码的复用和多态的实现。类的继承,是新的类从已有类那里得到已有的特性,并且有自己的的特性,从已有类产生新类的过程就是类的派生。原有类叫基类或者父类,产生的新类叫派生类或者子类。继承分为:单继承,多继承,菱形继承。
单继承:
这里写图片描述
我们用一个简单的例子来说明:

      class Base{public://构造函数    Base(int a=0,int b=0,int c=0)    : _a(a)    , _b(b)    , _c(c)    {        _a = a;        _b = b;        _c = c;    }//拷贝构造函数    Base(const Base& s)    {        _a = s._a;        _b = s._b;        _c = s._c;    }public:    int _a;protected:    int _b;private:    int _c;};//Deverd公有继承Baseclass Deverd :public Base{public:    Deverd()    {        _a = 10;        _b = 20;        _c = 30;//c在基类是私有的在派生类里面不可访问,所以在这编译的时候就会报错        _d = 30;    }public:    int _d;};

1.派生类的申明:
class 派生类名:继承方式 基类名
{
派生类成员申明;
}

这里写图片描述
2.继承方式:继承方式规定了如何访问基类成员。继承方式有public,protected,private。如果不显示给 出继承方式,默认为private继承。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。继承权限public>protected>private。派生类继承基类之后,权限则会发生降级。

这里写图片描述
这里写图片描述

3.总结:
1). 基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要 在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
2). public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类 对象也都是一个父类对象。
3). protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分, 是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的 都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比 组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函 数时它就是合理的。
4). 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存 在但是在子类中不可见(不能访问)。
5). 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最 好显示的写出继承方式。
6). 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承

4.派生类继承了基类中除了构造和析构函数以外的所有成员。但是根据基类成员访问限定符和继承方式来决定派生类可不可以访问基类成员。

5.派生类的构造函数和析构函数
派生类中由基类继承而来的成员初始化工作还是由基类的构造函数来完成,派生类新增的成员则是在派生类中的构造函数来完成。如上面给的例子,在类外定义同一个派生类变量初始化的时候,先进入派生类的构造函数,在初始化列表初始化基类成员的时候,进入基类,调用基类的构造函数,所以顺序为:Derived()->Base()->Derived(),在调用析构函数的时候也是一样的道理。

6.单继承的对象模型

这里写图片描述

7.继承与转换–赋值兼容规则–public继承 1. 子类对象可以赋值给父类对象(切割/切片) 2. 父类对象不能赋值给子类对象 3. 父类的指针/引用可以指向子类对象 4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成。

8.友元与继承
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员,如下:

class Person{     friend void Display(Person &p , Student&s);   protected :       string _name ; // 姓名 };class Student: public Person {  protected :         int _stuNum ;// 学号 };void Display(Person &p , Student &s) {    //在编译时就会报错  cout<<p._name<<endl;      cout<<s._name<<endl;     cout<<s._stuNum<<endl;}void TestPerson1() {     Person p;        Student s;       Display (p, s); }

9.继承与静态成员
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有 一个static成员实例

10.同名隐藏
在基类,派生类关系中,如果成员函数或者成员对象同名了,在派生类中发生同名隐藏,派生类自动隐藏基类中的同名对象或者同名函数。发生同名隐藏的条件:只和成员名称有关,和类型,参数无关。

class B2{public:    B2(int d =0)    {        cout << "B2()" << endl;        _d = d;    }public:    int _d;//基类有一个_d};class D : public B2{public:    D(int d = 0)    {        cout << "D()" << endl;        _d = d;//在派生类中也有一个_d    }public:    int _d;};void test(){    D d(10);//在这构造一个派生类变量 且赋值位10}int main(){    test();    system("pause");    return 0;}

我么可以再内存中看见
这里写图片描述
基类的d是默认初始值0;而派生类中为10;

多继承:

这里写图片描述

我们也用一段简单的代码来说明:

//基类class B1{public:    int _b1;};//基类class B2{public:    int _b2;};//派生类公有继承B1,B2class D :public B1, public B2{public:    int _d;};

多继承所有的特性和单继承差不多,派生类继承了所有基类除了友元函数,构造函数,析构函数之外的全部成员,多继承的对象模型如下:

这里写图片描述
构造函数调用过程:D()->B1()->B2()-D()

菱形继承:
这里写图片描述

菱形继承又叫钻石继承,另个子类同时继承一个父类,而且又有一个子类同时继承者两个子类,就叫菱形继承。

//一个基类class Base{public:    Base()    {        cout << "Base()" << endl;    }public:    int _b;};//子类c1继承Baseclass C1 : public Base{public:public:    int _c1;};//子类c2也继承Baseclass C2 : public Base{public:public:    int _c2;};//D同时继承C1,C2class D : public C1, public C2{public:    int _d;};

这种继承就是菱形继承,菱形继承的模型:
这里写图片描述

但是D的对象中有两份_b,造成数据冗余,且具有二义性问题,当我们在D中访问_b的时候,访问c1,还是c2中的_b 就有了二义性问题,为了解决这个问题,我们引出了虚拟继承。

虚拟继承:
虚拟继承使得虚基类对于它直接或者间接派生的类来说,拥有一个共同的基类对象实例。避免了带有歧义的组合而产生的问题。其原理是,间接派生类(D)直接穿透起父类(c1,c2),实质上直接继承了虚基类Base.
菱形虚继承为例子:
“`
class Base
{
public:
int _b;
};

class C1 :virtual public Base
{
public:

public:
int _c1;
};

class C2 :virtual public Base
{
public:

public:
int _c2;
};

class D : public C1, public C2
{
public:
int _d;
};
“`
菱形虚继承中,一定是在虚基类派生的c1,c2继承关系前面加关键字 virtual;
菱形虚拟继承的对象模型:
这里写图片描述

在这里我们可以很明显看见在对象模型中,菱形虚拟继承和菱形继承的模型不一样,虚基类的对象存放位置也不一样,而且菱形虚拟继承比菱形继承多了2个存放地址的指针空间,但是虚基类的对象只有一份在D中。虚拟继承中多的那个存放地址是指向偏移量表格的,偏移量表格存放了虚基类对象的偏移量和自己的偏移量。在调用个构造函数的时候,如果是虚拟继承,编译器自动添加了一句 push 1,判断时候把偏移量表格放在对象的前四个字节,也就是判断是否为虚拟地址。如果有virtual关键字 则push1 否则 不调用。

虚继承–解决菱形继承的二义性和数据冗余的问题
1. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
2. 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得 已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的 损耗