继承和派生

来源:互联网 发布:窗帘拼布算法 编辑:程序博客网 时间:2024/05/29 15:54

继承、多态和封装是面向对象的三大特性

一、什么是继承?
1.继承概念

  继承:面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行拓展,增加功能。  比如你和你父母就会有很多地方很像,但是你也有自己的特点,你只是继承了父母的一部分特性。

2.继承定义格式(派生类的书写格式):

class 派生类(子类)名称:继承类型 基类(父类)名称继承类型:基类成员在派生类中的可见性继承类型有:public(公有继承)          protected(受保护继承)           private(私有继承)   在这里呢,我们要把类中成员的访问限定符与继承类型分清。publicprotectedprivate作为类成员访问限定符时:   public修饰类成员时,表示此成员是公有的,可以在类外访问   protectedprivate修饰类成员时表示此成员在类外不能进行访问,只能在类内访问

3.基类成员在派生类中的访问属性:
这里写图片描述
由表可知:
(1) 无论是那种继承方式,基类的私有成员在派生类中都不可见,在派生类中都可以访问基类的公有成员和受保护的成员,只是会因为继承方式不同而引起权限发生变化。
(2)在实际应用中都会采取public继承,不太采取protected和private继承
(3)权限变化依次减小:public–>protected–>private,在继承时,权限会因为继承方式的不同而发生改变,会由大权限变为小权限,例如:在继承关系为protected时,public类成员会变为protected成员。
二、派生类
1、派生类是什么?

   继承基类的类就被称为派生类(子类)   派生类也会有六个默认的成员函数:构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载、const修饰的取地址操作符重载

2.在继承体系中基类和派生类的构造函数和析构函数的调用次序

构造函数:   调用派生类的构造函数--->调用基类的构造函数(在派生类构造函数的初始化列表第一条语句位置)--->初始化派生类自己的成员--->执行派生类构造函数的函数体析构函数:   调用派生类的析构函数--->销毁派生类自己管理的资源--->调用基类的析构函数(在派生类析构函数的函数体中的最后一条语句位置)--->销毁基类管理的资源   编译器给派生类合成默认构造函数的情况:     在继承人体系中,如果基类中存在缺省的构造函数,派生类的构造函数没有显式的给出,,编译器会给派生类合成一个默认的构造函数

3.继承体系中的作用域

   1.在继承体系中,基类和派生类是两个不同的作用域,由此也进一步证明 ,基类的私有成员,无论在那种继承体系下都不能在派生类中访问。   2.在继承体系中,基类和派生类中如果存在相同名字的成员,如果使用派生类对象调用此成员,会优先调用派生类中存在的成员,把基类中存在的隐藏掉---->同名隐藏   相同名字的成员:同名成员变量、同名成名函数(与变量和函数的类型无关,只要是相同名字就好)   所以,不要在继承体系中使用相同名字的成员   

4.继承与派生之间的关系—>赋值兼容规则
这里写图片描述

三、继承的分类
继承分为单继承、多继承、菱形继承(钻石继承)
单继承

一个基类对应一个派生类,它们之间的关系是一对一  

这里写图片描述
单继承的对象模型
这里写图片描述

多继承

一个派生类对应多个基类,它们之间的关系是一对多

这里写图片描述
多继承的对象模型
这里写图片描述
这里对象模型中基类的顺序是按照派生类继承基类时的顺序排列的,比如这里基类1就是Student类,基类2就是Teacher类,因为Graduate先继承Student类,所以基类1就是Student类
菱形继承

单继承和多继承的结合

这里写图片描述

由图可见:Student类和Teacher类分别单继承Person类,Graduate类多继承Student类和Teacher类

#include<stdlib.h>#include<iostream>using namespace std;class Person{public:    int _p;};class Student :public Person{private:    int _s;};class Teacher:public Person{private:    int _t;};class Graduate :public Student, public Teacher{public:    int _g;};int main(){    Graduate g;    Person p;    g._p = 0;//二义性问题,此时编译器就会报错,_p指代不明确    system("pause");    return 0;}  Graduate中有两份Person类中的成员,当访问Person类中的成员时,不知道该访问Student类中的,还是Teacher类中的,因为Student和Teacher分别单继承Person类,它们都把Person类中的成员继承了。所以,菱形继承会存在二义性和数据冗余问题。

那应该怎么解决菱形继承中存在的二义性和数据冗余问题呢?
虚继承很好的解决了这个问题,那什么是虚继承呢?

虚继承:是面向对象编程中的一种技术,是指一个指定的基类,在继承体系中,将其成员数据实例共享给也从这个基类直接或间接派生的其他类。简单来说就是会使指定基类中的成员在整个继承体系中只存在一份。

虚继承和单继承的比较:
这里写图片描述

虚拟继承多的四个字节是:指向偏移量表格的指针
偏移量表格
这里写图片描述

菱形虚拟继承很好的解决了菱形继承中存在的二义性(某数据在继承体系中保存了两份,编译器在访问时不知道该访问那个)问题
那什么是菱形虚拟继承呢?

菱形虚拟继承的对象模型:
这里写图片描述

例如:
如下表:总大小为20个字节,4个int,所有的偏移量表格占四个字节,总共20个字节
这里写图片描述

第一个偏移量表格的终地址为相对于整个对象大小的偏移量,终值是20
第二个偏移量表格的终地址为相对于整个对象大小的偏移量,终值是12

为什么菱形虚拟继承可以解决菱形继承中存在的二义性问题呢?

虚拟继承把具有二义性的数据(此例是Person类)放在类的其他成员之后,这样在整个继承体系中就只保存了一份,这样再访问Preson类中的数据时,就不会出现多种选择,只有一种选择(即某数据在继承体系中只会保存一份)