继承

来源:互联网 发布:安装的软件 编辑:程序博客网 时间:2024/06/03 14:12

继承

  • 概念:继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。
  • 定义格式:class 派生类(子类)名称 :继承类型 基类(父类)名称
  • 继承关系&访问限定符
    有三种继承方式:公有继承(public)、保护继承(protected)、私有继承(private)
//基类class Base{public:    Base()    {        cout<<"Base()"<<endl;    }    ~Base()    {        cout<<"~Base()"<<endl;    }    void Display()    {        cout<<"_pri = "<<_pri<<endl;        cout<<"_pub = "<<_pub<<endl;        cout<<"_pro = "<<_pro<<endl;    }private:    int _pri;public:    int _pub;protected:    int _pro;};//派生类class Derived:public Base{public:    Derived()    {        cout<<"D()"<<endl;    }    ~Derived()    {        cout<<"~D()"<<endl;    }    void Display()    {        cout<<"_d_pri = "<<_d_pri<<endl;        cout<<"_d_pub = "<<_d_pub<<endl;        cout<<"_d_pro = "<<_d_pro<<endl;    }private:    int _d_pri;public:    int _d_pub;protected:    int _d_pro;};

关系如下图所示
这里写图片描述
小结:
1. 基类的私有成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。(可以看出保护成员限定符是因继承才出现的。)
2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3.protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
4.关键字class时默认的继承方式是private,而struct时默认的继承方式是public
5.不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。

  • 一、继承中构造函数和析构函数的调用顺序
    这里写图片描述
    构造函数调用过程:
    这里写图片描述
    析构函数调用过程:
    这里写图片描述
    说明:
    (1)、基类没有缺省构造函数,派生类必须要在初始化列表中显示给出基类名和参数列表
    (2)、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数
    (3)、基类定义了带有形参构造函数,派生类就一定定义构造函数(在创建派生类对象时必须调用基类构造函数,编译器没有能力自己合成构造函数,所以派生类只能自己给出)
  • 二、继承体系中的作用域
    1.在继承体系中基类和派生类是两个不同的作用域
    2.子类和父类中有同名成员,子类成员将屏蔽父类成员的直接访问(在子类成员中可以使用 基类::基类成员 访问)
    3.注意在实际中在继承体系里面最好不要定义同名函数。

同名隐藏
当派生类中特有的成员和从基类所继承的成员同名(包括成员变量和成员函数)时,在派生类中会隐藏基类中的成员,即使用派生类的对象直接只能访问派生类中的(和基类同名)成员,但是有时候也需要可以访问基类中的成员(同名),这时在派生类的成员函数可以使用形如 : 基类类名::成员名来访问基类中的同名成员。
这里写图片描述

  • 三、继承与转换—赋值兼容规则—public继承
    1.子类对象可以赋值给父类对象(切割\切片)(相当于基类对象向基类赋值)
    2.父类对象不能赋值给子类对象。(基类的对象只是派生类的一部分)
    3.父类的指针\引用可以指向子类对象
    4.子类的指针\引用不能指向父类对象(可以通过强制类型转换)(但强制类型转换不安全,通过这种方法,扩大了父类的内存空间)
  • 四、友元与继承
    友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

  • 五、继承与静态成员
    静态成员变量可以继承,而且整个继承体系中只有一份静态成员变量,是所有类对象所共享的。

  • 六、单继承&多继承&菱形继承
    1.【单继承】:一个子类只有一个直接父类时的这种继承关系为单继承
    这里写图片描述
    2.【多继承】:一个子类有两个或以上直接父类时称这个继承关系为多继承(注:每个基类的继承权限必须要显性给出)
    这里写图片描述
    这里写图片描述
    3【菱形继承】(又叫钻石继承)
    这里写图片描述
    实例:(代码如下)
class B{public:    int _b;};class C1 :public B{public:    int _c1;};class C2 :public B{public:    int _c2;};class D :public C1, public C2{public:    int _d;};void test(){    D d;    d.C1::_b = 0;    d._c1 = 1;    d.C2::_b = 0;    d._c2 = 3;    d._d = 4;}

这里写图片描述
因为在D的对象中有两份B成员,即菱形继承存在二义性和数据冗余的问题,为了解决这个问题,故引入了虚继承。

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

这里写图片描述
代码见下:

class B{public:    int _b;};class C1 :virtual public B{public:    int _c1;};class C2 :virtual public B{public:    int _c2;};class D :public C1, public C2{public:    int _d;};void test(){    D d;    d.C1::_b = 0;    d._c1 = 1;    d.C2::_b = 0;    d._c2 = 3;    d._d = 4;}

这里写图片描述
由上图可知,解决二义性时在vs下使用的是偏移量

虚拟继承与普通继承的区别
1.书写形式—多了虚拟关键字virtual
2.对象模型区别—虚拟多了四个字节,里面存放的是地址,也称之为偏移量表格,第一个是对象相当于自己的偏移量,第二个是对象相当于成员的偏移量
3.普通继承基类在前,派生类在后,而虚拟继承基类部分在最低下
这里写图片描述
4.对于基类成员的访问形式不同—(普通继承:直接访问。虚拟继承:先找偏移量表格地址,在调用相对于基类成员的偏移量,最后访问基类)
5.构造函数不同—(1.派生类,编译器会为之合成构造函数 ,将偏移量表格的地址存放在对象的前四个字节中 2.多一个参数,作用是检测继承是否为虚拟继承)