第四课:
1.面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。要较好地进行面向对象程序设计,还必须了解面向对象程序设计另外两个重要特征——继承性和多态性。
2.在C++中,所谓“继承”就是在一个已存在的类的基础上建立一个新的类。
已存在的类(例如“马”)称为“基类(base class )”或“父类(father class )”。
新建的类(例如“公马”)称为“派生类(derived class )”或“子类(son class )”。
3.一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。
从另一角度说,从已有的类(父类)产生一个新的子类,称为类的派生。
4.一个派生类有两个或多个基类的称为多重继承(multiple inheritance)
关于基类和派生类的关系,可以表述为:派生类是基类的具体化,而基类则是派生类的抽象。
5.声明派生类的一般形式为5.
class 派生类名:[继承方式] 基类名{派生类新增加的成员};
继承方式包括: public (公用的),private (私有的)和protected(受保护的),此项是可选的,如果不写此项,则默认为private(私有的)。
6.在基类中包括数据成员和成员函数(或称数据与方法)两部分,派生类分为两大部分: 一部分是从基类继承来的成员,另一部分是在声明派生类时增加的部分。每一部分均分别包括数据成员和成员函数。
7.
具体说,在讨论访问属性时,要考虑以下几种情况:
(1) 基类的成员函数访问基类成员—— 基类的成员函数可以访问基类成员。
(2) 派生类的成员函数访问派生类自己增加的成员——派生类的成员函数可以访问派生类成员。
(3) 基类的成员函数访问派生类的成员——基类的成员函数只能访问基类的成员,而不能访问派生类的成员。
(4) 在派生类外访问派生类的成员——在派生类外可以访问派生类的公用成员,而不能访问派生类的私有成员。
(5) 派生类的成员函数访问基类的成员。——情况复杂。
(6) 在派生类外访问基类的成员。——情况复杂。
8.不同的继承方式决定了基类成员在派生类中的访问属性。
简单地说:
(1) 公用继承(public inheritance)
基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。
(2) 私有继承(private inheritance)
基类的公用成员和保护成员在派生类中成了私有成员。
其私有成员仍为基类私有。
(3) 受保护的继承(protected inheritance)
基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。
保护成员的意思是: 不能被外界引用,但可以被派生类的成员引用。
9.多级派生的访问属性:派生类中不能访问私有成员,多级派生均采用公用继承方式,那么直到最后一级都可以访问基类的公用成员和保护成员;若一直采用私有继承方式,若干次之后,基类成员最终将不可以被访问了;若采用保护继承的方式,则派生类外是无法访问派生类内的任何成员的。
10.派生类的构造函数和析构函数
派生类构造函数的构造方法:派生类构造函数名(总参数列表):基类构造函数名(参数列表)
{派生类中新增成员初始化语句}
注意:在类中对派生类构造函数作声明时,不包括基类构造函数名及其参数列表列。只在定义函数时才将它列出,
11.请注意:不仅可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一个构造函数的定义中同时实现这两种功能。
12.在建立一个对象时并进行初始化时,执行构造函数的顺寻是:1.派生类构造函数先调用基类构造函数;2.再执行派生类构造函数本身。例子如下:
#include
#include
using namespace std;
class Student
{public:
Student(int n,string nam,char s)
{
num=n;
name=nam;
sex=s;
}
void dispay()
{
cout<<"num:"<
子对象:类的数据成员中包含的对象。看一个例子理解,如下:
#include
#include
using namespace std;
class Student
{public:
Student(int n,string nam)
{
num=n;
name=nam;
}
void display()
{
cout<<"num:"<
(1)对基类数据成员初始化;
(2)对子对象数据成员初始化;
(3)对派生类数据初始化。
定义派生类构造函数的一般形式:
派生类构造函数名(总参数列表):基类构造函数名(参数列表),子对象名(参数列表)
{派生类中新增数据成员初始化语句}
15.多层派生时的构造函数
基类的构造函数首部:
Student(int n,string nam)
派生类Student1的构造函数首部:
Student1(int n,string nam,int a):Student(n,nam)
和Student1(int n,string nam,int n1,string nam1,int a,string ad):Student(n,nam),monitor(n1,nam1)
派生类Student2的函数构造首部:
Student1(int n,string nam,int a):Student1(n,nam,a).
初始化顺序:基类——派生类1新增成员——派生类2新增成员
16.派生类构造函数的特殊形式
(1) 当不需要对派生类新增的成员进行任何初始化操作时,派生类构造函数的函数体可以为空,
即构造函数是空函数,如例11.6程序中派生类Student1构造函数可以改写为
Student1(int n, strin nam,int n1, strin nam1):Student(n,nam),
monitor(n1,nam1) { }
此派生类构造函数的作用只是为了将参数传递给基类构造函数和子对象,
并在执行派生类构造函数时调用基类构造函数和子对象构造函数。
在实际工作中常见这种用法。
(2) 如果在基类中没有定义构造函数,或定义了没有参数的构造函数,那么在定义派生类构造函数时可不写基类构造函数。
因为此时派生类构造函数没有向基类构造函数传递参数的任务。
调用派生类构造函数时系统会自动首先调用基类的默认构造函数。
如果在基类和子对象类型的声明中都没有定义带参数的构造函数,而且也不需对派生类自己的数据成员初始化,则可以不必显式地定义派生类构造函数。
因为此时派生类构造函数既没有向基类构造函数和子对象构造函数传递参数的任务,也没有对派生类数据成员初始化的任务。
在建立派生类对象时,系统会自动调用系统提供的派生类的默认构造函数,并在执行派生类默认构造函数的过程中,调用基类的默认构造函数和子对象类型默认构造函数。
如果在基类或子对象类型的声明中定义了带参数的构造函数,那么就必须显式地定义派生类构造函数,并在派生类构造函数中写出基类或子对象类型的构造函数及其参数表。
如果在基类中既定义无参的构造函数,又定义了有参的构造函数(构造函数重载),则在定义派生类构造函数时,既可以包含基类构造函数及其参数,也可以不包含基类构造函数。
在调用派生类构造函数时,根据构造函数的内容决定调用基类的有参的构造函数还是无参的构造函数。
17.派生类的析构函数
在派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
在派生类中可以根据需要定义自己的析构函数,用来对派生类中所增加的成员进行清理工作。
基类的清理工作仍然由基类的析构函数负责。
在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
调用的顺序与构造函数正好相反:先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。
18.多重继承:1)允许一个派生类同时继承多个基类,这种行为称为多重继承(multiple inheritance)。
2)声明多重继承的方法
如果已声明了类A、类B和类C,可以声明多重继承的派生类D:
class D:public A,private B,protected C {类D新增加的成员}
3)多重继承派生类的构造函数
多重继承派生类的构造函数形式与单继承时的构造函数形式基本相同,只是在初始表中包含多个基类构造函数。
如
派生类构造函数名(总参数表列):基类1构造函数(参数表列),基类2构造函数(参数表列),基类3构造函数(参数表列){派生类中新增数成员据成员初始化语句}
19.多重继承引起的二义性问题与虚基类
在引用一些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如
c1.A::display( )。
在一个类中保留间接共同基类的多份同名成员,这种现象是人们不希望出现的。
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 继承方式 基类名
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。
在派生类B和C中作了上面的虚基类声明后,派生类D中的成员如图11.23所示。
需要注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。
2. 虚基类的初始化如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。例如:
class A//定义基类A
{A(int i){ } //基类构造函数,有一个参数};
class B :virtual public A //A作为B的虚基类
{B(int n):A(n){ } //B类构造函数,在初始化表中对虚基类初始化};
class C :virtual public A //A作为C的虚基类
{C(int n):A(n){ } //C类构造函数,在初始化表中对虚基类初始化};
class D :public B,public C //类D的构造函数,在初始化表中对所有基类初始化
{D(int n):A(n),B(n),C(n){ }};
1)只有公用派生类才是基类真正的子类型。
当使用到基类对象的时候可以用其子类对象代替:
(1) 派生类对象可以向基类对象赋值。
(2) 派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化。
(3) 如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。
(4) 派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。
21.类的组合与继承在一个类中以另一个类的对象作为数据成员的,称为类的组合(composition)。
继承是纵向的,组合是横向的。