继承与派生的概念:面向对象程序设计中提高代码复用率的最重要的手段,它允许程序员在保持原有类的特性上在进行扩展,增加功能产生新的类。原有的类称为基类或父类,新建立的类称为派生类或子类。
例如:
现实生活中公马继承了马的全部特征,新增了雄性的新特征,白公马继承了公马的全部特性增加了白色的新特性。
派生类的声明方式:我们先通过一个最简单的单继承例子来说明怎样建立派生类。
首先定义一个基类:
定义派生类:
由上述可见声明派生类的一般形式为:
派生类的构成:派生类分为两大部分,一部分是从基类继承来的成员一部分是在定义派生类时新增的部分。
总结:
(1)从基类接受成员:派生类将基类的全部成员继承过来(不包括构造函数和析构函数)没有选择不能只接收其中一部分而舍弃另一部分。
(2)在继承体系中基类和派生类是两个不同的作用域
(3)基类和派生类中有同名成员,派生类成员将屏蔽基类对成员的直接访问。(在派生类类成员函数中可以通过基类::基类成员 访问)--隐藏(实际继承体系中最好不要定义同名成员)
派生类的继承方式:
(1)公有继承(public)
采用公有继承时,基类的公有和保护成员仍保留其原有的属性,而私有成员虽然被继承但没有成为派生类的私有成员,它仍是基类的私有成员在派生类中属于不可访问的成员。
公有继承方式下基类成员在派生类中的访问属性:
在基类中的访问属性 | 继承方式 | 在派生类中的访问属性 |
private(私有) public(公有) protected(保护) | public(公有) public(公有) public(公有) | 不可访问 public(公有) protected(保护) |
public继承下的赋值兼容规则:
(1)子类对象可以赋值给父类对象
(2)父类对象不能赋值给子类对象
解析:两个对象可以赋值要么类型相同要么可以发生隐式的类型转换。
对于子类对象可以赋值给父类对象
对于父类对象不能赋值给子类对象
(3)父类的指针/引用可以指向子类对象
(4)子类的指针和引用不能指向父类对象(可以通过强制类型转换完成)
(2)保护继承(protected)
采用保护继承时,基类的公有成员和保护成员在派生类中都成了保护成员,其私有成员仍为基类私有,也就是说把基类原有的公有成员也保护起来,不让类外任意访问。
保护继承方式下基类成员在派生类中的访问属性:
在基类中的访问属性 | 继承方式 | 在派生类中的访问属性 |
private(私有) public(公有) protected(保护) | protected(保护) protected(保护) protected(保护) | 不可访问 protected(保护) protected(保护) |
(3)私有继承(private)
采用私有继承时,基类的公有成员和保护成员,在派生类中的访问属性相当于派生类中的私有成员,只有派生类的成员函数能访问它们,在派生类外不能访问。
私有继承方式下基类成员在派生类中的访问属性:
在基类中的访问属性 | 继承方式 | 在派生类中的访问属性 |
private(私有) public(公有) protected(保护) | private(私有) private(私有) private(私有) | 不可访问 private(私有) private(私有) |
友元与继承:友元关系不能继承,基类友元不能访问子类私有和保护成员。
特殊现象解释:对于输入输出运算符重载由于第二个参数是基类对象的引用可以访问到派生类中继承基类的部分,但是这种现象并不代表友元被继承。
继承与静态成员:基类定义了静态成员则整个继承体系只有这一个成员,无论派生多少个子类,都只有一个static成员实例。
派生类的默认成员函数:
派生类并没有继承基类的默认成员函数,并且在派生类没有显式定义时,系统会为它默认合成。
(1)构造函数:
对于上述例子
所以派生类的构造函数定义形式为:
继承关系中构造函数的调用顺序:
由程序结果可以看出先执行的是基类构造函数,而反汇编显示先调用派生类构造函数。
因此构造函数的调用顺序为:
调用派生类构造函数->调用基类构造函数(执行基类函数体)->执行派生类构造函数函数体
析构函数执行顺序:
在对象释放时 先执行派生类析构函数~derived()->在执行基类析构函数~Base()。
说明:
(1)基类定义了带有形参表构造函数,派生类就一定要显式定义构造函数并且派生类必须在初始化列表中显式给出基类名和参数列表。
(2)基类没有定义构造函数则派生类也可以不用定义,全部使用缺省构造函数。
多继承:
概念:一个子类有两个或两个以上直接父类时称为多继承。
声明方式举例:class D:public A ,protected B,private C
{类D新增的成员}
菱形继承:
菱形继承模型
菱形继承对象模型:
让我们一起通过下面的代码,来探讨菱形继承对象的模型
由上述可以看出上述菱形继承体系中对象模型如下:
问题:上述例子中Assistant对象中有两份Person成员那么在访问的时候怎么区分两个_data1呢?
这就是多重继承中的二义性问题了,为了解决这个问题C++中提出了虚继承的概念,让我们一起来看看它是如何解决的。
虚继承
虚继承使用方式 :在声明派生类时,使用virtual关键字指定继承方式。
一般形式为:
class 派生类名 : virtual 继承方式 基类名。
我们在上述例子中加入virtual关键字,看看它是如何解决的。
可以看出上述虚拟继承体系中对象模型为:
上述模型中相对积累的偏移量地址中存放的值如下,大家可以采用更多的方式去验证。