第7章 继承与派生
& s( q$ v$ v6 E4 p( D5 U
; ~1 w4 ~) w( u2 k 7.1 继承与派生 - Y. x/ Q4 e. E) H# ]/ D
1基本概念
( K1 f' m* s5 E% j* Z! P" ` 派生类从基类继承了各种成员的关系就称为继承。 " a9 R+ [, T3 X: o
类的继承是新的类从已有类那里得到已有的特性。从已有的类产生新类的过程就是类的派生。在继承过程中,原有的类或已经存在的用来派生新类的类称为基类或父类,而由已经存在的类派生出的新类则称为派生类或子类。 8 L5 C8 H+ d0 K. \9 G: U8 e
从派生类的角度,根据它所拥有的基类数目不同,可以分为单继承和多继承。一个类只有一个直接基类时,称为单继承;而一个类同时有多个直接基类时,则称为多继承。 7 R) ]+ t0 ^. K/ v: S/ O8 s, V
从上面的描述可知,任何一个类都可以派生出一个新类,派生类也可以再派生出新类,因此,基类和派生类是相对而言的,一个基类可以是另一个基类的派生类,从而形成了复杂的继承结构,出现了类的层次。基类与派生类之间的关系如下:
& v k1 d9 D7 L (1)基类是对派生类的抽象,派生类是对基类的具体化。基类抽取了它的派生类的公共特征,而派生类通过增加信息将抽象的基类变为某种有用的类型,派生类是基类定义的延续。
6 }# n1 b; Q- s5 u (2)派生类是基类的组合。多继承可以看作是多个单继承的简单组合。
6 \* C2 M% f& W( Q' r (3)公有派生类的对象可以作为基类的对象处理。这一点与类聚集(成员对象)是不同的,在类聚集(成员对象)中,一个类的对象只能拥有作为其成员的其他类的对象,但不能作为其他类对象而使用。 2 R# X" c+ o- t5 ?* u
2派生类的定义与构成
- O5 K: L) p o4 c# o$ W$ w: W- a) z 定义派生类的一般格式如下: / E! O) j- W+ D7 O9 i
class<派生类名>:<继承方式1><基类名1>, / d: f9 @/ n4 C" k5 p
<继承方式2><基类名2>, " X4 H) J, V! L3 X$ `1 M9 t; ~
……, 7 ?, c( F3 f3 x( y1 e
<继承方式n><基类名n> " p) _! R* l" W) j
{
# C$ w" B) j! [8 k9 g+ l <派生类新定义成员> # \: }3 r( D+ q! f
}; + Z" ^6 }$ G3 H# e0 @3 P
其中,<基类名>是已有的类的名称,<派生类名>是继承原有类的特性而生成的新类的名称。单继承时,只需定义一个基类;多继承时,需同时定义多个基类。
0 k5 F q* V- I9 e <继承方式>即派生类的访问控制方式,用于控制基类中声明的成员在多大的范围内能被派生类的用户访问。每一个继承方式,只对紧随其后的基类进行限定。继承方式包括3种:公有继承(public)、私有继承(private)和保护继承(protected)。如果不显式地给出继承方式,缺省的类继承方式是私有继承private。 " u8 a$ d- g) Q4 E* G- }0 |* n" U
7.2 派生类的构造函数和析构函数
. K, K: z1 N7 v/ X 1派生类构造函数的一般格式如下:
* k( g+ }( j h" I# p. h0 F <派生类名>::<派生类名>(<总参数表>):<基类名1>(参数表1), " t" q6 R! P5 {
…… 2 ?; |" v, A. o7 K& I# w$ W' M
<基类名n>(<参数表n>),
/ j$ L$ S: L. j, u+ u <成员对象名1>(<参数表n+1>), ' ~4 @ v/ `8 X$ }7 c& p$ j% @
……,
5 V' T, S& v7 R" o- a$ G' G4 k' y <成员对象名m>(<参数表n+m>)
* |$ b1 h0 n6 @ { 1 `$ z8 k$ |& @) c6 t3 d! L8 i
<派生类构造函数体> 3 l/ ~$ p9 q, [% R# I$ a6 q0 X
}
3 a F! H5 T, E9 m8 W& s 派生类的构造函数名与类名相同。在构造函数的参数表中,给出了初始化基类数据、成员对象数据以及新增的其他数据成员所需要的全部参数。在参数表之后,列出需要使用参数进行初始化的基类名和成员对象名以及各自的参数名,各项之间使用逗号分隔。注意对基类成员和新增成员对象的初始化必须在成员初始化列表中进行。
) P& E" S3 T( h4 M6 k- e) @5 z1 { 当派生类有多个基类时,处于同一层次的各个基类的构造函数的调用顺序取决于定义派生类时声明的顺序(自左向右),而与在派生类构造函数的成员初始化列表中给出的顺序无关。如果派生类的基类也是一个派生类,则每个派生类只需负责它的直接基类的构造,依次上溯。
M" L7 }3 W, X/ {, ?7 r 当派生类中有多个成员对象时,各个成员对象构造函数的调用顺序也取决于在派生类中定义的顺序(自上而下),而与在派生类构造函数的成员初始化列表中给出的顺序无关。
7 U% w, A+ y! G1 w1 Q8 D$ A& ? 建立派生类对象时,构造函数的执行顺序如下: 6 Y& y9 t( M$ r1 ~9 w
(1)执行基类的构造函数,调用顺序按照各个基类被继承时声明的顺序(自左向右);
5 h) y2 N& u) \ (2)执行成员对象的构造函数,调用顺序按照各个成员对象在类中声明的顺序(自上而下); . p: v& p& c; Q. A/ R& e7 Z- E
(3)执行派生类的构造函数。
, Z D: r* c# d 派生类的构造函数只有在需要的时候才必须定义。派生类构造函数提供了将参数传递给基类构造函数的途径,以保证在基类进行初始化时能够获得必要的数据。因此,如果基类的构造函数定义了一个或多个参数时,派生类必须定义构造函数。 & e6 E, {2 _/ c" ?7 |( ^
如果基类中定义了缺省构造函数或根本没有定义任何一个构造函数(此时,由编译器自动生成缺省构造函数)时,在派生类构造函数的定义中可以省略对基类构造函数的调用,即省略“<基类名>(<参数表>)”。成员对象的情况与基类相同。
/ d) ]7 p T3 e1 B$ x: p f1 U7 @/ T) g 当所有的基类和成员对象的构造函数都可以省略,并且也可以不在成员初始化列表中对其他数据成员进行初始化时,可以省略派生类构造函数的成员初始化列表。
4 ]5 }2 `! W, { 2派生类的析构函数 6 K% w# w8 u$ J
与构造函数相同,派生类的析构函数在执行过程中也要对基类和成员对象进行操作,但它的执行过程与构造函数严格相反,即: 9 ?7 m& }! y5 D& F
(1)对派生类新增普通成员进行清理。
7 Q4 I& r* g* E+ Y, Q (2)调用成员对象析构函数,对派生类新增的成员对象进行清理。
1 X" P* i- s0 P (3)调用基类析构函数,对基类进行清理。 派生类析构函数的定义与基类无关,与没有继承关系的类中的析构函数的定义完全相同。它只负责对新增普通成员的清理工作,系统会自己调用基类及成员对象的析构函数进行相应的清理工作。 |