再上一篇:10.3 实现类型转换和拷贝的构造函数
上一篇:10.4 构造函数与对象成员
主页
下一篇:11.2 初始化基类成员
再下一篇:11.3 冲突、支配规则和赋值兼容性
文章列表

第十一章 继承和派生类

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

从已有的对象类型出发建立一种新的对象类型,使它部分或全部地继承原对象的特点和功能,这是面向对象设计方法中的基本特性之一。继承不仅简化了程序设计方法,显著提高软件的重用性,而且还使得软件更容易维护。派生则是继承的直接产物,它通过继承已有的一个或多个类来产生一个新的类,通过派生可以创建一种类族。本章讨论继承和派生方面的语法和特性。

11.1继承

11.1.1 基本概念

在定义一个类A时,若它使用了一个已定义类B的部分或全部成员,称类A继承了类B;并称类B为基类或父类,称类A为派生类或子类。一个派生类又可以作为另一个类的基类,一个基类可以派生出若干个派生类,这样可以构成了类树。

继承常用来表示类属关系,不能将继承理解为构成关系。当从已有的类中派生出新的类时,可以对派生类做以下几种变化:

1、 可以全部或部分地继承基类的成员数据或成员函数。

2、 可以增加新的成员变量。

3、 可以增加新的成员函数。

4、 可以重新定义已有的成员函数。

5、 可以改变现有的成员属性。

在 C++中有二种继承:单一继承和多重继承。当一个派生类仅由一个基类派生时,称为单一继承;而当一个派生类由二个或更多个基类所派生时,称为多重继承。

在校人员类 学生类 职工类

学生类 职工类 在校人员类

(a) (b)

图 11.1 单一继承和多重继承

图 11.1(a)为单一继承,首先定义描述有关人员的公共特性,如姓名,年龄,身高,性别等构成一个基类“在校人员类”。在这基类的基础上,增加描述学生特性的信息,如学号,所学专业,学习的课程等,派生出“学生类”。同理在“在校人员类”的基础上,增加描述职工特性的信息,如工资,工作部门,职工号,所教课程等,派生出“职工类”。注意,图中的箭头是从派生类指向基类。单一继承形成一棵倒挂的树。在这种继承方式中,派生类继承了基类的所有成员数据和成员函数;并在派生类中增加新的成员数据和成员函数。在图11.1(b)中,分别定义了描述学生和职工特性的“学生类”和“职工类”,并把这二个类作为基类,派生出“在校人员类”。在这派生类中,可以增加也可以不增加成员数据和成员函数。

C++中派生类从父类中继承特性时,可在派生类扩展它们,或者对其作些限制,也可改变或删除某一特性,也可对某些特性不作任何修改。所有这些变化可归结为二种基本的面向对象技术。第一种称为特性(性质)约束,即对父类的特性加以限制或删除。第二种称为特性扩展,即增加父类的特性。

11.1.2 单一继承

从一个基类派生一个类的一般格式为:

class ClassName:BaseClassName

{

private:

......; //私有成员说明

public:

......; //公有成员说明

protected:

......; //保护成员说明

};

其中ClassName为派生类的类名;而BaseClassName为已定义的基类的类名;Access可有可无,用于规定基类中的成员在派生类中的访问权限,它可以是关键字 public、private 和protected之一。当Access省略时,对于类,系统约定为private;而对于结构体而言,系统约定为public。花括号中的部分是在派生类中新增加的成员数据或成员函数。这部分也可为空。

当Access为public时,称派生的类为公有派生;当Access为private时,称派生类为私有派生;而当Access为protected时,称派生类为保护派生。派生时指定不同的访问权限,直接影响到基类中的成员在派生类中的访问权限。下面对这三种访问权限分别讨论之。11.1.2.1 公有派生

公有派生时,基类中所有成员在公有派生类中保持各个成员的访问权限。具体地说,基类中说明为public的成员,在派生类中仍保持为public的成员,在派生类中或在派生类外都可以直接使用这些成员。基类中说明为private的成员,属于基类私有的,在公有派生类中不能直接使用基类中的私有成员,必须通过该基类公有的或保护的成员函数来间接使用基类中的私有成员。对于基类中说明为protected的成员,可以在公有派生类中直接使用它们,其用法与公有成员完全一样。但在派生类之外,不可直接访问这种类型的成员,必须通过派生类的公有的或保护的成员函数或者基类的成员函数才能访问它。

例11.1 公有派生例。

#include

class A {

int x;

protected:

int y;

public:

int z;

A(int a,int b,int c)

{

x=a;y=b;

z=c;

}

void Setx(int a){x=a;}

void Sety(int a){y=a;}

int Getx(){return x;}

int Gety(){return y;}

void ShowB()

{ cout<< "x="<

}

};

class B:public A{

int Length,Width;

public:

B(int a,int b,int c,int d,int e):A(a,b,c) //D

{

Length=d;Width=e;

}

void Show()

{ cout<< "Length="<

cout<<"x="<

}

int Sum()

{

return ( Getx()+y+z+Length+Width); //F

}

};

void main(void)

{

B b1(1,2,3,4,5);

b1.ShowB(); //G

b1.Show();

cout<< "Sum="<

cout << "y=" <

cout << "z="<

}

执行程序后输出以下五行:

x=1 y=2 z=3

Length=4 Width=5

X=1 y=2 z=3

Sum=15

y=2 z=3

D 行“:”后的 A(a,b,c)的作用是在派生类的构造函数中要调用基类的构造函数A(a,b,c)。初始化基类成员的一般方法在下一节中介绍。E行中直接使用了基类中的保护成员y和公有成员z,这是允许的。在派生类的成员函数中,不能直接使用基类的私有成员,如F行:

return ( Getx()+y+z+Length+Width);

改为:

return ( x+y+z+Length+Width);

则是不允许的(出现编译错误)。因x是基类的私有成员,在公有派生类中不能直接使用它,必须通过基类的公有成员函数Getx()来获取x的值。主函数中的G行,直接使用了基类中的公有成员函数ShowB(),H行中直接使用了基类的公有成员z。

从程序中可以看到,当一个类从一个基类公有派生时,基类中的公有成员就如同派生类中定义的公有成员一样,在类外可以通过派生类的对象名与成员名一起来直接使用它。基类中的保护成员,只能在派生类的成员函数中直接使用它,而在派生类之外不能直接使用它。对于基类而言,派生类也属于基类的“外部”,因此,派生类中定义的成员函数也不能直接使用基类中的私有成员。如上面例子中的y,在派生类B中可以直接访问它,而在主函数中不能直接访问它。如在主函数中直接输出y:

cout << b1.y<<'\n';

是不允许的,必须改写成:

cout << b1.Gety()<<'\n';

11.1.2.2 私有派生

对于私有派生类而言,其基类中公有成员和保护成员在派生类中均变为私有的,在派生类中仍可直接使用这些成员。在派生类之外均不可直接使用基类中的公有或私有成员,这些成员必须通过派生类中的公有成员函数来间接使用它们。同样地,对于基类中的私有成员,在派生类中不可直接使用,只能通过基类的公有或保护成员函数间接使用它们。当然在派生类之外,更是不能直接使用基类中的私有成员。

例11.2 私有派生。

#include

class Base {

int x;

protected:

int y;

public:

int z;

Base(int a,int b,int c)

{

x=a;y=b;

z=c;

}

void Setx(int a){x=a;}

int Getx(){return x;}

int Gety() { return y;}

};

class Inh:private Base{

int Length,Width;

public:

Inh(int a,int b,int c,int d,int e):Base(a,b,c)

{

Length=d;Width=e;

}

void Show()

{ cout<< "Length="<

cout<<"x="<

}

int Sum(void)

{

return ( Getx()+y+z+Length+Width); //F

}

};

void main(void)

{

Inh b1(1,2,3,4,5);

b1.Show();

cout<< "Sum="<

}

尽管基类中的成员函数Gety()是公有的,但由于是私有派生,该函数成为派生类Inh中的私有成员函数。在主函数中企图使用如下语句输出对象b1的成员y的值:

cout << "y=" <

是不允许的。同理,基类中的公有成员z,经私有派生后成为派生类的私有成员,在主函数中也不能直接输出b1的成员z的值:

cout << "z="<

用得最多的是公有派生,私有派生的方式用得比较少。而保护的(protected)派生极少使用,这里不作介绍了。

综上所述,在派生类中,继承基类的访问权限可以概括为表11.1。

表11.1 公有和私有派生

派生方式 基类中的访 基类成员在派 派生类之外的函数能否访

问权限 生类中的访问 问基类中的成员

权限

public public public 可访问

public protected protected 不可访问

public private 不可访问 不可访问

private public private 不可访问

private protected private 不可访问

private private 不可访问 不可访问

11.1.2.3 抽象类与保护的成员函数

当定义了一个类,这个类只能用作基类来派生出新的类,而不能用这种类来定义对象时,称这种类为抽象类。当对某些特殊的对象要进行很好地封装时,需要定义抽象类。当把一个类的构造函数或析构函数的访问权限定义为保护的时,这种类为抽象类。在定义这种类的对象时,在类的外面要调用这个类的构造函数,因构造函数是私有的,所以这种调用是不允许的,即不能产生这种类的对象。同样地,当把类的析构函数的访问权限说明为保护的,且在撤消对象时,也是在对象外调用析构函数,所以这种调用也是不允许的。但是,当用抽象类作为基类来产生派生类时,在派生类中可以调用其基类的保护成员,在产生派生类的对象和撤消派生类的对象时,是在派生类的构造函数中调用基类的构造函数,或者是在派生类析构函数中调用基类的析构函数,这种调用是允许的。因基类中的保护成员,在派生类中可以象公用成员一样使用。

当把类中的构造函数或析构函数说明为私有的时,所定义的类通常是没有任何实用意义的,一般情况下,不能用它来产生对象,也不能用它来产生派生类。

11.1.3 多重继承

用多个基类来派生一个类时,其一般格式为:

class 类名:类名1,类名2,..., 类名n

{

private:

...... ; //私有成员说明;

public:

...... ; //公有成员说明;

protected:

...... ; //保护的成员说明;

};

其中派生类“类名”继承了类名1到类名n的所有数据成员和成员函数,每一个基类的类名前的Access用以限定这基类中的成员在派生类中的访问权限,其规则与单一继承的用法类同。同样地,Access可以是三个关键字public,private和protected之一。从这种一般格式可以看出,很容易将单一继承推广到多重继承。

例11.3 多种继承。

#include

class A { //描述一个圆,(x,y)为圆心,r为半径

float x,y,r;

public:

A(float a,float b,float c)

{

x=a;y=b;r=c;

}

void Setx(float a){x=a;}

void Sety(float a){y=a;}

void Setr(float a){r=a;}

float Getx(){return x;}

float Gety(){return x;}

float Getr(){return r;}

float Area(){return (r*r* 3.14159);}

};

class B {

float High;

public:

B(float a)

{

High=a;

}

void SetHigh(float a){High=a;}

float GetHigh(){return High ;}

};

class C:public A,private B //描述一个圆柱体{

float Volume; //圆柱体的体积

public:

C(float a,float b,float c,float d):A(a,b,c),B(d) //D

{

Volume=Area() * GetHigh(); //E

};

float GetVolume(){return Volume;}

};

void main(void)

{

A a1(6,8,9);

B b1=23;

C c1(1,2,3,4);

cout << "x="<

cout << "r="<

cout <<"High="<

cout << "Volume="<

}

在上例中,类A描述了一个圆,类B描述了高度。类C由二个基类A(公有派生)和B(私有派生)派生而来的,它描述了一个圆柱体,它包含了这二个类的所有成员,并增加了描述圆柱体体积的新成员Volume。D行中,派生类C的构造函数调用基类A和基类B的构造函数。由于类A和类B中的数据成员都是私有的,在类C的成员函数中不能直接使用这二个基类中的数据成员。因此,在E行中只能使用基类的公有成员函数来间接使用基类中的数据成员。