1. 继承和派生一般性概念
在一个已经存在的类的基础上建立一个新的类,新类从已有的类那里获取某些已有的特征,这种现象称为类的继承。从另一个角度说,从已有的类产生一个新的类,称为类的派生.
01 | class CBase |
02 | { |
03 | public : |
04 | int m_iBase; |
05 | int BaseFunc() |
06 | { |
07 | return m_iBase; |
08 | } |
09 | }; |
10 | |
11 | class CDerived: public CBase |
12 | { |
13 | public : |
14 | int m_iDerived; |
15 | int DerivedFunc() |
16 | { |
17 | return m_iDerived; |
18 | } |
19 | }; |
如上,CBase类派生了一个新的类CDerived,或者说类CDerived从类CBase继承而来,这样,
类CDerived不但具有新增的成员m_iDerived和DerivedFunc(),还有从类CBase继承而来的成员
m_iBase和BaseFunc(),从而类CDerived实际具有4个成员。
派生类的一般声明形式为:
1 | class 派生类名:[继承方式] 基类名 |
2 | { |
3 | 派生类新增的成员 |
4 | }; |
2. 类的继承方式
派生类并非把基类的所有特性都继承下来。它还受两方面约束:
1、 不论何种方式,下面这些基类的特征是不能从基类继承下来的:
l 构造函数
l 析构函数
l 用户重载的new 运算符
l 用户重载的=运算符
l 友员关系
2、 对基类的除(1)以外的其他成员的继承受继承方式限制,有三种继承的方式:私有继承、保护继承和公有继承。不同的继承方式将导致从基类继承来的成员在派生类中具有不同的访问限制属性。三种继承方式一般形式为:
01 | class 派生类名: private 基类名 //私有继承 |
02 | { |
03 | 派生类新增成员 |
04 | }; |
05 | |
06 | class 派生类名: protected 基类名 //保护继承 |
07 | { |
08 | 派生类新增成员 |
09 | }; |
10 | |
11 | class 派生类名: public 基类名 //公有继承 |
12 | { |
13 | 派生类新增成员 |
14 | }; |
练习:写程序,推断在不同的继承方式下,派生类继承的基类成员的访问限制属性
不同继承方式下的访问限制属性
继承方式 | 基类中的访问属性 | 派生类中的访问属性 |
公有继承 | private | 为基类私有 |
protected | 保护类型 | |
public | 公有类型 | |
保护继承 | private | 为基类私有 |
protected | 保护类型 | |
public | 保护类型 | |
私有继承 | private | 为基类私有 |
protected | 私有类型 | |
public | 私有类型 |
3. 派生类对基类成员的覆盖
如果基类和派生类中存在同名的成员数据或者成员函数,那么派生类的成员数据和成员函数将覆盖掉基类的成员数据和成员函数。而且与基类和派生类的访问属性无关;与基类和派生类的函数间的参数和返回类型无关。
01 | class CBase |
02 | { |
03 | protected : |
04 | int m_i; |
05 | public : |
06 | int Func( int i) |
07 | { |
08 | return m_i; |
09 | } |
10 | }; |
11 | |
12 | class CDerived: public CBase |
13 | { |
14 | public : |
15 | int m_i; //覆盖掉基类的m_i; |
16 | int Func() //覆盖掉基类的Func |
17 | { |
18 | m_i=9; //访问自身的成员 |
19 | CBase::m_i=22; //通过作用域解析符访问基类的成员 |
20 | return m_i; |
21 | } |
22 | }; |
23 | |
24 | void main() |
25 | { |
26 | CDerived obj; |
27 | obj.Func(); |
28 | //obj.Func(3); //错误!基类的Func被覆盖 |
29 | obj.CBase::Func(3); //正确,通过作用域解析符访问基类的成员 |
30 | } |
4. 派生类对象的初始化与清除
如果需要在初始化派生类成员时也初始化基类的成员,则可在派生类的构造函数的初始化成员列表中初始化基类,如果基类的构造函数均带有参数,则基类必须出现在派生类的初始化成员列表中。
派生类构造函数及初始化列表的一般形式为:
派生类名(构造函数参数列表): 基类名(参数列表),派生类成员1(参数列表1),,,派生类成员n(参数列表n)
例:
01 | class CBase |
02 | { |
03 | protected : |
04 | int m_iBase; |
05 | public : |
06 | //CBase(){} |
07 | CBase( int i){m_iBase=i;} |
08 | }; |
09 | |
10 | class CDerived: public CBase |
11 | { |
12 | public : |
13 | int m_iDerived; |
14 | CDerived( int i); |
15 | }; |
16 | |
17 | CDerived::CDerived( int i):CBase(0),m_iDerived(i) |
18 | { |
19 | |
20 | } |
执行派生类构造函数的顺序为:
1、 调用基类的构造函数,初始化基类的数据成员;
2、 初始化派生类的数据成员;
3、 执行派生类的构造函数本身。
派生类的析构函数相对简单 ,与无继承关系的普通类的析构函数形式相同。执行析构函数的顺序为:
1、 调用派生类的析构函数;
2、 调用基类的析构函数。
5. 对象间的转换与赋值
5.1. 派生类与基类间的转换
正如整型数据可以自动转换成double型一样,基类对象与派生类对象间也存在赋值兼容的关系。具体表现在一下几个方面:
1 | class CBase |
2 | { |
3 | //... |
4 | }; |
5 | class CDerived: public CBase |
6 | { |
7 | //... |
8 | }; |
l 派生类对象可以向基类对象赋值;
CBase base;
CDerived derived;
base=derived; //将派生类对象的基类部分拷贝给基类对象
l 派生类对象可以替代基类对象向基类对象的引用进行赋值或者初始化;
CBase b1;
CDerived d1;
CBase &b1Alias=b1; //普通的引用
b1Alias=d1; //将d1的基类部分拷贝给b1Alias(即b1)
CBase &b2=d1; //d1基类部分的引用
l 如果函数的参数是基类对象或者基类对象的引用,则相应的实参可以是派生类对象。
01 | void Func1(CBase base) |
02 | { |
03 | //... |
04 | } |
05 | void Func2(CBase &base) |
06 | { |
07 | //... |
08 | } |
09 | CDerived derived; |
10 | Func1(derived); //将derived的基类部分拷贝给行参base |
11 | Func2(derived) //将derived的基类部分当作行参使用 |
l 派生类对象的地址可以赋给基类类型的指针变量,或者说,基类型的指针可以指向派生类对象。
1 | CBase *pBase; |
2 | CDerived derived; |
3 | pBase=&derived; |
5.2. 派生类与派生类间的赋值(拷贝)
如果用户定义了基类的拷贝构造函数,而没有定义派生类的拷贝构造函数,那么在用一个派生类对象初始化新的派生类对象时,两对象间的派生类部分执行缺省的行为,而两对象间的基类部分执行用户定义的基类拷贝构造函数。
如果用户重载了基类的对象赋值运算符=,而没有定义派生类的对象赋值运算符,那么在用一个派生类对象给新的派生类对象赋值时,两对象间的派生类部分执行缺省的赋值行为,而两对象间的基类部分执行用户定义的重载赋值函数。
如果用户定义了派生类的拷贝构造函数或者重载了派生类的对象赋值运算符=,则在用已有派生类对象初始化新的派生类对象时,或者在派生类对象间赋值时,将会执行用户定义的派生类的拷贝构造函数或者重载赋值函数,而不会再自动调用基类的拷贝构造函数和基类的重载对象赋值运算符,这时,通常需要用户在派生类的拷贝构造函数或者派生类的赋值函数中显式调用基类的相应函数。
例:
01 | class CBase |
02 | { |
03 | protected : |
04 | char *m_pszData; |
05 | public : |
06 | CBase( const char *pszData) |
07 | { |
08 | m_pszData= new char [ strlen (pszData)+1]; |
09 | strcpy (m_pszData,pszData); |
10 | } |
11 | CBase( const CBase &base) |
12 | { |
13 | m_pszData= new char [ strlen (base.m_pszData)+1]; |
14 | strcpy (m_pszData,base.m_pszData); |
15 | } |
16 | CBase &operator =( const CBase &base) |
17 | { |
18 | if ( this ==&base) |
19 | return * this ; |
20 | m_pszData= new char [ strlen (base.m_pszData)+1]; |
21 | strcpy (m_pszData,base.m_pszData); |
22 | return * this ; |
23 | } |
24 | ~CBase(){ delete []m_pszData;} |
25 | }; |
26 | |
27 | class CDerived: public CBase |
28 | { |
29 | public : |
30 | CDerived( const char *pszData):CBase(pszData){} |
31 | }; |
32 | |
33 | void main() |
34 | {1q |
35 | CDerived d1( "Hello!" ); |
36 | CDerived d2=d1; //派生类使用缺省的拷贝构造函数、基类调用用户定义的拷贝构造函数 |
37 | d1=d2; //派生类使用缺省的赋值操作,基类调用用户重载的赋值运算符 |
38 | } |
6. 保护构造函数与私有构造函数
l 如果一个类的构造函数和析构函数是保护类型的,则不能在程序中实例化该类的对象,这样的类称为抽象类。由于保护的构造函数可以被派生类访问,所以我们通常将抽象类用于各种派生类的公共基类,使得该抽象类派生的类拥有共同的特性。
01 | class CAbstract |
02 | { |
03 | protected : |
04 | CAbstract(){} //保护类型的构造函数 |
05 | //... |
06 | }; |
07 | |
08 | class CDerived: public CAbstract |
09 | { |
10 | //... |
11 | }; |
12 | void main() |
13 | { |
14 | //CAbstract objAbs; //错误,无法实例化 |
15 | CDerived objDer; //可以 |
16 | } |
l 如果类的构造函数是私有的,这样的类也是抽象类,但是因为派生类也无法调用它,从而无法用派生的方式初始化,但是可以用静态函数的方式实例化该类的对象。
01 | class CAbstract |
02 | { |
03 | private : |
04 | CAbstract(){} //保护类型的构造函数 |
05 | //... |
06 | public : |
07 | static CAbstract *CreateInstance() |
08 | { |
09 | return new CAbstract(); |
10 | } |
11 | static void ReleaseInstance(CAbstract *pThis) |
12 | { |
13 | delete pThis; |
14 | } |
15 | }; |
16 | |
17 | void main() |
18 | { |
19 | CAbstract *pObj=CAbstract::CreateInstance(); |
20 | //... |
21 | CAbstract::ReleaseInstance(pObj); |
22 | } |
7. 多重继承与虚基类
7.1. 多重继承
如果一个派生类同时有两个或者多个基类,派生类从两个和多个基类中继承所需的属性,这种继承方式称为多重继承(Multiple Inheritance)。
声明多重继承的一般形式为:
1 | class 派生类名:继承方式1 基类名1,继承方式2 基类名2,,,继承方式n 基类名n |
2 | { |
3 | //... 派生类新增的成员。 |
4 | }; |
例:
01 | class CBaseA |
02 | { |
03 | public : |
04 | int m_i; |
05 | void Func() |
06 | { |
07 | m_i++; |
08 | } |
09 | }; |
10 | |
11 | class CBaseB |
12 | { |
13 | public : |
14 | int m_i; |
15 | void Func() |
16 | { |
17 | m_i++; |
18 | } |
19 | }; |
20 | |
21 | class CDerived: public CBaseA, public CBaseB |
22 | { |
23 | public : |
24 | int m_j; |
25 | }; |
数据成员在内存中的布局形式为:
pa
int m_i [CBaseA中的成员] | [CDerived] |
int m_i [CBaseB中的成员] | |
int m_j |
pb
调用时必须注意防止二义性:
01 | CDerived d; |
02 | d.m_j=0; //正常 |
03 | //i=d.m_i; //引起二义性 |
04 | d.CBaseA::m_i=1; //通过成员民限定消除二义性,访问从CBaseA中继承来的m_i |
05 | d.CBaseB::m_i=2; //通过成员民限定消除二义性,访问从CBaseB中继承来的m_i |
06 | //d.Func(); //引起二义性 |
07 | d.CBaseA::Func(); //通过成员民限定消除二义性,访问从CBaseA中继承来的成员函数Func() |
08 | d.CBaseB::Func(); //通过成员民限定消除二义性,访问从CBaseB中继承来的成员函数Func() |
09 | CBaseA *pa; |
10 | CBaseB *pb; |
11 | pa=&d; //系统会自动将pa指向对象d的CBaseA部分 |
12 | pb=&d; //系统会自动将pb指向对象d的CBaseB部分 |
7.2. 虚基类
如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在生成派生类对象时,系统会为派生类对象生成共同基类数据成员的多份拷贝。如果希望派生类对象中只包含一份共同基类的数据成员,则可以在声明派生类时,通过virtual继承方式,使派生对象只保留共同基类的一份数据成员。
例:
01 | class CBase |
02 | { |
03 | public : |
04 | int m_i; |
05 | }; |
06 | |
07 | class CBaseA: public CBase |
08 | { |
09 | public : |
10 | int m_ia; |
11 | }; |
12 | |
13 | class CBaseB: public CBase |
14 | { |
15 | public : |
16 | int m_ib; |
17 | }; |
18 | class CDerived: public CBaseA, public CBaseB |
19 | { |
20 | public : |
21 | int m_d; |
22 | }; |
则内存布局形式为
int m_i | CBase | CBaseA | CDerived |
int m_ia | |||
int m_i | CBase | CBaseB | |
int m_ib | |||
int m_id |
而如果类CBaseA和CBaseB的声明形式为:
01 | class CBaseA: virtual public CBase |
02 | { |
03 | public : |
04 | int m_ia; |
05 | }; |
06 | |
07 | class CBaseB: virtual public CBase |
08 | { |
09 | public : |
10 | int m_ib; |
11 | }; |
这样派生类对象可以直接使用公共基类的成员:
1 | CDerived d; |
2 | d.m_i=9; |