📄 c++11.dat
字号:
第十一章 继承与多态
每一节 基类和派生类
上一章,我们学习了类,类是进行面向对象程序设计的基础.它能够定义数据和对数据的操作,并通过不同的访问权限,将类的接口和内部的实现分开,支持信息的封装和隐藏.面向对象程序设计的其它重要特征还包括:继承和多态.支持程序代码的复用是面向对象程序设计的主要目标之一,而支持程序代码复用的最重要的方法之一就是继承.继承能够从一个类派生出另一个类,前者称之为基类或父类,后者称之为派生类或子类.派生类能够继承基类的功能,也能改变或增加它的功能.
在面向对象的程序设计中,多态的功能强大,但也较难掌握.它允许指向基类对象的指针指向派生类的对象.基类和派生类中可有名字和参数完全相同的函数,但他们的功能并不相同.当程序运行时,如果用基类指针调用成员函数,能够根据该指针所指向的对象的类型自行确定是调用基类的成员函数还是调用派生类的成员函数.如果没有多态的功能,我们就不得不用条件语句,确定是调用哪一个类的成员函数.本章中,我们将介绍继承和多态的用法.
代码复用是C++最重要的性能之一,它是通过类继承机制来实现的.通过类继承,我们可以复用基类的代码,并可以在继承类中增加新代码或者覆盖基类的成员函数,为基类成员函数赋予新的意义,实现最大限度的代码复用.
1.1 继承的定义及工作方式
我们可以用一个简单的例子解释什么叫继承.假定我们要处理2维空间中的点,定义了一个称之为twoD的2维空间点类:
class twoD
{
protected:
double x, y; // x和y坐标
public:
twoD(double i, double j):x(i), y(j){}
//内联构造函数
//内联成员函数
void setX(double NewX){x = NewX;}
void setY(double NewY){y = NewY;}
double getX() const {return x;}
double getY() const {return y;}
};
假定后来又要处理3维空间点的情形,一个直接的方法是再定义一个3维空间点类threeD:
程序1
class threeD
{
protected:
double x, y, z; // x 、y和z坐标
public:
twoD(double i, double j, double k):x(i), y(j) , z(k){}
//内联构造函数
//内联成员函数
void setX(double NewX){x = NewX;}
void setY(double NewY){y = NewY;}
void setZ(double NewZ){z = NewZ;}
double getX() const {return x;}
double getY() const {return y;}
double getZ() const {return z;}
};
实际上threeD类仅比twoD类多一个成员变量z及两个成员函数setZ()和getZ().也就是说,threeD类增加了一点新的代码到twoD类的定义之中.
在threeD类中编写了一部分与twoD类中重复的代码,如果使用继承,则可以简化threeD类的代码.继承的一般形式如下:
class 派生类:访问权限 基类
{
…
}
访问权限是访问控制说明符,它可以是public、private或protected.如果使用继承,我们可以将threeD类的定义改写为:
// 使用继承定义threeD类
class threeD:public twoD
{
private:
double z;
public:
// 内联的构造函数
// 基类的构造函数不被继承
// threeD 类的构造函数复用了twoD类的构造函数,并通过
//成员初始化表将值传送到twoD的构造函数
threeD(double i, double j, double k):twoD(i,j){z = k;}
void setZ(double NewZ){z = NewZ;}
double getZ() {return z;}
};
上例中,twoD称为基类,threeD称为派生类.应该注意到:派生类threeD中,setX()、setY()、getX(、getY()函数没有再定义,因为这些函数是可从基类twoD继承,就好象在threeD类中定义了这些函数一样.
注意:twoD的构造函数用threeD的构造函数的初始化表中,说明基类的数据成员先初始化.基类的构造函数和析构函数不能被派生类继承.每一个类都有自己的构造函数和析构函数,如果用户没有显式定义,则编译器会隐式定义缺省的构造函数和析构函数.
派生类是与基类有一定联系的,基类是描述一个事物的一般特征,而派生类有比基类更丰富的属性和行为.比如正文中的例子,基类是一个二维点的模型,派生类从一个二维点模型中派生,添加了第三维向量和相应函数,生成了一个三维点模型.继承的一般形式如下:
class 派生类:访问权限 基类
{
…
}
如果需要,派生类可以从多个基类继承,也就是多重继承,这将在后面章节中讲解.通过继承,派生类自动得到了除基类私有成员以外的其它所有数据成员和成员函数,在派生类中可以直接访问,从而实现了代码的复用.
从概念上讲,基类和派生类的关系与类和对象的关系有着根本的不同.比如说,我们定义了猫科动物类:
class felid
{
//类中定义了猫科动物的基本特征
};
现在由于某种需要,我们想对猫这个动物进行定义.猫是猫科动物的一种,很自然,我们就想到从猫科动物这个类里面派生:
class cat: public felid
{
char name[20] //名字
char color[80] //毛色
cat();
cat(char* name, char* color);
}
在类cat中,示意性地定义了两个变量.我们知道,对象是类的一个实例,而在这里,cat还是一个类,一个更加具体的类,不是felid类的对象,这一点要分辨清楚.我们可以定义猫的一个对象(实例):
cat mycat("Jack","bronze");
mycat才是一只实实在在的猫对象.
派生类对象生成时,要调用构造函数进行初始化.编译器的调用过程是先调用基类的构造函数,对派生类中的基类数据进行初始化,然后再调用派生类自己的构造函数,对派生类的数据进行初始化工作.当然,在派生类中也可以更改基类的数据,只要它有访问权限.
基类数据的初始化要通过基类的构造函数,而且,它要在派生类数据之前初始化,所以基类构造函数在派生类构造函数的初始化表中调用:
派生类名 (参数表1):基类名(参数表2)
其中"参数表1"是派生类构造函数的参数,"参数表2"是基类构造函数的参数.通常情况下,参数表2中的参数是参数表1的一部分.也就是说,用户应该提供给派生类所有需要的参数,包括派生类和基类的.事实上也是这样,派生类继承了基类的成员变量,就相当于是自己的一部分,当然有责任对基类的变量进行初始化,只不过对于基类成员的初始化要借助于基类的构造函数而已.如果派生类构造函数没有显式调用基类的构造函数,编译器也会先调用基类的缺省参数的构造函数,对基类数据进行初始化.如果派生类自己也没有显式定义构造函数,那么编译器会为派生类定义一个缺省的构造函数,在生成派生类对象时,仍然先调用基类的构造函数.所以,派生类没有定义构造函数的话,必须保证基类有缺省参数的构造函数.
调用内嵌对象的构造函数与调用基类构造函数不同的是:调用基类构造函数用的是基类构造函数名,而这里使用的是内嵌的对象名.
上面我们讨论了派生类的构造函数的调用,也有必要讲解一下析构函数的调用.析构函数在对象被销毁时调用,对于派生类对象来说,基类析构函数和派生类构造函数也要分别调用,不过我们不再需要进行显式的析构函数调用,编译器能够为我们做好这件事.因为对于任何类型,只有一个析构函数,并且它并不带任何参数.需要注意的是:析构函数次序与构造函数调用次序正好相反.
下面我们举个例子来说明构造函数和析构函数的调用顺序.
例1
#include "iostream.h"
#define CLASS(ID) class ID{\
public:\
ID(int) {cout<<#ID<<" created"<<endl;}\
~ID() {cout<<#ID<<" destroyed"<<endl;}\
};
CLASS(base);
CLASS(member1);
CLASS(member2);
CLASS(member3);
CLASS(member4);
class derived1:public base
{
member1 mem1;
member2 mem2;
public:
derived1(int i) :mem2(i), mem1(i) ,base(i){
cout<<"derived1 created"<<endl;
}
~derived1() {
cout<<"derived1 destroyed"<<endl;
}
};
class derived2 : public derived1
{
member3 mem3;
member4 mem4;
public:
derived2(int i) :derived1(i), mem3(i), mem4(i) {
cout<<"derived2 created"<<endl;
}
~derived2() {
cout<<"derived2 destroyed"<<endl;
}
};
void main()
{
derived2 d(1);
}
在程序中,我们用下面的语句定义类:
#define CLASS(ID) class ID{\
public:\
ID(int) {cout<<#ID<<" created"<<endl;}\
~ID() {cout<<#ID<<" destroyed"<<endl;}\
};
CLASS(base);
实际上就是:
class base{
public:
base(int) {cout<<"base"<<" created"<<endl; }
~base() {cout<<"base"<<" destroyed"<<endl; }
};
程序运行结果为:
base created
member1 created
member2 created
derived1 created
member3 created
member4 created
derived2 created
derived2 destroyed
member4 destroyed
member3 destroyed
derived1 destroyed
member2 destroyed
member1 destroyed
base destroyed
除了构造函数和析构函数不能被继承外,operater =() 也不能被继承,因为它完成和构造函数类似的工作.
1.2 访问控制
一、类内访问说明符
我们定义twoD和threeD类的时候,变量成员和成员函数前面有访问说明符:public、private或proteced,它们控制变量成员和成员函数在类内和类外如何访问.所谓类内访问是指用类的成员函数进行访问,而类外访问是指用对象或指向对象的指针进行访问.
当一个类的成员定义为public,就能够在类外访问,包括它的派生类.
当一个成员定义为private,它仅能在类内访问,不能被它的派生类访问.
当一个成员定义为proteced,它仅能在类内访问,但是能被它的派生类访问.
当一个成员没有指定访问说明符时,默认为private.
二、继承访问说明符
在定义派生类时,访问说明符也能出现在基类的前面,它控制基类的变量成员和成员函数在派生类中的访问方法.当访问说明符为public时,称为公有继承.同样地,当问说明符为protected时,称为保护继承,而当问说明符为private时,称为私有继承.
公有继承时,基类的公有成员,变为派生类的公有成员,基类的保护成员,变为派生类的保护成员.
保护继承时,基类的公有和保护成员,均变为派生类的保护成员.
私有继承时,基类的公有和保护成员,均变为派生类的私有成员.
虽然C++语法上,能够定义公有继承、保护继承和私有继承.但是,我们通常使用公有继承,保护继承与私有继承的用处不大.
下面,我们再举一个公有继承的例子:
例1
class A
{
private:
int x;
protected:
double w;
public:
A(int v, double z): x(v), w(z) {}
~A() {}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -