继承性是面向对象程序设计的第二大特性,它允许在既有类的基础上创建新类,新类可以继承既有类的数据成员和成员函数,可以添加自己特有的数据成员和成员函数,还可以对既有类中的成员函数重新定义。利用类的继承和派生实现了更高层次的代码可重用性,符合现代软件开发的思想。
C++语言同时支持单一继承和多重继承。单一继承是指派生类只从一个基类继承而来;相应的,多重继承指派生类同时从两个或更多的基类继承而来。java只支持单一继承。
一. 派生类
派生类的定义格式如下:
class <派生类名>:[继承方式]<基类名1>
[,[继承方式]<基类名2>,...,[继承方式]<基类名n>]
{
<派生类新增的数据成员和成员函数定义>
};
说明:
(1)定义派生类关键字可以是class或者是struct,两者区别是:用class定义派生类,默认的继承方式是private,用struct定义派生类,默认的继承方式为public。新增加的成员默认属性也是class对应private属性,struct对应public属性。
(2)基类不能被派生类继承的两类函数是构造函数和析构函数。
二. 3种继承方式下基类成员在派生类中的访问属性
继承描述符 | 父public成员 | 父protected成员 | 父private成员 |
public | 子public成员 | 子protected成员 | - |
protected | 子protected成员 | 子protected成员 | - |
private | 子private成员 | 子private成员 | - |
用下面的代码简单理解一下:
1 #include "stdafx.h"
2 #include<iostream>
3 using namespace std;
4
5 class Base
6 {
7 private:
8 int priData;
9 protected:
10 int proData;
11 public:
12 int pubData;
13 };
14
15 class D1:private Base//私有继承
16 {
17 void f1()
18 {
19 //priData=1;//基类private成员在派生类中不可直接访问
20 proData=2;//基类的protected成员在派生类中为private访问属性
21 pubData=3;//基类的public成员在派生类中为private访问属性
22 }
23 };
24
25 class D2:protected Base//保护继承
26 {
27 void f2()
28 {
29 //priData=1;//基类private成员在派生类中不可直接访问
30 proData=2;//基类的protected成员在派生类中为protected访问属性
31 pubData=3;//基类的public成员在派生类中为protected访问属性
32 }
33 };
34
35 class D3:public Base//公有继承
36 {
37 void f3()
38 {
39 //priData=1;//基类private成员在派生类中不可直接访问
40 proData=2;//基类的protected成员在派生类中为protected访问属性
41 pubData=3;//基类的public成员在派生类中为public访问属性
42 }
43 };
44
45 int main()
46 {
47 Base obj;
48 //obj.priData=1;//对象不可访问Base类中private成员
49 //obj.proData=2;//对象不可访问Base类中protected成员
50 obj.pubData=3;
51 D1 objD1;
52 //objD1.pubData=3;//private属性,不可访问
53 D2 objD2;
54 //objD2.pubData=3;//protected属性,不可访问
55 D3 objD3;
56 objD3.pubData=3;//public属性,可以访问
57 return 0;
58 }
基类的private成员函数虽然在派生类的成员函数中不可直接访问,但派生类的成员函数可以通过调用基类被继承的函数来间接访问这些成员。如果基类的函数被继承后在派生类中仍为public成员,则可以通过派生类对象直接调用。
先来看一下类成员的访问属性及作用:
访问属性 | 作用 |
private | 只允许该类的成员函数及友元函数访问,不能被其他函数访问 |
protected | 既允许该类的成员函数及友元函数访问,也允许其派生类的成员函数访问 |
public | 既允许该类的成员函数访问,也允许类外部的其他函数访问 |
好了,继续通过代码来理解:
1 #include "stdafx.h"
2 #include<iostream>
3 using namespace std;
4
5 class Base
6 {
7 private:
8 int priData;
9 protected:
10 int proData;
11 public:
12 int pubData;
13 //在类的定义中不能对数据成员进行初始化
14 void SetData()//为基类中的数据成员赋值
15 {
16 priData=100;
17 proData=200;
18 pubData=300;
19 }
20 void Print()
21 {
22 cout<<"priData="<<priData<<endl;
23 cout<<"proData="<<proData<<endl;
24 cout<<"pubData="<<pubData<<endl;
25 }
26 };
27
28 class Derived:public Base
29 {
30 public:
31 void ChangeData()
32 {
33 SetData();
34 proData=12;//在派生类的成员函数类可以访问基类的非私有成员
35 }
36 };
37
38 int main()
39 {
40 Base b;
41 b.SetData();
42 b.Print();
43
44 Derived d1;
45 d1.ChangeData();
46 d1.pubData=13;
47 d1.Print();
48
49 return 0;
50 }
程序运行结果如下:
三. 派生类的构造函数和析构函数
在定义一个派生类的对象时,在派生类中新增加的数据成员当然用派生类的构造函数初始化,但是对于从基类继承来的数据成员的初始化工作就必须由基类的构造函数完成,这就需要在派生类的构造函数中完成对基类构造函数的调用。同样,派生类的析构函数值能完成派生类中新增加数据成员的扫尾、清理工作,而从基类继承来的数据成员的扫尾工作也应有基类的析构函数完成。由于析构函数不能带参数,因此派生类的析构函数默认直接调用了基类的析构函数。
派生类构造函数定义格式如下:
<派生类名>(<总形式参数表>):<基类名1>(<参数表1>),
<基类名2>(<参数表2>),[...,<基类名n>(<参数表n>),其他初始化项>]
{
[<派生类自身数据成员的初始化>]
}
说明:
(1)总形式表给出派生类构造函数中所有的形式参数,作为调用基类带参构造函数的实际参数以及初始化本类数据成员的参数;
(2)一般情况下,基类名后面的参数表中的实际参数来自前面派生类构造函数形式参数总表,当然也可能是与前面形式参数无关的常量;
(3)在多层次继承中,每一个派生类只需要负责向直接基类的构造函数提供参数;如果一个基类有多个派生类,则每个派生类都要负责向该积累的构造函数提供参数。
1.单一继承
1 #include"stdafx.h"
2 #include<iostream>
3 using namespace std;
4
5 class Other
6 {
7 public:
8 Other()
9 {
10 cout<<"constructing Other class"<<endl;
11 }
12 ~Other()
13 {
14 cout<<"destructing Other class"<<endl;
15 }
16 };
17
18 class Base
19 {
20 public:
21 Base()
22 {
23 cout<<"constructing Base class"<<endl;
24 }
25 ~Base()
26 {
27 cout<<"destructing Base class"<<endl;
28 }
29 };
30
31 class Derive:public Base
32 {
33 private:
34 Other ot;
35 public:
36 Derive()
37 {
38 cout<<"constructing Derive class"<<endl;
39 }
40 ~Derive()
41 {
42 cout<<"destructing Derive class"<<endl;
43 }
44 };
45
46 int main()
47 {
48 Derive d;
49
50 return 0;
51 }
程序运行结果如下:
可以看到定义派生类对象时,构造函数的调用顺序:
a.先调用基类的构造函数
b.然后调用派生类对象成员所属类的构造函数(如果有对象成员)
c.最后调用派生类的构造函数
析构函数的调用顺序正好与构造函数调用顺序相反。
2.多重继承
1 #include"stdafx.h"
2 #include<iostream>
3 using namespace std;
4
5 class Grand
6 {
7 int g;
8 public:
9 Grand(int n):g(n)
10 {
11 cout<<"Constructor of class Grand g="<<g<<endl;
12 }
13 ~Grand()
14 {
15 cout<<"Destructor of class Grand"<<endl;
16 }
17 };
18
19 class Father:public Grand
20 {
21 int f;
22 public:
23 Father(int n1,int n2):Grand(n2),f(n1)
24 {
25 cout<<"Constructor of class Father f="<<f<<endl;
26 }
27 ~Father()
28 {
29 cout<<"Destructor of class Father"<<endl;
30 }
31 };
32
33 class Mother
34 {
35 int m;
36 public:
37 Mother(int n):m(n)
38 {
39 cout<<"Constructor of class Mother m="<<m<<endl;
40 }
41 ~Mother()
42 {
43 cout<<"Destructor of class Mother"<<endl;
44 }
45 };
46
47 class Son:public Father,public Mother
48 {
49 int s;
50 public:
51 Son(int n1,int n2,int n3,int n4):Mother(n2),Father(n3,n4),s(n1)
52 {
53 cout<<"Constructor of class Son s="<<s<<endl;
54 }
55 ~Son()
56 {
57 cout<<"Destructor of class Son"<<endl;
58 }
59 };
60
61 int main()
62 {
63 Son s(1,2,3,4);
64 return 0;
65 }
程序运行结果如下:
可以看到,与单一继承不同的是:在多重继承中,派生类有多个平行的基类,这些处于同一层次的基类构造函数的调用顺序,取决于声明派生类时所指定的各个基类的顺序,而与派生类构造函数的成员初始化列表中调用基类构造函数的顺序无关。
派生类的3种继承方式总结如下:
1.公有继承
基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。这里保护成员和私有成员相同。
派生类的可见性对派生类来说,基类的公有成员和保护成员可见,基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态;基类的私有成员不可见,基类的私有成员仍然是私有的,派生类不可访问基类中的私有成员。
派生类对象的可见性对派生类对象来说,基类的公有成员是可见的,其他成员是不可见的。
在公有继承时,派生类的对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有成员和保护成员。
2.私有继承
基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。
派生类的可见性对派生类来说,基类的公有成员和保护成员是可见的,基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问;基类的私有成员是不可见的,派生类不可访问基类中的私有成员。
派生类对象的可见性对派生类对象来说,基类的所有成员都是不可见得。
在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。
3.保护继承
与私有继承的区别仅在于对派生类的成员而言,基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。
派生类的可见性对派生类来说,基类的公有成员和保护成员是可见的,基类的公有成员和保护成员都作为派生类的保护成员,并且不能被这个派生类的子类的对象所访问,但可以被派生类的子类的所访问,;基类的私有成员是不可见的,派生类不可访问基类中的私有成员。
派生类对象的可见性对派生类对象来说,基类的所有成员都是不可见的。
虚继承是多重继承中特有的概念。虚基类是为解决多重继承而出现的。
类D继承自B和C,类B,C都继承自A,所以在类D中会出现两次A,为了节省空间,可以将B,C对A的继承定义为虚拟继承,而A就成了虚拟基类
class A;
class B:public virtual A;
class C:public virtual A;
class D:public B,public C;