学习笔记之继承与派生

来源:互联网 发布:java程序员的自我介绍 编辑:程序博客网 时间:2024/04/30 23:22

面向对象程序设计有4个主要特点:抽象,封装,继承和多态。

 

1.1继承与派生的概念

在C++中可重用性是通过继承(inheritance)这一机制来实现的。继承是C++的一个只要组成部分.

一个新类从已有的父类那里获得其已有特征,这种现象称为类的继承。通过继承,一个新建子类从已有的父类那里获得父类的特性。

从另一个角度说,从已有的类(父类)产生一个新的子类,称为类的派生。

类的继承是用已有的类来简历专用类的编程技术.

派生类继承了基类的所有数据成员和成员函数,并可以对成员做必要的增加和调整。

一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此基类和派生类是相对而言的.

一个派生类不仅可以从一个基类派生,也可以从多个基类派生。一个派生类有两个或者多个基类的称为多重继承(multiple inheritance)。

关于派生类和基类的关系,可以表述为:派生类是基类的具体化,而基类则是派生类的抽象。

继承方式包括:public(公用的),private(私有的),protected(保护的).此项是可选的,如果不写此项,则默认是private的。

派生类从基类接收成员,派生类把基类全部的成员(不包括构造函数和析构函数)接手过来,也就是说是没有选择的,不能选择接收其中一部分成员,而舍弃另一部分成员.

 

不同继承方式决定了基类成员在派生类中的访问属性.

1)公用继承(public inheritance)

基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。

2)私有继承(private inheritance)

基类的公有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类私有。

3)受保护的继承(protected inheritance)

基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有.

 

公用继承:

采用公用继承方式时,基类的公用成员和保护成员在派生类中仍然保持其公用成员和保护成员的属性,而基类的私有成员在派生类中并没有成为派生类的私有成员,它仍然是基类的私有成员,只有基类的成员函数可以应用它,而不能被派生类的成员函数应用,因此就成为派生类中的不可访问的成员。

私用继承

私有基类的公用成员和保护成员在派生类中的访问属性相当于派生类中的私有成员,即派生类的成员函数能访问他们,而在派生类不能访问他们。私有基类的私有成员在派生类中成为不可访问的成员,只有基类的成员函数可以应用他们.一个基类成员在基类中的访问属性和在派生类中的访问属性可能是不同的.

保护继承

如果基类声明了私有成员,那么任何派生类都是不能访问他们的,若希望在派生类中能访问他们,应当把它们声明为保护成员,如果一个类中声明了保护成员,就意味着该类可能要用作基类,在他的派生类中会访问这些成员

比较一下私有继承和保护继承,可以发现,在直接派生类中,以上两种继承方式的作用实际上是相同的;在类外不能访问任何成员,而在派生类中可以通过成员函数访问基类中的公用成员和保护成员。

 

在派生类中,成员有4种不同的访问属性:

1)公用的,派生类和派生类外都可以访问。

2)受保护的,派生类内可以访问,派生类外不能访问,其下一层的派生类可以访问。

3)私有的,派生类内可以访问,派生类外不能访问

4)不可访问的,派生类内和派生类外都不能访问。

 

 

 

派生类的构造函数和析构函数

用户在声明类时可以不定义构造函数,系统会自动设置一个默认的构造函数,在定义类对象时会自动调用这个默认的构造函数。这个构造函数实际上是一个空函数,不执行任何操作,如果需要对类中的数据成员初始化,应自己定义构造函数。

构造函数的主要作用是对数据成员初始化,在设计派生类的构造函数时,不仅要考虑派生类所新增的数据成员的初始化,还应当考虑基类的数据成员初始化,也就是说,希望在执行派生类的构造函数时,使派生类的数据库成员和基类的数据成员同时都被初始化,解决这个问题的思路是:在执行派生类的构造函数时,调用基类的构造函数。

其一般形式为

派生类构造函数名(总参数列表):基类构造函数名(参数类别)

{派生类中新增数据成员初始化语句}

请注意:在类中对派生类构造函数作声明时,不包括基类构造函数名及其参数列表,只在定义函数时才将他们列出。

实际上,在派生类构造函数中对基类成员初始化,就是构造函数初始化表,也就是说,不仅可以利用初始化表对构造函数的数据成员初始化,而且可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一个构造函数的定义中同时实现这两种功能。

 

在建立一个对象时,执行构造函数的顺序是:派生类构造函数先调用基类的构造函数,再执行派生类构造函数本身(即派生类构造函数的函数体)

在派生类对象释放时,先执行派生类的析构函数,再执行基类的析构函数。

 

派生类构造函数的任务应该包括3个部分

对基类数据成员的初始化,对子对象数据成员初始化,对派生类数据成员初始化

程序中派生类构造函数首部如下:

Student1(int n,string nam,int n1,string nam1,int a,string ad):Student(n,nam),monotor(n1,nam1)

 

归纳起来,定义派生类构造函数的一般形式为

派生类构造函数名(总参数表列):基类构造函数名(参数表列),子对象名(参数表列)

{派生类中新增数据成员初始化语句}

执行派生类构造函数的顺序是:

调用基类构造函数,对基类数据成员初始化

调用子对象构造函数,对子对象数据成员初始化

再执行派生类构造函数本身,对派生类数据成员初始化。

派生类构造函数的总参数表列中的参数,应当包括基类构造函数和子对象的参数列表中的参数,基类构造函数和子对象的次序可以是任意的。如上面的派生类构造函数首部可以写成

Student(int n,string nam,int n1,string nam1,int a,stirng ad):monitor(n1,nam1),Student(n,nam)

 

如果在基类中没有定义构造函数,或者定义了没有参数的构造函数,那么在定义派生类构造函数时可以不写基类构造函数,因为此时派生类构造函数没有向基类构造函数传递参数的任务。调用派生类构造函数时系统自动首先调用基类的默认构造函数。如果基类和子对象类型的声明中都没有定义带参数的构造函数,而且也不需对派生类自己的数据成员初始化,则可以不必显示的定义派生类构造函数。因为此时派生类构造函数既没有像基类构造函数和子对象构造函数传递参数的任务,也没有对派生类数据成员初始化的任务。在建立派生类对象时,系统会自动调用系统提供的派生类的默认构造函数,并在执行派生类默认构造函数的过程中,调用基类的默认构造函数和子对象类型默认构造函数。

如果在基类或者子对象类型的声明中定义了才参数的构造函数,那么就必选显示的定义派生类构造函数,并在派生类构造函数中写出基类或子对象类型的构造函数及其参数。

如果在基类中既定义无参的构造函数,又定义了有参的构造函数(构造函数重载),则在定义派生类构造函数时,既可以包含基类构造函数及其参数,也可以不包含基类构造函数,在调用派生类构造函数时,根据构造函数的内容决定调用基类的有参构造函数还是无参构造函数。

 

 

派生类的析构函数

在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。调用的顺序和构造函数正好相反;先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理.

 

声明多重继承的方法

如果已声明了类A,类B和类C,可以声明多重继承的派生类D:

class D:public A,private B,protected C

{类D新增加的成员}

多重继承派生类的构造函数形式于单继承时的构造函数形式基本相同,只有在初始表中包含多个基类构造函数

派生类构造函数名(总参数列表):基类1构造函数(参数类别),基类2构造函数(参数列表),基类3构造函数(参数列表)

{派生类中新增成员初始化}

各基类的排列顺序任意。派生类构造函数同样为:先调用基类的构造函数,再执行派生类构造函数的函数体.调用基类构造函数的顺序是按照声明派生类时基类出现的顺序.

 

 

多重继承一起的二义性问题

 

虚基类

虚基类的作用: 如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员,在引用这些同名成员时,必选在派生类对象名后增加直接基类名,以避免产生二义性,使其唯一地标识一个成员.

在一个类中 保留间接共同基类的多份同名成员,这种现象是人们不希望出现的。

C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员.

现在,将类A声明为虚基类,方法如下

class A//声明基类A

{......};

 

class B:virtual public A//声明类B是类A的公用派生类,A是B的虚基类

{......};

 

class C:virtual public A//声明类C是类A的公用派生类,A是C的虚基类

{......};

 

注意:虚基类并不是声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在声明另一个派生类时不作为虚基类,声明虚基类的一般形式为

class 派生类名:virtual 继承方式 基类名

经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。

需要注意:为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍然会出现对基类的多次继承.

 

 虚基类的初始化

C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化.

注意:在定义派生类的构造函数时,与以往使用的方法有所不同。规定:在最后的派生类中不仅仅要负责对其直接基类进行初始化,还要负责对虚基类初始化

 

使用多重继承时要十分小心,经常会出现二义性问题。许多专业人员认为:不要提倡在程序中使用多重继承,只有在比较简单和不易出现二义性的情况下或实在必要时才使用多重继承,能用单一继承解决的问题不要使用多重继承,也是由于这个原因,有些面向多谢的程序设计语言(如java,smalltalk)并不支持多重继承.

 

基类与派生类的转换

基类于派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以其子类对象代替.

1)派生类对象可以向基类对象赋值。

A a1;//定义基类A对象a1

B b1;//定义类A的公用派生类B的对象b1

a1 = b1;//用派生类B对象b1对基类对象a1赋值

在赋值时舍弃派生类自己的成员,实际上,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。

请注意:赋值后不能企图通过对象a1去访问派生类对象b1的成员,因为b1的成员于a1的成员是不同的。

应当注意,只能用子类对象对其基类对象赋值,而不能用基类对象对其之类对象赋值,理由是显然的,因为基类对象不包括派生类的成员,无法对派生类的成员赋值

2)派生类对象可以提到基类对象向基类对象的引用进行赋值或者初始化.

如已定义了基类A对象a1,可以定义a1的引用变量

A a1;//定义基类A对象a1

B b1;//定义公用派生类B对象b1

A &r = a1;//定义基类A对象的引用变量r,并哦那个a1对其初始化

这时,引用变量r是a1的别名,r和a1共享同一段储存单元。也可以用子类对象初始化引用变量r,将上面最后一行改为:

A &r = b1;//定义基类A对象的引用变量r,并用派生类B对象b1对其初始化

或者保留上面第3行"A &r = a1;",而对r重新赋值:

r = b1;//用派生类B对象b1对a1的引用变量r赋值

 

3)如果函数的参数是基类对象或者基类对象的引用,相应的实参可以用子类对象

4)派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象.

 

用指向基类对象的指针变量指向子类对象是合法的,安全的,不会出现遍以上的错误,但在应用上却不能完全满足人们的希望,人们有时希望通过使用基类指针能够基类和子类对象的成员。在下一章就要解决这个问题。办法是使用虚函数和多态性