派生类与继承
概念
继承允许编程者在已有类的基础上创建新的类,可以从一个或者多个已有类中继承函数和数据,并重新定义或者添加新的函数和数据,已有类称为基类或父类,新类称为派生类和子类。
声明
声明一个派生类的一般格式为:
class 派生类名 : [继承方式] 基类名 { 派生类新增的数据成员和成员函数 };
继承方式种类有 private, public ,protected ,分别为私有、公有和保护继承
若不显式地给出关键字,则默认为私有
构成
构造一个派生类包括三部分公作:
1)派生类从基类接受成员
派生类将基类除构造函数和析构函数以外的全部成员全部接收
2)调整从基类接收来的成员
调整包括两个方面 改变基类成员在派生类中的访问属性(通过继承方式实现)
对基类成员重定义(派生类的同名成员会覆盖基类中的同名成员)
3)在派生类中添加新的成员
基类成员在派生类中的访问属性
基类中的成员 | 在公有派生类中的访问属性 | 在私有派生类中的访问属性 | 在保护派生类中的访问属性 |
---|---|---|---|
私有成员 | 不可直接访问 | 不可直接访问 | 不可直接访问 |
公有成员 | 公有 | 私有 | 保护 |
保护成员 | 保护 | 私有 | 保护 |
派生类对基类成员的访问规则
派生类对基类成员的访问形式主要有两种:
(1)内部访问 由派生类中新增的成员函数对基类继承来的成员的访问
(2)对象访问 在派生类外部,通过派生类对象对从基类继承来的成员的访问
私有继承的访问规则
基类中的成员 | 私有成员 | 公有成员 | 保护成员 | |
---|---|---|---|---|
访问方式 | 内部访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 不可访问 | 不可访问 |
公有继承的访问规则
基类中的成员 | 私有成员 | 公有成员 | 保护成员 | |
---|---|---|---|---|
访问方式 | 内部访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 可访问 | 不可访问 |
保护继承的访问规则
基类中的成员 | 私有成员 | 公有成员 | 保护成员 | |
---|---|---|---|---|
访问方式 | 内部访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 不可访问 | 不可访问 |
派生类的构造函数和析构函数
基类的构造函数和析构函数不能被继承,所有从派生类的初始化需要加入派生类的构造函数,而基类继承下来的成员的初始化还是有基类的构造函数完成的,析构函数同理。
执行顺序
创建派生类对象时,先执行基类构造函数,再执行派生类的构造函数,
在撤销派生类对象时,先执行派生类析构函数,再执行基类的析构函数。
构造规则
简单派生类的构造函数
派生类构造函数的一般格式为
派生类名(参数总表):基类名(参数表) { 派生类新增数据成员的初始化语句 }
基类构造函数的参数通常来自于派生类构造函数的参数总表,也可以用常数值
若基类使用默认构造函数或不带参数的构造函数,则可略去基类的构造函数,若此时不需要构造函数,则可不定义派生类构造参数
当基类构造函数中存在参数,则派生类必须定义构造函数
含有对象成员的派生类的构造函数
构造参数的一般形式为
派生类名(参数总表):基类名(参数表0),对象成员名1(参数表 1),......,对象成员名n(参数表 n) { 派生类新增数据成员的初始化语句 }
该构造函数执行顺序如下:
+调用基类的构造函数;
+调用内嵌对象成员的构造函数;
+执行派生类的构造函数体;
注意:
1.当派生类中含有多个内嵌对象成员时,调用内嵌对象成员的构造函数顺序由它们在类中的声明顺序决定
2.若派生类的基类也是派生类,则每个派生类只需负责其直接基类数据成员的初始化
调整基类成员在派生类中的访问属性的其他方法
同名成员
派生类可以重新说明与基类成员同名的成员,在派生类使用该名字意味着访问在派生类中重新定义的成员,若要访问基类的同名成员,则需要在该成员名前加上基类名和作用域标识符"::"
例:
class X { public: X(); ~X(); void f(); }; class Y:public X { public: Y(); ~Y(); void f(); }; //若要访问派生类中的f(),则调用函数f()或Y::f() //若要访问基类中的f(),则调用函数X::f()
访问声明
对于私有继承,外界无法利用派生类的对象直接调用基类的成员函数,只能通过调用派生类的成员函数间接调用基类的成员函数
C++提供类称为访问声明的特殊机制,可以个别调整基类中的某些成员,使之在派生类中保持原有的访问属性
访问声明的方法就是把基类的保护或公有成员直接写至私有派生类定义式中的同名段中,同时冠以基类名和作用域标识符"::",利用这种方法该成员即成为派生类的保护和公有成员
例
class X:private Y { public: X(); ~X(); A::print; };
注意:
1.数据成语也可以使用访问声明
2.访问声明中只含不带类型和参数的函数名和变量名
3.访问声明不能改变成员在基类中的访问属性
4.对于基类中的重载函数名,访问声明将对基类中的所有同名函数起作用
多重继承
当一个派生类具有两个或多个基类时,即称为多重继承或多基继承。
多重继承派生类的声明
声明一般形式如下
class 派生类名 : 继承方式1 基类1 ,......,继承方式n 基类n { 派生类新增的数据成员和成员函数 }
继承方式即:private,pubilc,protected,默认继承方式为 private
注意:对基类成员的访问必须是无二义的
多重继承派生类的构造函数和析构函数
多重继承派生类的构造函数的一般形式如下:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),......,基类名n(参数表n) { 派生类新增成员的初始化语句 }
多重继承派生类的构造函数与单继承下派生类构造函数相似,它必须同时负责该派生类所有基类构造函数的调用。同时,派生类的参数个数必须包含完成所有基类初始化所需的参数个数
多重继承派生类的构造函数与单继承下派生类构造函数的执行顺序相同,也是遵循先执行基类的构造函数,再执行对象成员的构造函数,最后执行派生类的构造函数体的原则。
析构函数与单继承的情况类似
虚基类
虚基类的意义:避免产生二义性
虚基类的概念
在c++中,如果想使这个公共的基类只产生一个复制,则可以将这个基类说明为虚基类。
虚基类在派生类中声明,其语法形式如下:
class 派生类名:virtual 继承方式 基类名 { ...... }
虚基类的初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的调用顺序不同。
注意:
1.如果在虚基类中定义带有形参的构造函数,并且没有定义默认形式的构造参数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化列表中列出对虚基类构造函数的调用。
2.建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
3.若同一个层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数。
4.虚基类不影响构造函数的调用顺序。
说明:
1.关键字virtual和派生方式关键字(public 或 private)的先后顺序无关紧要。
2.一个基类既可以作为某些派生类虚基类,也可以作为另一些派生类的非虚基类。
基类与派生类对象之间的赋值兼容关系
在一定条件下,不同类型的数据之间可以进行类型转换,这种不同类型数据之间的自动转换和赋值称为赋值兼容
在基类与派生类对象之间的赋值兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代
在基类对象可以使用的任何地方,都可以使用派生类的对象来替代,但只能使用从基类继承来的成员,具体表现在以下几个方面。
1.派生类对象可以向基类对象赋值
2.派生类对象可以初始化基类对象的引用
3.派生类对象的地址可以赋给指向基类对象的指针
4.如果函数的形参是基类对象或基类对象的引用,在调用函数时可以用派生类对象作为实参
说明:
1.声明为指向基类对象的指针可以指向它的公有派生的对象,但不允许指向它的私有派生的对象
2.允许将一个声明为指向基类的指针指向其公有派生类的对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象