第5章 继承与派生.docx

上传人:b****7 文档编号:10914181 上传时间:2023-02-23 格式:DOCX 页数:81 大小:60.10KB
下载 相关 举报
第5章 继承与派生.docx_第1页
第1页 / 共81页
第5章 继承与派生.docx_第2页
第2页 / 共81页
第5章 继承与派生.docx_第3页
第3页 / 共81页
第5章 继承与派生.docx_第4页
第4页 / 共81页
第5章 继承与派生.docx_第5页
第5页 / 共81页
点击查看更多>>
下载资源
资源描述

第5章 继承与派生.docx

《第5章 继承与派生.docx》由会员分享,可在线阅读,更多相关《第5章 继承与派生.docx(81页珍藏版)》请在冰豆网上搜索。

第5章 继承与派生.docx

第5章继承与派生

第5章继承与派生

面向对象程序设计有4个特点:

抽象、封装、继承和多态性。

前面3章学习了对类和对象的最基本操作,对抽象性和封装性有了足够的认识。

下面我们要来了解继承性和多态性。

继承是C++语言的一种重要机制,该机制自动地为一个类提供来自另一个类的操作和数据结构,这使得程序员只需在新类中定义已有类中没有的成分来建立新类。

有了继承,过去的代码就可以不被丢弃,只要稍加修改就可重用。

类的继承使简单的代码慢慢发展到丰富的高级代码,程序会越来越完善,功能越来越强大。

没有继承就等于没有面向对象的程序设计,继承性是面向对象程序设计中最重要的特性。

当然继承的过程面临许多问题,下面我们来一一解答。

5.1继承与派生的概念

C++中可重用性是通过继承来实现的。

因此,继承是C++的一个重要组成部分。

前面介绍了类,一个类中包含数据成员和成员函数。

不同的类中,数据成员和成员函数是不相同的。

但有时两个类的内容基本相同或一部分相同。

这样自然就想到,能否利用原来声明的类作为基础以减少重复工作量。

C++的继承机制就可以解决这个问题。

在C++中,所谓”继承”就是在一个已存在的类的基础上建立一个新的类。

已存在的类称为基类或父类。

新建立的类称为派生类或子类。

其实在现实生活中就存在继承关系。

如下所示。

上图是交通工具的类层次,最顶部的交通工具为基类。

这个基类会有飞机类、汽车类、火车类等等,图中只画出了汽车类。

汽车类以交通工具类为父类,相对交通工具类来说汽车类是子类。

汽车类有三个子类,小汽车类、卡车类、旅行车类,它们以汽车类作为父类,此时交通工具类是它们的祖先类,简称祖类。

图中展示了四层结构,从上到下它们是派生的关系,从下到上是继承的关系。

面向对象程序设计可以让你声明一个新类作为另一个类的派生。

派生类(也称子类)继承它父类的属性和操作。

子类也声明了新的属性和新的操作,剔除了那些不适合于其用途的继承下来的操作。

这样,继承可让你重用父类的代码,专注于为子类编写新代码。

那些父类已经存在,在新的应用中,你无须修改父类。

所要做的就是派生子类,在子类中做一些增加与修改。

这种机制,使重用成为可能。

我们来看P150中类Student和类Student1的定义,这两个类中有很多相同的部分,只是Student1中新增加了二个数据成员,成员函数display也有了一些变化。

这样我们可以在Student的基础上加上新的内容来定义Student1,以减少重复工作量。

以上介绍的是最简单的情况:

一个派生类只从一个基类派生,这称为单继承,如果一个派生类有两个或多个基类的称为多重继承。

如下图所示为最简单的单继承结构图

 

 

下二个图为最简单的多重继承的结构图

5.2派生类的声明方式

声明派生类的一般形式为:

class派生类名:

[继承方式]基类名//继承方式包括public、private(默认)、protected

{

派生类新增加的成员

}

派生类对象包括两个部分,一个为基类部分,另一个是派生类部分。

派生类部分总是依附于基类的,派生类对象中总是含有基类的成员。

显然,派生类对象比基类对象大,它保存了更多的数据,提供了更多的操作。

//定义书中提供的Student1,Student作为基类。

#include

#include

usingnamespacestd;

classStudent

{public:

Student()

{cin>>num>>name>>sex;}

voiddisplay()

{cout<<"num:

"<

cout<<"name:

"<

cout<<"sex:

"<

private:

intnum;

stringname;

charsex;

};

classStudent1:

publicStudent//声明基类为Student,定义派生类是Student1

{public:

Student1()

{cin>>age>>addr;}

voiddisplay_1()//新增加的成员函数

{cout<<"age:

"<

cout<<"address:

"<

private:

intage;//新增加的数据成员

stringaddr;//新增加的数据成员

};

5.3派生类的构成

派生类中的成员包括两大部分:

1)基类继承过来的成员2)自己增加的成员。

从基类继承的成员体现了派生类从基类继承而获得的共性,而新增加的成员体现了派生类的个性。

为了形象地表示继承关系,采用图来示意。

P154图5.6

构造一个派生类包括以下3部分工作:

●从基类接收成员。

派生类毫无选择地接收除构造函数和析构函数之外的基类全部成员。

所以声明派生时,要自己定义构造函数和析构函数。

派生类中用不到也继承过来了,造成了数据的冗余,这是C++目前无法解决的问题。

●调整从基类接收的成员。

可以改变基类成员在派生类中的访问属性;可以在派生类中声明一个与基类成员同名的成员,覆盖基类同名成员。

这里说的同名可不是说名字相同而已,还要参数也完全相同。

●在声明派生类时增加新的成员。

5.4派生类成员的访问属性

派生类中包含基类和派生类自己增加的成员,这样就得考虑以下6个问题,这6个问题是两个成对的。

●基类成员函数访问基类成员。

类中可以访问自己的成员,这点是肯定的。

●派生类的成员函数访问派生类自己增加的成员。

类中可以访问自己的成员,这点也是肯定的。

●基类的成员函数访问派生类的成员。

基类的成员函数只能访问基类的成员,而不能访问派生类的成员。

●派生类的成员函数访问基类的成员。

前面提到过,派生类继承了除构造函数的析构函数之外的基类全部成员,那么是不是就可以访问基类中的所有成员了呢?

这与基类的访问属性和继承方式有关。

这是我们要讲解的部分。

●在派生类外访问派生类的成员。

在类外只能访问该类的公有成员,所以,在派生类外也只可以访问派生类的公有成员,而不能访问派生类的私有成员。

●在派生类外访问基类的成员。

因为在类外只能访问该类的公有成员,所以在派生类外只能访问继承后仍具有公有成员特性的成员。

这与基类的访问属性和继承方式有关。

这是我们要讲解的部分。

以上6个问题中,只有其中用红色标识的是我们要讨论的。

我们知道这两个问题都与成员的访问属性有关。

访问属性共有3种:

public:

公有的。

可以被本类中的成员函数引用,也可以在类外被使用(当然要在作用域内)

private:

私有的。

只能被本类中的成员函数引用,类外不能调用。

protected:

不能被类外引用,但可以被派生类的成员函数访问。

我们知道这两个问题还都与继承方式有关。

继承方式共有3种:

1)公有继承public:

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

2)私有继承private:

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

其私有成员仍为基类私有。

3)受保护的继承protected:

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

从继承方式可以看出,基类中的私有成员仍为基类私有,即派生类中是不能访问的。

下面我们来分别讲述这三种继承方式。

5.4.1公有继承

//自编,为了说明问题,成员共有3种访问属性。

#include

#include

usingnamespacestd;

classStudent

{

private:

intnum;

stringname;

protected:

charsex;

public:

set(intn,stringnam,chars)//没有用构造函数,派生类无法继承构造函数,

{

num=n;name=nam;sex=s;

}

voiddisplay()

{

cout<<"num:

"<

cout<<"name:

"<

cout<<"sex:

"<

}

};

classStudent1:

publicStudent//公有继承

{

intage;

stringaddr;

public:

voiddisplay1()

{

//cout<<"num:

"<

//cout<<"name:

"<

display();//公有成员可以在派生类中访问

cout<<"sex:

"<

cout<<"age:

"<

cout<<"addr:

"<

}

};

intmain()

{

Students;

s.set(1,"Li",'F');

s.display();

Student1s1;

s1.display1();

return0;

}

由上例可以看出基类在派生类中的访问情况

基类的成员

公有派生类中的访问属性

私有成员

不可访问

公有成员

可以访问(公有)

保护成员

可以访问(保护)

5.4.2私有继承

将上例改为私有继承后运行程序可以得到下表

基类的成员

私有派生类中的访问属性

私有成员

不可访问

公有成员

可以访问(私有)

保护成员

可以访问(私有)

5.4.3保护继承

将上例改为保护继承后运行程序可以得到下表

在类中,还有一种保护(protected)型的访问控制符,保护成员与私有成员一样,不能被使用类的程序员进行访问(也就是可以在类外访问),但可以被类内部的成员函数访问。

除此之外,派生类成员是可以访问的,这就是与私有成员的区别所在。

基类的成员

私有派生类中的访问属性

私有成员

不可访问

公有成员

可以访问(保护)

保护成员

可以访问(保护)

综合以上三张表,可得出以下的表

基类中的成员

公有派生

私有派生

保护派生

私有

不可访问

不可访问

不可访问

公有

可访问(公有)

可访问(私有)

可访问(保护)

保护

可访问(保护)

可访问(私有)

可访问(保护)

从以下的表中可以看出,在直接派生中,可访问情况结果是一样的,但如果再继续派生,它们就会有所不同了。

这个问题在下面的多级派生中讲解。

下面再来看一个实例

//思考题:

自编,继承父类成员的简单实例

#include

usingnamespacestd;

classPoint

{

intX,Y;

public:

voidsetPoint(intx,inty){X=x;Y=y;}

voidmove(intxoff,intyoff){X+=xoff;Y+=yoff;}

intgetX(){returnX;}

intgetY(){returnY;}

voiddisplayPoint(){cout<

};

classRectangle:

publicPoint

{

intW,H;

public:

voidsetRectangle(intx,inty,intw,inth){setPoint(x,y);W=w;H=h;}

intgetW(){returnW;}

intgetH(){returnH;}

//voiddisplayRectangle(){cout<

会出现错误吗?

如果有错,该怎么修改?

};

intmain()

{

Rectanglerect;

rect.setRectangle(2,3,10,20);

rect.move(2,3);

//cout<

会出现什么错误?

cout<

return0;

}

从上例我们可以了解到派生类Rectangle从基类Point公有继承,所以继承了基类中的全部公有成员和私有成员(构造函数与析构函数除外),并可以访问基类中的公有成员setPoint、move、getX、getY和displayPoint,此外,它还有比父类多一些的信息,即它有W、H数据成员以及setRectangle、getW、getH、displayRectangle成员函数。

下面来看一下书中的实例。

//例5.3在派生类中引用保护成员

#include

#include

usingnamespacestd;

classStudent//声明基类

{public:

//基类公用成员

voiddisplay();

voidget_value(){cin>>num>>name>>sex;}

protected:

//基类保护成员

intnum;

stringname;

charsex;

};

voidStudent:

:

display()

{cout<<"num:

"<

cout<<"name:

"<

cout<<"sex:

"<

}

classStudent1:

protectedStudent//用protected继承方式声明一个派生类

{public:

voiddisplay1();

voidget_value1(){cin>>age>>addr;}

private:

intage;

stringaddr;

};

voidStudent1:

:

display1()

{cout<<"num:

"<

cout<<"name:

"<

cout<<"sex:

"<

cout<<"age:

"<

cout<<"address:

"<

}

intmain()

{

Student1stud1;//stud1是派生类student2的对象

stud1.get_value();

stud1.get_value1();

stud1.display1();//display是派生类中的公用成员函数

return0;

}

下面再来看一个实例。

//思考题:

自编。

#include

#include

usingnamespacestd;

//-------------------------------------

classBase

{

inta;

voidf(){cout<

public:

intb;

voidg(){cout<

protected:

intc;

voidk(){cout<

};//-----------------------------------

classDerived:

publicBase{//改为private、protected,再看结果

public:

voiddf(){

//cout<

cout<

cout<

//f();在派生类不能访问私有成员函数

g();//在派生类中可以访问公有成员

k();//在派生类中可以访问保护成员

}

};

voidfunc(){

Baseb;

//cout<

cout<

//cout<

//b.f();//在类外不可以访问私有成员

b.g();//在类外可以访问公有成员

//b.k();//在类外不可以访问保护成员

}//====================================

voidmain()

{

}

从上面的实例中可以了解到以下信息:

 

在基类中对基类成员的访问能力

公有派生类对基类成员的访问能力

在类外对基类成员的访问能力

公有

保护

×

私有

×

×

那么如果是保护派生呢?

情况会怎么样?

通过程序的运行,可以知道与上述情况完全一样。

如果私有派生呢?

情况也一样,那到底有什么区别呢?

在多级派生中可以得到答案。

5.4.4多级派生时的访问属性

下图是一个典型的多级继承结构图,我们用程序来描述。

 

//例5.4多级派生的访问属性

classA//基类

{public:

inti;

protected:

voidf2();

intj;

private:

intk;

};

classB:

publicA//public方式

{public:

voidf3();

protected:

voidf4();

private:

intm;

};

classC:

protectedB//protected方式

{public:

voidf5();

private:

intn;

};

intmain()

{

return0;

}

我们用一个表格形式来看各成员在不同类中的访问情况:

成员

i

f2

j

k

f3

f4

m

f5

n

基类A

公有

保护

保护

私有

×

×

×

×

×

公有派生类B

公有

保护

保护

不可访问×

公有

保护

私有

×

×

保护派生类C

保护

保护

保护

不可访问×

保护

保护

不可访问×

公有

私有

如果是保护派生,访问情况如下表:

成员

i

f2

j

k

f3

f4

m

f5

n

基类A

公有

保护

保护

私有

×

×

×

×

×

保护

派生类B

保护

保护

保护

不可访问×

公有

保护

私有

×

×

保护派生类C

保护

保护

保护

不可访问×

保护

保护

不可访问×

公有

私有

如果是私有派生,访问情况如下表:

成员

i

f2

j

k

f3

f4

m

f5

n

基类A

公有

保护

保护

私有

×

×

×

×

×

私有

派生类B

私有

私有

私有

不可访问×

公有

保护

私有

×

×

保护派生类C

不可访问×

不可访问×

不可访问×

不可访问×

保护

保护

不可访问×

公有

私有

通过以上分析可知,无论是哪一种继承方式,在派生类中都不能访问基类中的私有成员,如果在多级派生时采用公用继承方式,直到最后一级派生类都能访问基类的公有成员和保护成员。

如果采用私有继承方式,经过若干派生后,基类的所有的成员都不可访问了。

如果采用保护继承方式,在派生类外是无法访问派生类中的任何成员的。

在经过多次派生后,人们很难记清楚各成员的访问情况,因此在实际应用中多采用公有继承。

//思考题:

阅读下列程序,画出访问情况表。

classA{

inti;

public:

voidf1();

protected:

voidf2();

};

classB:

publicA{

intm;

public:

voidf3();

intk;

};

classC:

protectedB{

intn;

public:

voidf4();

protected:

intm;

};

classD:

privateC{

intq;

public:

voidf5();

protected:

intp;

};

成员

i

f1

f2

B:

:

m

f3

k

n

f4

C:

:

m

q

f5

p

基类A

私有√

公有√

保护√

B公有继承

×

公有√

保护√

私有√

公共√

公共√

C保护继承

×

保护√

保护√

×

保护√

保护√

私有√

公共√

保护√

D私有继承

×

私有√

私有√

×

私有√

私有√

×

私有√

私有√

私有√

公共√

保护√

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

在第3章中讲过,用户如果没有定义构造函数,C++会自动提供给我们一个默认的构造函数。

在定义类对象时会自动调用默认的构造函数。

基类的构造函数是不能继承的,所以在声明派生类时,不仅要考虑派生类所增加的数据成员的初始化,还要考虑基类的数据成员的初始化。

5.5.1简单的派生类中的构造函数

简单的派生只有一个基类,而且只有一级派生,在派生类的数据成员中不包含子对象(子对象问题在下面马上讲解)。

先来看一个最简单的实例。

//自编,是简单的继承。

隐含调用默认构造函数

#include

usingnamespacestd;

classA

{

public:

A(){cout<<"A"<

};

classB:

publicA

{

public:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 自然科学 > 物理

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1