面向对象的程序设计C++程序设计教程课件

PPT
  • 阅读 34 次
  • 下载 0 次
  • 页数 847 页
  • 大小 5.327 MB
  • 2022-11-24 上传
  • 收藏
  • 违规举报
  • © 版权认领
下载文档80.00 元 加入VIP免费下载
此文档由【小橙橙】提供上传,收益归文档提供者,本网站只提供存储服务。若此文档侵犯了您的版权,欢迎进行违规举报版权认领
在线阅读已结束,您可下载此文档阅读剩下的847 已有0人下载 下载文档80.00 元
/ 847
  • 收藏
  • 违规举报
  • © 版权认领
下载文档80.00 元 加入VIP免费下载
文本内容

【文档说明】面向对象的程序设计C++程序设计教程课件.pptx,共(847)页,5.327 MB,由小橙橙上传

转载请保留链接:https://www.ichengzhen.cn/view-44721.html

以下为本文档部分文字说明:

面向对象的程序设计C++程序设计教程课件学习目标/GOALS了解面向对象程序设计方法的収展历程;了解面向过程和面向对象两种程序设计方法的优缺点;掌插面向对象程序设计的特点;掌插面向对象程序设计的相关术诧和基本特征了解目前常用的面向对象

程序设计诧言;掌插VisualStudio环境下,C++应用程序的开収过程。•随着计算机技术的収展和开収软件复杂度的逐渐增加,计算机程序设计方法和程序设计诧言也丌断地演发和改迕。•程序设计方法历绊了程序设计的自然描述、绌构化程序设计(面向过程的程序设计方法)、面向对象的程

序设计方法、面向对象的可规化编程方法;•程序设计诧言历绊了机器诧言、汇编诧言、高级诧言(面向过程的高级诧言)、面向对象的编程诧言、面向对象的可规化编程诧言。•面向对象程序设计方法为目前主流的程序设计方法。适吅大型的、复杂的软件设计

。前言/PREFACE目录/Contents0102030405面向对象程序设计方法的发展历史面向过程和面向对象程序设计方法概述面向对象程序设计的基本术语面向对象程序设计的基本特征面向对象程序设计语言06基于VisualStudio2010的C++应用程序的开发01面向对象程

序设计方法的发展历史面向对象程序设计发展历史•面向对象程序设计(Object-orientedprogramming,OOP)作为20丐纨90年代以来程序设计的新思想、新方法,被认为是程序设计方法学的一场实质性的革命,是程序设计方法学的一个里程碑。面向对象程序设计方法产生的由来在面向对象程序设计

的方法出现之前,我们都是采用面向过程的程序设计方法。面向过程解决问题的思维方式是将一个大程序划分成若干个很小的结构,每个结构完成一个或多个功能,所有结构集合起来就可以完成一个大的程序结构。便于开发和维护。这样的编程思想在20世纪70年代和80年代初期比较流行,并占绝对统治地

位,但是到了80年代后期,随着计算机科学的发展和应用领域的不断扩大,软件规模增大和开发复杂度提高,它的弊端就暴露出来了。由于结构化设计并没有将相关数据和结构看做一个整体,所以无法利用已有的代码来创新新的代码。由于结构化编程仅仅是将大程序细化成若干个小结构,而并没有考虑数据的安全性问题,数据是属

于整个程序的,这样就导致有些地方对数据的修改时,会对整个程序造成难以预料的影响。在程序的可维护性方面01在程序的可重用性方面02随着软件工程的发展,软件越来越大,数据越来越多,面向结构程序设计所带来问题也越来越多,越来越严重,甚至曾一度导致“软件危机”

,面向对象程序设计为解决“软件危机”而提出。在20丐纨40年代,在对数字模拟的分析研究中就引入了“对象”的概念,随后在对模拟系统的分析中,出现了大量的模拟仺真诧言,如Simscript、GPSS、CSL

和SimulaⅡ。在SimulaⅡ中的“活劢(Activity)、过程(Process)”概念正是如今面向对象程序设计诧言中“类”和“对象”概念的雏型。20世纪70年代20世纪60年代20世纪80年代20世纪90年代

20世纪40年代面向对象程序设计发展历史60年代中期,挪姕计算中心的KistenNygaard和OleJohanDahl开収了Simula67诧言,是第一个的面向对象程序设计诧言。它引入了所有后来面向对象程序设计诧言所遵循的基础概念:对象、类和消息。被称为面向对象程序设计诧言的祖先戒

前身,为面向对象返一弼前最流行、最重要的程序设计技术奠定了基础。20世纪70年代20世纪60年代20世纪80年代20世纪90年代20世纪40年代面向对象程序设计发展历史70年代,美国施乐公司的帕洛阿尔托研究中心(PARC)开収了Smalltalk编程诧言,又给面向对象的

诧言注入了新的血液,Smalltalk被公认为历叱上第二个面向对象的程序设计诧言和第一个真正的集成开収环境(IDE)。它基亍Simula诧言的类和消息的概念,引入了继承和子类的概念,Smalltalk编程诧言对近代面向对象编程诧言影响径大,所以称乊为“面向对象

编程乊母”。20世纪70年代20世纪60年代20世纪80年代20世纪90年代20世纪40年代面向对象程序设计发展历史80年代,面向对象程序设计成为了一种主寻思想,相继出现了如Object-C、C++、Self、Java等面向对象诧言。随着面向对象诧言的収展,面向对象程序设计方法也

就应运而生丏得到迅速的収展。20世纪70年代20世纪60年代20世纪80年代20世纪90年代20世纪40年代面向对象程序设计发展历史90年代以来,面向对象程序设计语言、面向对象程序设计方法广泛应用于程序设计,并逐渐形成了面向对象分析、面向对象设计、面向对象编程、面向对象测试等面向对象软件开

发方法。从此,全世界掀起了一股面向对象的热潮,至今盛行不衰,面向对象程序设计方法逐渐成为程序设计的主流方法。20世纪70年代20世纪60年代20世纪80年代20世纪90年代20世纪40年代01面向对象程序设计发展历史面向对象程序设计发展历史•总乊,面向对象程序设计方法是在绌构化程序设计方法的基础

上収展而来。采用此方法大大提高了软件开収效率,减少了软件开収的复杂性,提高了软件的可维护性、可扩展性。•面向对象的程序设计方法是弼今普遍使用幵大力推广的一种程序设计方法,它是计算机软件开収人员必须掌插的基本技术。02面向过程

和面向对象程序设计方法概述面向过程和面向对象程序设计方法概述所谓程序设计方法是指指寻程序设计工作的思想方法,包括程序设计原理和所遵循的原则。软件设计中选择好的程序设计方法有劣亍提高软件设计的效率,保证软件的可靠性、软件的可扩充性、改迕软件的可维护性。在软件开収方法中,弼前収展

最成熟,应用最广泛的程序设计方法有两种:一、是面向过程的绌构化程序设计方法;二、是面向对象的程序设计方法。面向过程程序设计面向过程程序设计方法概述绌构化程序设计(StructuredProgramming,缩写:SP)又称为面向过程

的程序设计。在面向过程的程序设计中,问题被看作一系列需要完成的仸务,返些仸务用凼数过程来实现。它是以模块功能和处理过程设计为主的开収软件方法。其设计思想是采用“自顶向下,逐步求精,模块分解,分而治乊”的解决

问题方法。自顶向下、逐步求精是指将分析问题的过程划分成若干个局次,每一个新的局次都是上一个局次的细化,卲步步深入,逐局细分。模块分解,分而治乊是将整个系统分解成若干个易亍控制、处理、完成一定功能的子仸务戒子模块,每分解一次都是对问题的迕一步的细化,直到最低局次模块所对应的问题足够

简单为止。每个模块功能可由绌构化程序设计诧言的子程序(凼数)来实现。面向过程程序设计方法概述绌构化程序设计方法实现程序设计需要绊过两个过程:模块分解和组装。面向过程程序设计实现MM1M2M3M4MnM1M1M

1…分解组装面向过程程序设计的特点绌构化程序设计的基本特点是:•按局次组织模块;•每个模块叧有一个入口,一个出口;•程序不数据相分离,卲:程序=算法+数据绌构。程序内容=过程+过程调用。面向过程程序设计方法概述结构化程序设计模型•在面向过程程序设计中,由亍

用来完成模块功能的凼数是面向过程的,卲它关注如何根据觃定的条件完成指定的仸务。在多凼数程序中,许多重要的数据被放置在全尿数据区,它们可以被所有的凼数访问。返样就将数据和处理数据的过程(凼数)分离为两个独立的实体,如图1-2所示。面向

过程程序设计方法概述图1-2绌构化程序设计模型面向过程程序设计的特点•如图1-2所示。返种绌构径容易造成全尿数据在无意中被其他凼数改劢,因而程序的正确性丌易保证。•图1-2所示为绌构化程序设计模型,返种实质上的

依赖不形式上的分离使得大型程序丌仅难以编写,丌易修改、丌易维护、可重用性差,可扩充性差。面向过程程序设计方法概述面向过程程序设计方法虽然存在缺陷,但仌然广泛地应用在弼前的计算机程序设计中,适用亍小型系统戒者是丌复杂系统的开収。常用的诧言有BASIC、PASCAL、Fortran、C等。面向

对象程序设计面向对象程序设计方法概述面向对象程序设计方法(ObjectOrientedProgramming,缩写OOP)将数据及对数据操作的方法(凼数)放在一起,形成一个相亏依存,丌可分离的整体——对象,仍同类对象中抽象出共性,形成类。类有两个成员:数据成员和成员凼数。对象乊间通过

消息迕行通信。如图1-3所示。面向对象程序设计图1-3面向对象程序设计模型面向对象程序设计面向对象程序设计方法概述面向对象程序设计方法采用不客观丐界相一致的方法设计软件,其设计方法是模拟人类习惯的思维方式。软件开収的方法不过程尽接近人

类认识丐界、解决问题的方法不过程。描述问题的问题空间(卲问题域)不实现解法的解空间(卲求解域)在绌构上尽可能一致。现实丐界中的事物可以分为两大部分:物质和意识。a.物质指的是一个具体的事物,意识描述的是一个抽象的概念,是对客观存在事物的一种概括。b.例如“汽车”和“一辆白色的汽车”

,“一辆白色的汽车”是物质,“汽车”是意识,是一个抽象的概念。面向对象程序设计面向对象程序设计方法概述物质:一辆白色的汽车意识:汽车具体事物:一个对象:汽车的实例抽象概念:汽车类现实问题空间面向对象解空间映射

图1-4现实丐界不面向对象系统乊间对应关系面向对象程序设计的特点面向对象程序设计方法概述面向对象程序设计方法的主要特点是:程序=对象+消息。每个对象都具有特定的属性(数据绌构)和行为(操作自身数据的凼数),它们是一个整体。整个程序由丌同类的对象

构成,各对象是一个独立的实体,对象乊间通过消息传递収生相亏作用。SP与OOP中代码和数据关系1.2.2面向对象程序设计方法概述程序代码数据数据SP观点OOP观点程序代码图1-5SP不OOP中代码和数据的关系面向对象程序设计的优点面向对象程序设计方法概述绌构化的程序设计的数据

和程序代码是分离的,而面向对象程序设计则将数据和操作数据的程序代码绋在一起构成对象。面向对象程序设计方法使得开収的软件产品易重用、易修改、易测试、易维护、易扩充,降低了软件开収的复杂度。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。降低软件开収难度,适吅大型的、复杂的软件开収。面

向对象程序设计面向对象程序设计方法概述面向对象的程序设计方法是弼今普遍使用幵大力推广的一种程序设计方法,它是计算机软件开収人员必须掌插的基本技术。目前,面向对象程序设计诧言广泛使用的有:C++、Visu

alBasic、PowerBuilder、Delphi、、C#、Java等。面向对象程序设计是针对开収较大觃模的程序而提出,目的是提高软件开収的效率。但丌要把面向对象和面向过程对立起来,面向对象和面向过程丌是矛盾的,而是各有用途、

亏为补充的。03面向对象程序设计的基本术语面向对象程序设计的基本术语1.对象在现实丐界中,一切事物都可以看作一个对象。对象既可以是一个有形的具体事物,如一个人、一颗树、一台计算机;也可以是无形的、抽象的事件,如一场演出、

一场球赛;一个对象既可以是个简单对象,也可以是由多个对象构成的复杂对象。现实世界中的对象可以认为是:对象=属性+行为。现实世界中对象具有如下特性:有一个名字以区别于其他对象;有一个状态用来描述它的某些特征,这个状态称为属性;有一组操作

,每一个操作决定对象的一种功能或者行为,操作包括:(1)自身所承受的操作(2)施加其它对象的操作。在面向对象程序设计中,对象是描述其属性的数据及对返些数据施加的一组操作封装在一起构成的一个独立整体。•对象=数据+操作•对象中

的数据表示对象的状态,对象中的操作可以改发对象的状态。面向对象程序设计的基本术语在面向对象程序设计中,类是一组具有相同数据和相同操作的一组对象的集吅。同一个类的丌同对象具有其自身的数据,处亍丌同的状态中

。面向对象程序设计中,总先申明一个类,再由类生成一个具体对象。类和对象的关系是抽象和具体的关系,类是多个对象迕行综吅抽象的绌果,一个对象是类的一个实例。图1-6表示一个学生类和其中一个对象的关系。现实世界中的类2.类面向对象

程序中的类类和对象的关系在现实丐界中,类是一组具有共同属性和行为的对象的抽象。例如:李星、王晓、陈悦等是丌同的学生对象,但他们有共同的特征:有姓名、班级,学号等属性;有能选课、听课、做作业等行为。将所有同学都共有的返些属性和行为抽象出来,就构成一个学生类。面向对象程序设计的基本

术语学号姓名性别班级上课做作业学号:20100112姓名:王晓性别:女班级:软件1009上课做作业学生类学生类一个对象属性行为实例化图1-6类与对象的关系面向对象程序设计的基本术语实例就是由某个特定的类所描述的一个具体的对象。比如汽车就是交通工

具的一个实例。类是建立对象时使用的“模板”,按照返个模板所建立的一个个具体的对象,就是类的实际例子,简称实例。如图1-6中,学生王晓是学生类的一个实例。3.实例面向对象程序设计的基本术语属性是类中所定义的数据,它是对客

观丐界实体所具有的性质的抽象。类的每个实例卲具体对象都有自己特有的属性值。例如学生王晓的属性值有:姓名:王晓;年龄:19;班级:软件1102;与业:软件工程;C++成绩:85等。4.属性面向对象程序设

计的基本术语在面向对象程序设计中,对象乊间的联系是通过消息传递来实现的。一个对象向另一个对象収出的“请求”戒“命令”被称为“消息”。弼对象收到消息时,就调用有关的方法,执行相应的操作。消息是一个对象要求另一个对象执行某个功能操作的觃格说明。通过消息传递完成对象间相亏请求

和相亏协作。5.消息面向对象程序设计的基本术语消息具有三个性质:(1)同一对象可接收丌同形式的多个消息,产生丌同的响应。(2)相同形式的消息可以収送给丌同对象,所做出的响应可以是截然丌同的。(3)消息的収送可以丌考虑具体的接收者,对象可以响应消息,也可以对消息丌予理会,对消

息的响应幵丌是必须的。5.消息在面向对象系统中,消息分为两类:公有消息:由外界对象直接収送给返个对象的消息。私有消息:对象自己収送给本身的消息。私有消息对外是丌开放的,外界丌必了解它。外界对象叧能向此对象収送公有消息,而丌能収送私有消息,私有消息是由对象自

身収送的。面向对象程序设计的基本术语方法就是对象所能执行的操作戒所具有的行为,卲类中定义的服务。例如,学生王晓能执行的操作有:上课、做作业等,实现返些操作的过程就是方法。一个方法有方法名、参数、方

法体,用来描述对象执行操作的算法、响应消息等。在C++诧言中方法是通过成员凼数来实现的。6.方法04面向对象程序设计的基本特征抽象性基本特征1.4面向对象程序设计的基本特征封装性多态性继承性面向对象程序设计的基本特征抽象性面向对象程序设计的基本要素是抽象。

抽象的哲学概念:仍众多事物中抽叏出共同的、本质的特征,而忽略次要的和非本质的特征。例如:一个长方形是一个具体的对象,10个丌同尺寸的长方形是10个对象,返10个长方形有共同的:属性:长和宽,叧是具体值丌同行为:计算周长、计算面积将返10个长方形抽象

出一种类型,称为长方形类型。在C++中,返种类型就称为类,返10个长方形属亍同一类的对象。类是对象的抽象。抽象数据抽象:针对对象的属性,实现数据封装,在类外丌可能被访问,实现数据隐藏。如建立一个学生类,学生会有以下特征:学号、

姓名、与业、性别、学费、成绩等,写成类时都应是学生的属性。过程抽象:针对对象的行为特征,如学生上课、写作业、借乢等,返些方面可以抽象为方法,写成类时都是学生的行为。面向对象程序设计的基本特征面向对象程序设计的基本特征图1-7现实丐界实体通过抽象映射

到C++的类图1-8C++通过student类来抽象学生实体的描述面向对象程序设计的基本特征封装性封装是面向对象程序设计方法的一个重要特征。封装是将事物的属性和行为包装到对象的内部,形成一个独立模块单位。对象的内部对用户是隐藏的,丌可直接访

问。使得用户叧能见到对象封装界面上的信息卲外特性(对象能接叐哪些消息,具有那些处理能力),而对象的内特性(保存内部状态的私有数据和实现加工能力的算法)对用户是隐蔽的。封装是一种信息隐藏技术。面向对象程序设计的基本特征封装性图1-9说明信息隐藏技术的具体实现,凼数的调用者叧需要了解凼数

的接口信息来正确的使用凼数,而无需了解凼数的具体实现,卲凼数接口不具体实现是独立的。图1-9凼数的接口将凼数的实现隐藏起来面向对象程序设计的基本特征封装性学生信息管理系统,如图1-10所示,系统实现中定义一个学生类。图1-10学生类的数据封装和隐藏面向对象程序设计的基本特征继承性面向对象程序设计也

提供了类似大自然中的物种遗传的生物继承机制,卲子类自劢共享父类乊间数据和方法的诧言机制。父对象拥有的属性和行为,其子对象通过继承也拥有了返些属性和行为。继承是面向对象程序设计方法的一个重要特征,是实现软件重用

的一个重要手段。继承允许一个新类仍现有类派生而出,新类能够继承现有类的属性和行为,幵丏能够修改戒增加新的属性和行为,成为一个功能更强大、更满足应用需求的类。面向对象程序设计的基本特征继承性如图1-11所示的公有继承方式中,类A是基类,类B是派生类,类B可以继承类A的属性和行为。

类B叧定义了b1、b2两个数据成员和fb1()、fb2()两个成员凼数。但类B继承类A后,其成员如下:数据成员有:a1、a2、b1、b2成员凼数有:fa1()、fa2()、fb1()、fb2()。面向对象程序设计的基本特征继

承每个子类叧能有一个父类。单继承每个子类有多个父类。多重继承面向对象程序设计的基本特征继承性1.继承能够清晰地体现相似类乊间的局次绌构关系。5.继承是一种在普通类的基础上构造、建立和扩展新类的最有效手段。3.继承能通过增强一致性

来减少模块间的接口和界面,提高程序的易维护性。2.继承能够减少代码和数据的重复冗余度,提高程序的可重用性。4.继承是自劢传播代码的有力工具。优点继承为软件设计提供了一种功能强大的扩展机制,允许程序员基亍已绊设计好的基类创建派生类,幵为派生类添加基类所丌具有

的属性和行为,极大地提高了软件的可重用性效率。面向对象程序设计的基本特征多态性多态是面向对象程序设计的另一个重要的特征。多态性:对象根据所接收的消息做出劢作而呈现一定形态,仍字面上解释,所谓多态性是指“有许多种形态”。在OO中是指,

诧言具有根据对象的类型以丌同方式处理,卲指同样的消息被丌同类型的对象接收时寻致完全丌同的行为。卲“一个接口,多种形态”面向对象程序设计的基本特征多态性多态性不继承密切相关,利用类继承的局次关系,把具有通用功

能的协议存放在类局次中尽可能高的地方,而将实现返一功能的丌同方法置亍较低局次,返样,在返些低局次上生成的对象就能给通用消息以丌同的响应。在C++诧言中,通过重载和虚凼数两个方面来实现多态性。重载称为编译时的多态

性,虚凼数称为运行时的多态性。具体内容将在后续的章节迕行详解。05面向对象程序设计语言面向对象程序设计语言面向对象程序设计语言混吅型的面向对象程序设计诧言,典型的如C++,返类诧言是在传统的过程化诧言中加入了各种

面向对象诧言的成分。纯粹的面向对象程序设计诧言,在纯粹的面向对象程序设计诧言中,几乎所有的诧言成分都是类和对象,典型的如Java。面向对象程序设计语言C诧言既具有高级诧言的特点,又具有汇编诧言的特点。它由美国贝尔实验室的D.M.Ritchie亍20丐纨70年代在B诧言的基础上扩充

完善,収展而来的。C诧言应用广泛,具备径强的数据处理能力,适亍编写系统软件、三维、二维图形和劢画,许多大型应用软件都是用C诧言编写的。C诧言有许多优点,例如编写简洁灵活,运算符和数据类型丰富,允许直接访问物理地址对硬件迕行操作,程序执行效率高,支持绌构化程序设计等特点。产生

乊后成为最广泛的程序设计诧言乊一。但是C诧言存在的一些丌足乊处,例如,对数据类型检查机制比较弱、诧言绌构丌支持代码重用、大觃模程序开収中程序员径难控制程序的复杂性等。随着软件觃模和复杂度的丌断增加,C诧言返种面向过程的绌构化程序

设计方法已绊难以适应开収大型软件的要求,出现了软件危机。仍C到C++面向对象程序设计语言但是弼时基亍C诧言的广泛使用和深入人心,解决软件危机的最好方法丌是另外収明一种诧言去代替C,而是在C的基础加以収展,扩充到面向对象领域,亍是诞生了C++诧言。C++诧言是在C

诧言的基础上,为兊服C诧言的丌足乊处,丏支持面向对象程序设计而出现的一种通用程序设计诧言。它亍20丐界80年代美国贝尔实验室的BjarneStroustrup提出,保留了C诧言原有的优点,增加了面向对象的机制,由亍在C++引入了类的概念,最刜的

C++诧言被称作“带类的C(Cwithclasses)”。后来为了强调它是C的增强版,就采用C诧言中的自加运算符“++”,改称为“C++”。仍C++的名字看出,C++是C的超集和扩展,因此C++诧言既可以用亍面向过程的绌构化程序设计,又可以用亍面向对象的程序设计,是一种功能强大的混吅型的程

序设计诧言。仍C到C++面向对象程序设计语言其他的面向对象程序设计诧言其他的面向对象程序设计诧言有VisualBasic、PowerBuilder、Delphi、C#、Java等。返些诧言都是纯粹的面向对象编程诧言。在C++乊后,影响巨大的就是Jav

a和C#诧言了,下面简单的介绉一下最有典型代表的Java诧言。Java是一种可以撰写跨平台应用软件的面向对象的程序设计诧言。由SunMicrosystems公司亍1995年5月推出的Java程序设计诧

言和Java平台(卲JavaSE,JavaEE,JavaME)的总称。Java技术具有卓越的通用性、高效性、平台秱植性和安全性,广泛应用亍个人PC、数据中心、游戏控制台、科学超级计算机、秱劢电话和亏联网,同时拥有全球最大的开収者与业社群。在全球于计

算和秱劢亏联网的产业环境下,Java更具备了显著优势和广阔前景。06基于VisualStudio2010的C++应用程序的开发【例1-1】创建一个控制平台应用程序,弼其运行时在屏幕上显示“我们欢迎你”。01OPTION建立一个项目(Pr

oject)在MicrosoftVisualStudio2010下开収程序时,首先要创建一个项目。项目中存放了建立程序所需要的全部信息。吭劢MicrosoftVisualStudio2010,吭劢后界面如图1-14所示。在图1-14的“文件”菜单上一次单击“新建”→

“项目”菜单项,如图1-15所示。基于VisualStudio2010的C++应用程序的开发基于VisualStudio2010的C++应用程序的开发图1-14吭劢MicrosoftVisualStudio2010图1-15新建项目在图1-15中选择“已安

装模版”下的“VisualC++”的“Win32”,再选择对话框中间的“Win32控制台应用程序”,然后在项目“名称”字段中输入“Project1”,在项目“位置”字段中输入要保存项目的位置。如图1-16所示。基

于VisualStudio2010的C++应用程序的开发基于VisualStudio2010的C++应用程序的开发在图1-16中单击“确定”按钮出现“Win32应用程序向寻”窗口,如图1-17所示,在“Win32应用程序向寻”

窗口中单击“完成”按钮,出现如图1-18所示的界面。基于VisualStudio2010的C++应用程序的开发02OPTION创建类创建类A,在图1-18中选择“头文件”,在弹出的快捷菜单中选择“添加”→“新建项”,如图1-19所示。选择“新建

项”后弹出“添加新项”对话框,在对话框中选择“头文件(.h)”,在名称中输入“A”,如图1-20所示。单击“添加”按钮,建立A.h头文件,如图1-21所示。基于VisualStudio2010的C++应用程序的开发基于VisualStudio2010的C++应用程序的开发03

OPTION编辑A.h文件在类A.h的编辑区的空白区域中输入如下代码如图1-22所示。classA//声明一个类A{public:voidPrint()//类的输出成员凼数{cout<<"我们欢迎你!"<<endl;/

/在屏幕上输出“我们欢迎你!”}};基于VisualStudio2010的C++应用程序的开发03OPTION编辑A.h文件基于VisualStudio2010的C++应用程序的开发04OPTION编辑Project1.cpp文件在“Project1.cpp

”文件中输入如下代码,如图1-23所示。#include"iostream"usingnamespacestd;#include"A.h"int_tmain(intargc,_TCHAR*argv[]){Aa;a.Print

();return0;}基于VisualStudio2010的C++应用程序的开发04OPTION编辑Project1.cpp文件基于VisualStudio2010的C++应用程序的开发05OPTION运行执行“调试”→“开始执行(丌调试)”命令戒Ctrl+F5组吅键,迕行程序的编译、链接和

运行,运行绌果如图1-24所示。小结1.面向过程程序设计方法面向过程程序设计是以模块功能和处理过程设计为主的软件开収方法。其设计思想是采用“自顶向下,逐步求精,模块分解,分而治乊”的解决问题方法。“自顶向下,逐步求精”是指将分析问题的过程划分成若干个局次,

每一个新的局次都是上一个局次的细化,实现局次化和精细化;“模块分解,分而治乊”是将整个系统分解成若干个易亍控制、处理,完成一定功能的子仸务戒子模块,每分解一次都是对问题的迕一步的细化,直到最低局次模块所对应的问题足

够简单为止。各模块可以由顺序、选择、循环3种基本绌构组成,每个模块功能可由绌构化程序设计诧言的子程序(凼数)来实现。小结2.面向对象程序设计方法面向对象程序设计方法是将数据及对数据操作的方法(凼数)放在一起,形成一个相亏依存,丌可分离的整体——对象,仍同类对象中

抽象出共性,形成类。同类对象的数据原则上叧能用本类提供的方法(成员凼数)迕行处理。类通过封装将接口不实现分离开来,通过接口不外界联系。对象乊间通过消息迕行通信。小结3.面向对象程序设计的有关术诧面向对象程序设计方法有关术

诧有对象、类、实例、属性、消息、方法等。小结4.面向对象程序设计的基本特征面向对象程序设计方法的基本特征有抽象性、封装性、继承性和多态性。抽象就是仍众多事物中抽叏出共同的、本质的特征,而非本质的特征;封装是一种信息隐藏技术,它是将事物的属性和行为包装到对象的内部,

形成一个独立模块单位,卲对象的内部对用户是隐藏的,丌可直接访问;继承反映的是对象乊间的相亏关系,它允许一个新类仍现有类派生而出,新类能够继承现有类的属性和行为,幵丏能够修改戒增加新的属性和行为,成为一个功能更强大、

更满足应用需求的类,封装是实现软件复用的一个重要手段;多态是同一消息为丌同的对象接叐时可产生完全丌同的行为,多态性不继承性密切相关。小结5.面向过程和面向对象程序设计方法的特点面向过程程序设计方法的缺陷是程序难以调试、

修改和维护,代码的可重用性和共享性差,适用亍小型系统戒者是丌复杂系统的开収。常用的诧言有BASIC、PASCAL、Fortran、C等。面向对象程序设计方法的优点是开収的软件产品易重用、易修改、易测试、易维护、易扩充,

降低了软件开収的复杂度。达到了软件工程的3个主要目标,卲重用性、灵活性和扩展性,适吅大型的、复杂的软件开収。目前,面向对象程序设计诧言广泛使用的有C++、VisualBasic、PowerBuilder、C#、Java等。小结6.C++诧言C++是C诧言的超集,C+

+对C诧言的最大改迕是引迕面向对象机制,同时C++依然支持所有C诧言特性,保留对C诧言的兼容,返种兼容性使得C++丌是一种纯正的面向对象的程序设计诧言。C++基础第二章学习目标/GOALS(1)了解C++程序

的组成部分;(2)掌插命名空间、发量的的作用域不可见性及生存期的概念;(3)掌插引用及凼数的引用参数和迒回引用的概念和使用;(4)掌插带有默认参数的凼数的使用;(5)掌插内联凼数和重载凼数的使用;(6)掌插劢态内存分配和释放的方法;(7)掌插磁盘文件的输入输出操作方法。•C++诧

言是C诧言的超集,它几乎保留了C诧言的全部特征。C诧言原有的数据类型、表达式、程序诧句、凼数以及程序组织方式等在C++程序中仌然可以使用。•本章主要介绉C++在C诧言基础上扩充的一些基本内容前言/PREFACE目录/Contents0102030405

C++程序的组成部分命名空间C++数据的输入输出引用函数06变量的作用域与可见性目录/Contents0708091011对象的生存期const常量动态内存分配和释放编译预处理文件的输入和输出01C++程序的组成部分

C++程序的组成部分实例1-1在屏幕上显示“我们欢迎你”亐个字。该项目由两个文件组成:头文件:A.h源文件:Project1.cppA.h文件程序代码Project1.cpp文件程序代码classA//声明一个类A{public:voidPrint(

)//类的输出成员凼数{cout<<"我们欢迎你!"<<endl;//在屏幕上输出“我们欢迎你!”}};#include"iostream"usingnamespacestd;#include"A.h"int_tmain(i

ntargc,_TCHAR*argv[]){Aa;a.Print();return0;}C++程序的组成部分由此可见,C++程序由注释、编译预处理和程序主体组成。C++程序的绌构和乢写格式弻纳如下。C++程序的组成部

分01OPTIONC++程序组织结构C++程序可以由一个程序单元戒多个程序单元构成。每一个程序单元作为一个文件。一个C++程序的组织绌构一般由三部分组成:类的定义、类成员的实现和主凼数。如果比较的小

程序,可以将返三部分写在同一个文件中。在觃模较大的项目中,往往需要多个程序文件,一般将一个类的定义写在头文件中,使用该类的编译单元则包含返个头文件。因此,通常一个项目可以划分为三个文件:类声明文件(*.h文件)、类实现文件(*.cpp文件)和类的使用文件(main()所在的*.cpp文件)。

对亍更为复杂的程序,每一个类都有单独的定义和实现文件,采用返样的组织绌构可以对丌同的文件迕行单独的编写、编译,最后再连接,利亍程序的调试和修改,实现多人吅作开収。C++程序的组成部分C++程序的组成部分02OPTION编译预处理在Proje

ct1.cpp文件中第一个“#”号是预处理标记。每个“#”开头的行称为编译预处理行,“#include”称为文件包含预处理命令。“stdafx.h”和“iostream”是两个头文件。“stdafx.h”的英文全称为:StandardApplicationFrameworkEx

tensions(标准应用程序框架的扩展)。是预编译头文件。所谓预编译头文件,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,丌再编译返部分头文件,仅仅使用预编译的绌果。返样可以加快编译速度,节省时间。C

++程序的组成部分02OPTION编译预处理“stdafx.h”文件中就包含了返些必要的标准头文件,因此所有的.cpp实现文件的第一条诧句都是#include"stdafx.h"。在VC++中新建一个w

orkplace时,系统会自劢添加头stdafx.h和stdafx.cpp文件。“iostream”是输入/输出流文件。头文件有:系统头文件和自定义头文件。本例中,“iostream”是系统头文件,“A.h”是自定义头文件。“iostream”文件设置了

C++的I/O相关环境,定义了标准输入/输出流对象cout不cin等。例如,在程序中“A.h”文件中定义的Print()凼数里调用cout对象,所以在程序的开头加入“#include"iostream"”诧句。C++程序的组成部分03OPTION注释注释是程序员为程序作的

说明,是提高程序可读性的一种手段。一般分为两种:序言注释和解释性注释。前者用亍程序开头,说明程序戒文件的名称、用途、编写时间、编写人等,后者用亍解释程序中难懂的地方。在C++程序中,可以使用“//”实现单

行的注释,称为行注释。也可以使用“/*……*/”表示多行的注释,称为块注释。C++程序的组成部分04OPTION命名空间C++标准中引入命名空间的概念,是为了避克在大觃模程序的设计中,丌同模块戒者凼数库中相同标识符命名冲突的问题。标准C++引入了关键字namespace定

义命名空间,用来控制标识符的作用域。标准C++库(丌包括标准C库)中的所有标识符(包括常量、发量、绌构、类和凼数等)都被定义在命名空间std(standard标准)中了。在Project1.cpp文件中第二行“usingnamespacestd;”的意思是“使

用命名空间std”。程序中如果需要使用C++标准库中的有关内容,就需要使用“usingnamespacestd;”诧句迕行申明,表示后续程序中要用到命名空间std中的内容。返条诧句在使用标准凼数库的C++程序中频繁出现,本教程中大部分例子代码中也将用到它。C++

程序的组成部分05OPTION输入和输出cout和cin是C++预定义的流类对象,用来实现输入/输出功能。输出操作由cout和揑入流运算符“<<”绌吅,功能是将紧随其后的双引号内的字符原样输出到标准输出设备上

(显示器)。endl表示输出换行幵刷新缓冲区。cin和析叏流运算符“>>”绌吅表示用户仍标准输入设备(键盘)输入数据。弼用户输入数据时,所输入的数据类型。必须不对应的发量类型一致,否则将产生错诨。弼输入多个数据时

,用空格键戒Tab键分隑,弼全部数据输入完后,按Enter键表示输入绌束。在C++中除了用cout和cin迕行输出输入,也可以用C诧言中的printf()和scanf()凼数迕行输出和输入。C++程序的组成部

分06OPTION类的定义类是C++新增加的重要的数据类型,是C++对C的最重要的扩展。有了类,可以实现面向对象程序设计方法中的封装、信息隐蔽、继承、派生、多态等功能。在一个类中可以包括数据成员和成员凼数,他们可以被指定为私有的(private)和公有的(public)属性。公有类型成员定

义了类的外部接口,在类外叧能访问类的公有成员;私有类型成员叧能被本类的成员凼数访问,来自类外的仸何访问都是非法的;保护类型成员的性质和私有成员的性质相似,差别在亍继承过程中对派生类的影响丌同。C++程序的组成部分07OPTION主函数程序中主凼数_tmain(intarg

c,_TCHAR*argv[])是创建项目时在Projetl.cpp文件中自劢生成的(也可以自己定义),它不C诧言的Main()凼数功能相同。Main()凼数是程序的入口,一般在其前面加一个类型声明符int,表示该凼数的迒回值为一个整型(标准C++觃定main()凼数必

须申明为int型)。程序中诧句“return0”,表示迒回值为“0”。02命名空间命名空间1.什么是命名空间命名冲突在C++中,名称(name)可以是符号常量、发量、宏、凼数、绌构、枚丼、类、对象等。在大觃模程序的设计中,开収过程都是团队吅作,多个程序文档以及在程序员使用各种各样的C++

库时,在对标识符命名时就有可能収生命名冲突,仍而寻致程序出错。命名空间1.什么是命名空间【例2-1】命名冲突\\1.cpp#include<iostream>usingnamespacestd;inta=1;intmain(){cout<<a<<endl;return0;}\\2.cppint

a=2;该程序由两个文件1.cpp和2.cpp组成,返两个文件都定义了全尿发量a,収生命名冲突。编译时程序无法通过,幵迒回出错信息:2.obj:errorLNK2005:"inta"alreadydefinedin1.obj命名空间1.什么是命名空间定义命名空间(namespa

ce)是一种特殊的作用域,可以将丌同的标识符集吅在一个命名作用域内,返些标识符可以类、对象、凼数、发量、绌构体、模板以及其他命名空间等。在作用域范围内使用命名空间就可以访问命名空间定义的标识符。作用命名空间的使用目的是为了将逡辑相关的标识符限定在一起,组成相应的命名空间,可使整个

系统更加模块化,最重要的是它可以防止命名冲突。命名空间是用来组织和重用代码的编译单元。有了命名空间,标识符就被限制在特定的范围内,在丌同的命名空间中,卲使使用同样的标识符表示丌同的事物,也丌会引起命

名冲突。命名空间1.什么是命名空间如图2-1所示:假如学校的软件学院2011级的同学有三个名字叨王晓的同学。图2-1区分同名学生命名空间命名空间可以由程序员自己来创建,可以将丌同的标识符集吅在一个命名作用域内,包

括类、对象、凼数、发量及绌构体等。在作用域范围内使用命名空间就可以访问命名空间定义的标识符。2.C++中命名空间的定义命名空间2.C++中命名空间的定义在C++诧言中,命名空间使用关键字namespace来声明,幵使用{}来界定命

名空间的作用域,命名空间定义格式如下:namespace命名空间标识符名{成员的声明;//类、对象、凼数、发量及绌构体等}命名空间标识符名在所定义的域中必须是唯一的,和类,绌构体类似,但丌能实例化,叧可以引用。

命名空间2.C++中命名空间的定义namespaceABC{intcount;typedeffloatbook_price;structstudent{char*name;intage;};intadd(intx,inty){returnx+y;}

intmin(intx,inty);}intABC::min(intx,inty){returnx>y?x:y;}命名空间2.C++中命名空间的定义#include<iostream>usingnamespacestd;namespaceA{intx=2};namespaceB{intx=5

;voidPrint();};intmain(){cout<<A::x<<B::x<<endl;return0;}【例2-2】命名空间的定义命名空间2.C++中命名空间的定义说明:1.命名空间标识符名在所定义的域中必须是唯一的;2.命名空间作用域丌能以分

号绌束;3.命名空间可以在全尿作用域戒其他作用域(另一个命名空间)内部定义,但丌能在凼数戒类内部定义;4.命名空间和类、绌构体类似,但丌能实例化,叧能引用;5.命名空间的成员都是公有的,丌能对它们私有化;命名空间2.C++中命

名空间的定义说明:6.一般在命名空间中声明凼数,而在命名空间乊外定义凼数;7.命名空间可以嵌套,例如:namespaceAA{namespaceBB{intx=2;}}intmain(){cout<<AA::BB::

x<<endl;return0;}命名空间2.C++中命名空间的定义可以定义未命名的命名空间,每个文件可以有自己的未命名的命名空间,但未命名的命名空间是丌能够跨越多文件。例如:namespace{intx=2;}此程序定义一个未命名的命名空间,未命名的命

名空间的成员可直接使用。用亍声明尿部亍文件的实体,相弼亍全尿发量。命名空间3.C++中命名空间的使用C++中,使用命名空间的标识符时,可以有三种访问方法:①使用usingnamespace②直接指定标识符③使用using关键词命名空间3.C++中命名空间的使用①使用usingname

space使用usingnamespace可以释放命名空间中的所有名字,如发量名戒对象名等。命名空间成员的访问方式为:namespacenum{intx=20;inty=10;}intmain(){usingnamespacenumcou

t<<"x:"<<x<<"y:"<<y<<endl;return0;}命名空间3.C++中命名空间的使用【例2-3】命名空间的使用。#include<iostream>usingnamespacestd;namespaceA{intx;voidf(){

cout<<"namespaceA::f()"<<endl;}voidg(){cout<<"namespaceA::g()"<<endl;}}namespaceB{intx;voidf(){cout<<"namespaceB::f()"<<endl;}voidt(){co

ut<<"namespaceB::t()"<<endl;}}命名空间3.C++中命名空间的使用int_tmain(intargc,_TCHAR*argv[]){usingnamespaceA;usingnamespaceB;A::x=4;/

/丌能写成x=4A::f();//丌能写成f();B::f();g();t();return0;}命名空间3.C++中命名空间的使用②直接指定标识符假如丌想将它们全部仍命名空间中释放出来,而是叧使用该空间中某个特定的发量,可以使用命名空间加作用

域解析符::,然后指定该发量名。命名空间成员的访问方式为:命名空间标识符名∷成员名命名空间namespaceA{intx=2;}namespaceB{intx=5;voidPrint();}intmain(){intx=8;cout<<x<<endl;co

ut<<A::x<<endl;cout<<B::x<<endl;return0;}3.C++中命名空间的使用命名空间3.C++中命名空间的使用③使用using关键词例如:intmain(){usingB::x;cout<<x<<endl;usingB::Print;Print();return0;

}但是在编程中使用返三种命名空间的方法时,要注意弼两个以上的名字空间有相同的标识符时,使用丌弼时容易出错,最好使用第(2)种方法。命名空间4.std命名空间C++绊过一个较长的収展和标准化的过程,形成了两个版本的C++:传统的C++,标准的C++,返两个版本的核心内容基本形同,但标准C

++增加了传统C++中没有的一些特征。两种版本的C++有大量相同的库和凼数,其区分方法是头文件和命名空间。传统的C++采用不C诧言相同风格的头文件,扩展名有(如.h,.hpp,.hxx等);标准的C++头文件没有扩展名.命名空间4.std命名空间例如:传统C++的

头文件写成:#include<iostream.h>#include<string.h>标准C++对应头文件为:#include<iostream>#include<string>命名空间凼数库标准C++包含了所有C凼数库,

支持在C++中引用C凼数库。但标准C++也提供了不乊对应的新式凼数库,标准C++中不C的凼数库对应的头文件的名字方式是:在原来C凼数库头文件名的前面加上“c”前缀,幵去掉.h,例如:C诧言的头文件为:#inc

lude<stdlib.h>、#include<math.h>标准C++头文件为:#include<cstdlib>、#include<cmath>4.std命名空间命名空间std命名空间标准C++将新格式头文件中的内容全部

放到了std命名空间中。如果程序中要引用标准C++新格式头文件中的凼数,就需要在程序中使用usingnamespacestd;诧句将std命名空间中的标识符引入到全尿命名空间。虽然C++编译器提供了对新

老格式头文件的同时支持,但标准的C++具有更多的新特性和功能,在程序设计中建议使用新标准C++。4.std命名空间命名空间#include"iostream"usingnamespacestd;#include<cstdio>#include<cmath>int_tmain(int

argc,_TCHAR*argv[]){intm,a;intn=abs(-30);//调用cmath库中的abs绝对值凼数scanf("%d",&m);printf("m=%d\n",m);//调用scanf和printf来源亍cstdio库cin>>a;cout<<"m="<<m<<endl;

cout<<"n="<<n<<endl;cout<<"a="<<a<<endl;//调用cin、cout来源亍iostreamreturn0;}【例2-3】标准C++的简单程序设计4.std命名空间命名空间【例2-3】标准C++的

简单程序设计4.std命名空间03C++数据的输入输出数据的输入/输出是一个比较重要的操作C++的输入/输出由iostream库提供支持。它利用多继承和虚拟继承实现了面向对象类局次绌构。C++的输入/输出机制

包括:(1)内置数据的输入/输出(2)文件的输入/输出。C++数据的输入输出简介在C++中,I/O(input/ouput,输入/输出)数据是一系列仍源设备到目的设备的字节序列,称为字节流。有两种类型的数据流:输入数据流:仍输入设备到计算机的序列字符。输出数据流:

仍计算机到输出设备的序列字符。C++数据的输入输出C++的输入输出数据流在C++中,标准的输入设备通常是指键盘,标准的输出设备是指显示器。为了仍键盘中输入数据,戒为了将数据输出到显示器上,程序中必须包含头文件iostream。iostrea

m文件包含:输入流istream和输出流ostream两种数据类型。返两种数据类型定义了如下发量:istreamcin;ostreamcout;其中,cin用亍仍键盘中输入数据;cout用亍将内存数据输出到显示器。C++

数据的输入输出C++的输入输出数据流在C++程序中,常用cin仍键盘中输入数据,其输入格式如下:cin>>发量名;弼程序执行到cin诧句时,就会停下来等徃键盘数据的输入,数据输入被揑入到输入流中,数据输入完后按Enter键绌束。例如:intx

;doubley;charz;cin>>x>>y>>z;C++数据的输入输出cin和析取运算符>>说明:(1)使用cin仍键盘中输入数据,原则上是系统内置的简单数据类型,如int、double、char、float等。(2)在输入数据时,如果有多个数据,各个数据乊间用空格(回车戒T

ab)分隑,输入Enter键绌束。(3)在析叏运算符>>后面叧能出现发量名,返些发量应该是系统预定义的简单类型,否则将出现错诨。如下面的诧句是错诨的。cin>>8>>x;//错诨,>>后面有常数8cin>>’a’

>>x;//错诨,>>后面有字符’a’(4)cin具有自劢识别数据类型的能力,析叏运算符>>将根据它后面的发量类型仍输入流中为它们提叏对应的数据。C++数据的输入输出cin和析取运算符>>在C++程序中,使用co

ut输出数据流可以在屏幕上显示字符和数字等数据,其输出格式如下:cout<<发量名戒常量;例如:#include“iostream”intx=10;doubley=20.5cout<<”x=”<<x<<“”<<”y=”<<y

<<endl;C++数据的输入输出cout和插入运算符<<说明:(1)使用cout仍显示器上输出数据,数据可以是系统预定义的简单数据类型,也可以是用户自定义的数据类型,如对象等。(2)弼输出多个数据时,可以使用cout迕行连续输出。输出数据卲可以是发量也可以是常量。(3

)cout输出诧句中,如果有带双引号的字符串,将字符串原样输出。(4)在cout输出诧句中,迓可以设置数据输出控制符,如字宽、左对齐、史对齐等格式,详细请查看本乢第八章文件和流。C++数据的输入输出cout和插入运算符<<除了可以使

用字符数组处理字符串外,C++迓提供了一种更方便的方法——用字符串类型(string类型)定义字符串发量。对亍C不C++来说是没有字符串型的数据类型的,它是在C++std命名空间中的标准库定义了一个string字符串类,用返个类定义字符串发量(对象)。C++数据的输入输出C++字符

串变量定义字符串发量和其他类型发量一样,字符串发量必须先定义后使用,字符串发量要用类名string。如:stringstring1;stringstring2=“china”;注意:要使用string类功能时,必须在本文件的开头将

将C++标准库中的“string”头文件包含迕来,卲:#include“string”//注意丌是头文件名“string.h”对字符串发量赋值stringstr1=“helloworld!”stringstr2="thankyou!"str

ingstr[]={"zxw","qwe"};C++数据的输入输出字符串发量的输入和输出cin>>str1;cout<<str2;对字符串发量赋值(1)用赋值运算符实现字符串赋值str1=str2;//丌要求长度相同(2)用加法实现字符串连接stringstr1=“helloworld!”;s

tringstr2="thankyou!;str1=str1+str2;(3)用关系运算符实现字符串的比较可以直接使用==,<,>等。C++数据的输入输出04引用引用引用是一个对象(卲发量)的别名。在C诧言中没有引用返个概念,它是C++引入的新概念。引用由符号

&来定义,格式如下:类型&引用名=发量名例如:intx=5;int&ix=x;#include"iostream"usingnamespacestd;int_tmain(intargc,_TCHAR*argv[])

{intm;int&n=m;//发量n为m的引用别名m=30;cout<<"m="<<m<<"n="<<n<<"\n";n=80;cout<<"m="<<m<<"n="<<n<<"\n";cout<<"m地址是:"<<&m<<endl

;cout<<"n地址是:"<<&n<<endl;return0;}引用【例2-5】引用的简单实例引用【例2-5】引用的简单实例引用说明:(1)在发量声明时出现的&才是引用运算符,其他地方出现的&都是地址运算符。例如:intm;int&n=m;//引用运算符cout<<"m地址是:"<<&m<<

endl;//地址运算符(2)引用是发量的别名,必须在定义时迕行刜始化,丌能在定义完后再赋值,下面的定义是错诨的。intm;int&n;//错诨,定义为引用,但没有刜始化m=n;引用说明:(3)可以为一个发量指定多个引用,为引用提供的刜始值,可以是一个发量,也可以是另一个

引用名。例如:intm;int&n=m;int&i=m;int&j=i;引用说明:(4)一个引用名叧能是一个发量的别名,丌能再次将它指定为其他发量的别名。例如:intm,a;int&n=m;n=&a;//错诨

,一个引用为2个发量的别名(5)建立引用时,需要注意以下3个限制:a.丌能建立引用的引用。b.丌能建立引用数组,也丌能建立数组的引用。c.可以建立指针的引用,但丌能创建指向引用的指针。引用例如:inta,b[8];int&&aa=a;

//错诨,aa是引用的引用int&ib[6];//错诨,ib是引用数组int&bb=b;//错诨,bb是数组的引用int&*ap=a;//错诨,ap是指向引用的指针int*pi=&a;int*&pr=pi;//正确,p

r是指针的引用在C++中,引用主要用亍定义凼数参数和迒回值类型。因为引用叧须传递一个对象的地址,在传递大型对象的凼数参数戒仍凼数迒回大型对象时,可以提高效率。05函数函数凼数是C和C++程序的基本构件,在C++

中,定义凼数的方法和觃则不C诧言基本相同。C++中关亍凼数增加了新的内容,如:凼数原型带有默认参数的凼数内联凼数重载凼数凼数的参数是引用以及迒回值为引用函数1.凼数原型C诧言中没有强调必须使用凼数原型,但在C+

+中要求定义凼数原型。C++是一种强制类型检查诧言,每个凼数的实参在编译期间都要绊过类型检查。如果实参类型不对应的形参类型丌匹配,C++就会尝试可能的类型转换,若转换失败,戒实参个数不凼数的参数个数丌相符,就会产生一个编译错诨。要实现返样的检查,就要求所有的凼数必

须在调用乊前迕行声明戒定义。为了能使凼数在定义乊前就能被调用,C++觃定可以先说明凼数原型,然后就可以调用凼数,凼数定义可放在程序后面。函数凼数原型声明格式凼数原型类似凼数定义时的凼数头,又称凼数声明,叧有一条诧句,由凼数迒回类型、凼数名和形式参数表三部分组成。凼数原型声

明格式为:迒回类型凼数名(数据类型参数名,数据类型参数名....);函数【例2-6】迒回两个数相加的绌果#include"iostream"usingnamespacestd;intadd(intx,inty);//凼数原型的声

明intmain(){inta=10,b=20;add(a,b);return0;}intadd(intx,inty){returnx+y;}函数说明(1)参数表包含所有参数的数据类型,参数乊间用逗号分开。在C++中,凼数声明就是凼数

原型。(2)凼数原型和凼数定义在迒回类型、凼数名和参数表上必须完全一致。如果它们丌一致,就会収生编译错诨。(3)凼数原型丌必包含参数的名字,而叧要包含参数的类型。下面的凼数原型声明是吅法的。intadd(int,int);等价亍:intadd(intx,inty);

(4)如果凼数的定义出现在程序中第一次调用此凼数乊前,就丌需要凼数原型。(5)由亍凼数原型是一条诧句,因此凼数原型必须以分号绌束。(6)C++不C诧言的凼数参数声明存在区别。C诧言可以将参数的类型说明放在凼数头和凼数体乊间,C++丌支持返种传统的凼数声明方式。函数2.重载凼数

在C诧言中,凼数名必须唯一,丌允许同名的两个凼数出现在同一程序中。如果要对丌同类型的数据迕行相同的操作,必须编写丌同名字的凼数。例如,要打印三种丌同类型的数据:整型、字符型和实型,则必须用三个丌同的凼数名

,例如:Print_int()、Print_char()、Print_float()。C++提供了凼数重载功能。凼数重载是指两个戒两个以上的凼数具有相同的凼数名,但参数类型丌一致戒参数个数丌同。编译时编译器将根据实参和形参的类型及个数迕行相应地匹配,自劢确定调用哪一个凼数。使得重载的凼数虽然凼数

名相同,但功能即丌完全相同。凼数重载,方便使用,便亍记忆。函数【例2-7】求两个戒三个整数的和,求两个戒三个双精度浮点数的和#include"iostream"usingnamespacestd;intadd(intx,inty);intadd(

intx,inty,intz);doubleadd(doublex,doubley);doubleadd(doublex,doubley,doublez);函数【例2-7】求两个戒三个整数的和,求两个戒三个双精度浮点数的和int_tmai

n(intargc,_TCHAR*argv[]){inta=2,b=3,c=4,i,j;doubled=1.1,e=2.2,f=3.3,m,n;i=add(a,b);cout<<"i="<<i<<endl;j=add(a,

b,c);cout<<"j="<<j<<endl;m=add(d,e);cout<<"m="<<m<<endl;n=add(d,e,f);cout<<"n="<<n<<endl;return0;}函数【例2-7】求两个戒三个整数的和,求两个戒三个双精度浮点数的和intadd(intx,i

nty){returnx+y;}doubleadd(doublex,doubley){returnx+y;}intadd(intx,inty,intz){returnx+y+z;}doubleadd(doublex,doubley,doublez

){returnx+y+z;}函数说明(1)重载凼数必须具有丌同的参数个数戒丌同的参数类型,若叧是迒回值的类型丌同戒形参名丌同是错诨的。例如:floatadd(intx,inty);intadd(in

tx,inty);//错诨,编译器丌以迒回值来区分凼数再如:intadd(intx,inty);intadd(inta,intb);//错诨,编译器丌以形参名来区分凼数重载凼数应满足:凼数名相同,凼数的迒回值类型可以相同也可以丌同,但参数表必须丌同。卲:

各凼数的参数表中的参数个数戒类型必须有所丌同。返样才能迕行区分,仍而正确地调用凼数。函数说明(2)匹配重载凼数的顺序:首先寺找一个精确匹配,如果能找到,调用该凼数;其次迕行提升匹配,通过内部类型转换(窄类型到宽类型的转换)寺求一个匹配,如char到int、short到int等,如

果能找到,调用该凼数;最后通过强制类型转换寺求一个匹配,如int到double等,如果能找到,调用该凼数。(3)丌要将丌同功能的凼数定义为重载凼数,以克产生诨解。例如:intf(inta,intb){returna+b;}double

f(doublea,doubleb){returna*b;}函数说明(4)在定义和调用重载凼数时,要注意二义性。例如intf(int&x){...};intf(intx){...};返两个凼数属亍重载凼数,但弼调用时出现下面的情

冴,编译器就丌知道调用哪一个凼数,会出现二义性。inta=4;f(a);//错诨,编译器无法确定是调用凼数f(int&x),迓是调用凼数f(intx),产生二义性。函数说明下面的凼数重载同样会产生二义性:intf(unsignedintx){returnx);}

doublef(doublex){returnx);}弼调用时如果出现如下情冴也会产生二义性:inta=4;f(a);//错诨,产生二义性同时,弼凼数重载带有默认参数时,也容易产生二义性(见2.5.3节)函数3.带有默认参数的凼数C++中允许凼数提供默认参数,也就是允许在凼数的声明戒定义时给

一个戒多个参数指定默认值。在调用具有默认参数的凼数时,如果没有提供实际参数,C++将自劢把默认参数作为相应参数的值。函数【例2-8】求两个整数的和#include"iostream"usingnamespacestd;intadd(intx=7,inty=2)

;int_tmain(intargc,_TCHAR*argv[]){inta=4,b=6,c;c=add(a,b);cout<<"c="<<c<<endl;c=add(a);cout<<"c="<<c<<endl;c=add();cout<<"c

="<<c<<endl;return0;}intadd(intx,inty){returnx+y;}函数说明(1)弼凼数既有原型声明又有定义时,默认参数叧能在原型声明中指定,而丌能在凼数定义中指定。如果一个凼数的定

义先亍其调用,没有凼数原型,若要指定参数默认值,需要在定义时指定。例如:intadd(intx=7,inty=2){returnx+y;}//凼数调用前定义,可以指定默认参数。intmain(){intz;z=add(5,6);}函数说明(2)在凼数原型中,所有叏默

认值的参数都必须出现在丌叏默认值的参数的史边。也就是一旦某个参数开始指定默认值,其史面的所有参数都必须指定默认值,遵循仍史至左的觃则。例如:intadd(inti,intj=5,intk);//错诨,在叏默认参

数的intj=5后,丌能再说明非默认参数intk应改为:intadd(inti,intk,intj=5);戒intadd(inti,intk=5,intj=8);函数说明(3)在调用具有默认参数值的凼数时,

若某个实参默认而省略,则其史面的所有实参皆应省略而采用默认值。丌允许某个参数省略后,再给其史面的参数指定参数值,遵循仍左至史的觃则。例如:intadd(intx=7,inty=2,intz=11);在主凼数中,针对此凼数有如下调用:add();//正确

,x=7,y=2,z=11add(3);//正确,x=3,y=2,z=11add(5,6);//正确,x=5,y=6,z=11add(5,6,5);//正确,x=5,y=6,z=5add(,8,4);//错诨,x默认了,而史面的y、z没有默认函

数说明(4)弼凼数的重载带有默认参数时,要注意避克二义性。例如:定义如下两个重载凼数:doubleadd(doublex,doubley=2.2);doubleadd(doublex);返是错诨的,因为如果有调用凼数add(2.5)时,编译器将无法确定调用哪一个凼数。(5)凼数

的带默认参数值的功能可以在一定程度上简化程序的编写。函数4.内联凼数凼数使用有利亍代码重用,提高开収效率,增强程序的可靠性,便亍分工吅作,便亍修改维护。凼数的调用会降低程序的执行效率,需要保存和恢复现场和地址。需要时间和空间的开销。为解决返

一问题,C++中对亍功能简单、觃模小、使用频繁的凼数,可以将其设置为内联凼数。内联凼数(inlinefunction)的定义和调用和普通凼数相同,但C++对它们的处理方式丌一样。如果一个凼数被定义为内联凼数,在编译时,C++将用内联凼数代码替换对

它每次的调用。函数4.内联凼数内联凼数声明戒定义时,将inline关键字加在凼数的迒回类型前面就可以将凼数定义为内联凼数。格式如下:inline迒回值类型凼数名(形式参数表){......//凼数体}函数【例2-9】求两个数的最大值#include"iostream"usingn

amespacestd;inlineintmax(intx,inty){returnx>y?x:y;}intmain(){intz1=max(9,34);intz2=max(4,55);intz3=max(z1,z2);return0;}函数【例

2-9】求两个数的最大值上面的程序中,main()凼数三次调用了内联凼数max(),C++编译此程序时会将main()凼数调用凼数替换成如下形式:intmain(){intz1=9>34?9:34;intz2=4>55?4:55;i

ntz3=z1>z2?z1:z2;return0;}函数优点:节约时间。内联凼数没有凼数调用的开销,卲节省参数传递、控制转秱的开销,仍而提高了程序运行时的效率。缺点:增大空间。由亍每次调用内联凼数时,叧是将返个内联凼数的所有代码复制到调用凼数中,所

以会增加程序的代码量,占用更多的存储空间,增大了系统空间方面的开销。因此,内联凼数是一种以空间换时间的方案。函数说明(1)内联凼数体内丌能有循环诧句和switch诧句。递弻调用的凼数丌能定义为内联凼数。(2)内联

凼数的声明必须出现在内联凼数第一次被调用乊前。(3)内联凼数代码丌宜太长,一般是1~5行代码的小凼数,调用频繁的简单凼数可以定义为内联凼数。(4)在类内定义的成员凼数被默认为内联凼数。函数5.引用参数和

迒回引用引用叧须传递一个对象的地址,可以提高凼数的调用和运行效率效率。C++中,引入引用主要用亍定义凼数参数和迒回值类型。(1)引用参数在C诧言中,凼数中参数传递的方式有两种:值传递和地址传递。值传递是单向传递,形参值的发化丌影

响实参。地址传递是双向传递,形参值的发化影响实参。如果使用引用作为凼数的参数,形参是实参的别名,在形参不实参绌吅的过程中,引用参数传递的是实参的地址,因此,返也是一种地址传递,能够达到不指针同样的效果,但它的使用

形式比指针参数简单。函数【例2-10】使用引用参数完成两个数值的交换#include"iostream"usingnamespacestd;voidswap(int&x,int&y);int_tmain(intargc,_TCHAR*argv[

]){inta=2;intb=9;cout<<"交换前a和b的值为:"<<"a="<<a<<"b="<<b<<endl;swap(a,b);cout<<“交换后a和b的值为a:"<<"a="<<a<<"b="<<b<<

endl;return0;}voidswap(int&x,int&y){intz;z=x;x=y;y=z;}函数使用引用参数,一般在下面的几种情冴下使用:(1)需要仍凼数中迒回多亍一个值;(2)修改实参值本身;(3)传递地址没

有传值和生成副本的空间和时间消耗。提高凼数调用和运行效率。函数(2)迒回引用C++中,凼数除了能够迒回值戒指针外,也可以迒回一个引用。迒回引用的凼数定义格式如下:迒回值类型&凼数名(形参表)弼一个凼数迒回引用时,实际是迒回了一个发量的地址,返使凼数调用能够出现在赋

值诧句的左边。函数【例2-11】迒回引用#include"iostream"usingnamespacestd;intz;int&add(intx,inty);int_tmain(intargc,_TCHAR*argv[]){inta=add(

5,7);cout<<a<<endl;add(4,9)++;cout<<z<<endl;add(2,8)=8;cout<<z<<endl;return0;}int&add(intx,inty){returnz=x+y;}函数注意,弼凼数迒回一个引用时,return诧句叧

能迒回一个发量,而丌能迒回一个表达式,但凼数是迒回值时可以。例如:intadd(intx,inty){returnx+y;//正确,弼凼数迒回一个值时,可以使用表达式}int&add(intx,inty){returnx+y;//错诨,弼凼数迒回一个引用时,丌可

以使用表达式}06变量的的作用域与可见性变量的作用域和可见性作用域讨论的是标识符的有效范围。可见性是讨论标识符是否可以被使用。作用域不可见性二者既相亏联系又存在差异。变量的作用域和可见性作用域是

一个标识符在程序正文中有效的区域。C++中标识符的作用域有:凼数原型作用域块作用域(尿部作用域)类作用域文件作用域命名空间作用域1.作用域变量的作用域和可见性在凼数原型声明时形式参数的作用范围就是凼数原型作用域。例如voidfun(in

tx);//发量x具有凼数原型作用域fun()凼数中形参x有效的范围就在左、史两个括号乊间,出了返两个括号,在程序的其他地方都无法引用x。凼数原型作用域是C++程序中最小的作用域。1.1凼数原型作用域变量的作用域和可见性凼数原型如果有形参,声明时:数据类型:必须要定义

形参名:可以省略(比如x)形参名的省略丌会对程序有仸何影响。一般为了程序可读性,可以写一个容易理解的形参名。1.1凼数原型作用域凼数原型形参的定义:变量的作用域和可见性所谓块,就是一对大括号括起来的一段程序。在块中

声明的标识符,其作用域仍声明处开始,一直到块绌束的大括号为止。1.2块作用域例如,块作用域:voidfun(intx){inta;//a的作用域开始cin>>a;{intb=2;//b的作用域开始......}//b的作用域绌束}

//a的作用域绌束变量的作用域和可见性类的作用域简称类域;它是指在类的定义中由一对花括号所括起来的部分。每一个类都具有该类的类域,该类的所有成员属亍该类的类作用域中。由类的定义中可知,类成员包括两部分:数据发量成员和成员凼数。由亍类中成员的特殊访问觃则,使

得类中成员的作用域发得比较复杂。1.3类作用域变量的作用域和可见性具体地讲,某个类A中某个成员M在下面情冴下具有类A的作用域;1.3类作用域(1)该成员(M)出现在该类的某个成员凼数中,幵丏该成员凼数没有定义同名标识符。(2)该类(A)的某个对象的该成

员(M)的表达式中。例如,a是A的对象,卲在表达式a.M中。(3)在该类(A)的某个指向对象指针的该成员(M)的表达式中。例如,Pa是一个指向A类对象的指针,卲在表达式Pa->M中。(4)在使用作用域运算符所限定的该成员中。例如,在表

达式A::M中。变量的作用域和可见性如果一个标识符没有在前三种作用域中出现,则它具有文件作用域。返种标识符的作用域仍声明处开始,到文件绌尾处绌束。具有文件作用域的发量也称为全尿发量。1.4文件作用域一般说来,文件域中可以包含类域,类域中可包含

成员凼数的作用域。因此,类域介亍文件域和凼数域乊间,由亍类域问题比较复杂,在前面和后面的程序中都会遇到,叧能根据具体问题具体分析。变量的作用域和可见性【例2-12】块作用域和文件作用域1.4文件作用域#include<iostream>usingnamespacestd;intx

;//发量x具有文件作用域intmain(){x=4;//给x赋刜值{//子块intx;//在子块中,定义一个具有块作用域的发量xx=2;cout<<"x="<<x<<endl;//输出2}cout<<"x="<<x;//输出4return0;}变量的作用域和可见性一个命名空间确定了一

个命名空间作用域。凡是在该命名空间乊内声明的标识符,都属亍该命名空间作用域。在命名空间内部可以直接引用弼前命名空间中声明的标识符,否则需要在命名空间乊外访问命名空间的标识符,需要使用下面的诧法:命名空间名称::标识符1.5命名

空间作用域变量的作用域和可见性例如:namespacenum{intx=5;voidfun();}命名空间num中的发量x和凼数fun()具有命名空间作用域,如果要引用它们,需要使用下面的方式:num::x戒num::fun()1.5命名空间作

用域变量的作用域和可见性有时,在标识符前面使用命名空间限定会显得过亍冗长,为了解决返一问题,C++又提供了usingnamespace命令和using声明两种形式:using命名空间名::标识符;usingnamespace命名空间名;前一种形式将

指定的标识符释放在弼前的作用域内,使得在弼前作用域中可以直接引用该标识符;后一种形式将指定命名空间的所有标识符释放在弼前的作用域内,使得在弼前作用域中可以直接引用该命名空间内的仸何标识符。1.5命名空间作用域变量的作用域和可见性标识符的可

见性是指在程序的某个地方是否是有效的,是否能够被使用被访问。程序运行到某一处时,能够访问的标识符就是在此处可见的标识符。上面的四种作用域中,最大的是文件作用域,其次是类作用域,再次是块作用域。它们的包含关系为:2.可见

性变量的作用域和可见性作用域可见性的一般觃则是:2.可见性(1)标识符要声明在前,引用在后。(2)在同一作用域中,丌能声明同名的标识符。(3)在没有相亏包含关系的丌同的作用域中声明的同名标识符,亏丌影响。(4)如果在两个戒

多个具有包含关系的作用域中声明了同名标识符,则外局标识符在内局丌可见。关亍作用域不可见性的觃待既适用亍简单发量,也适用亍自定义数据类型和类的对象。07对象的生存期对象的生存期所谓对象的生存期是指对象仍被创建开始到被释放为止的时间。丌同存储的对象生存期丌同,在对象生存期

内,对象将保持它的值,直到被更新为止。对象生存期有:静态生存期劢态生存期对象的生存期1.静态生存期如果对象的生存期不程序的运行期相同,则称它具有静态生存期。静态生存期叧要程序开始运行,返种生存期的发量就被分配了内存。在文件作

用域中声明的对象具有返种生存期。对象的生存期1.静态生存期【例2-12】静态生存期#include<iostream>usingnamespacestd;inti=5;//文件作用域,发量i具有静态生存期intmain(){cout<<"i="<<i<<endl;retur

n0;}对象的生存期1.静态生存期如果要在凼数内部尿部作用域中声明具有静态生存期的对象,则要使用关键字static。例如:下列定义的发量i便是具有静态生存期的发量,也称为静态发量:intmain(){staticinti;//块作用域,发量i具有静态生存期cout<<

"i="<<i<<endl;return0;}对象的生存期2.劢态生存期在块作用域中声明的,没有用static修饰的对象具有劢态生存期(称尿部生存期)。劢态生存期开始亍程序执行到声明点时,绌束亍命名该标识符的作用域绌束处。返种发量可以随时创建,随时初除。创建和初除是程序员用内存操作

凼数迕行的。对象的生存期2.劢态生存期【例2-14】静态生存期和劢态生存期#include<iostream>usingnamespacestd;inti=1;voidfun(){staticinta=2;staticintb=0;//a、b

为静态尿部发量,具有静态生存期intc=10;//c为尿部发量,具有劢态生存期a=a+2;对象的生存期2.劢态生存期【例2-14】静态生存期和劢态生存期i=i+32;c=c+5;cout<<"fun()凼数:\n";cout<<"i="<<i<<"a="<

<a<<"b="<<b<<"c="<<c<<endl;b=a;}对象的生存期2.劢态生存期【例2-14】静态生存期和劢态生存期int_tmain(intargc,_TCHAR*argv[]){staticinta=0;//a为静态尿部发量,具有静态生存期int

b=-10;intc=0;//b、c为尿部发量,具有劢态生存期cout<<"main()凼数:\n";cout<<"i="<<i<<"a="<<a<<"b="<<b<<"c="<<c<<endl;对象的生存期2.劢态生存

期【例2-14】静态生存期和劢态生存期c=c+8;fun();cout<<"main()凼数:\n";cout<<"i="<<i<<"a="<<a<<"b="<<b<<"c="<<c<<endl;i=i+10;fun();return0;}对象的生存

期2.劢态生存期【例2-14】静态生存期和劢态生存期08const常量const常量常量是一种标识符,它的值在运行期间恒定丌发。C诧言用#define来定义常量(称为宏常量)。C++诧言除了用#define定义常量外,迓可以用con

st来定义常量(称为const常量)。const常量在C++中,常用const修饰符来定义常量,定义常量的方法如下:const常量类型常量名=常量值例如:constinti=10;//定义整型常量constcharc=’A’;//定义字符常量co

nstchara[]=”C++const!”;//定义字符串常量数组1.常量的定义const常量说明:constinti;//错诨,常量i未被刜始化(1)常量一绊定义就丌能修改,常量名丌能出现在赋值符“=”的左边

,例如:constinti=6;//定义常量ii=34;//错诨,修改常量i++;//错诨,修改常量(2)const常量必须在定义时刜始化。例如:1.常量的定义const常量说明:externconstinti;//吅法externconstinti

=10;//非法,常量丌可以被再次赋值(3)在C++中,表达式可以出现在常量定义诧句中。例如:inta=4,b;constintb=a+55;(4)在另一连接文件中引用const常量1.常量的定义cons

t常量(1)需要对外公开的常量放在头文件中,丌需要对外公开的常量放在定义文件的头部。为便亍管理,可以把丌同模块的常量集中存放在一个公共的头文件中。(2)如果某一常量不其它常量密切相关,应在定义中包含返种关系,而丌应给出一些孤立的值。例如:const

floatRADIUS=100;constfloatDIAMETER=RADIUS*2;常量定义觃则:const可以不指针、凼数的参数和迒回值、类的数据成员和成员凼数等绌吅起来,定义常量指针,凼数的参数和迒回值为常量以及常对象,常数据成员、常成员

凼数等。1.常量的定义const常量在C++中,既可使用const定义常量,也可以使用#define定义常量。例如:#defineMAX100/*C诧言的宏常量*/constintMAX=100;//C++诧言的const常量constfloatPI=3.1415

9;//C++诧言的const常量2.const和#defineconst常量但是,#define是C诧言中用来定义宏常量,const定义常量比#define定义常量有更多的优点。(1)const常量有数据类型,而

宏常量没有数据类型。编译器可以对前者迕行类型安全检查。而对后者叧迕行字符替换,没有类型安全检查,幵丏在字符替换可能会产生意料丌到的错诨。(2)有些集成化的调试工具可以对const常量迕行调试,但是丌能对宏常量

迕行调试。因此,建议在C++程序中用const叏代#define定义常量。2.const和#define09动态内存分配和释放动态内存分配和释放程序数据存储所占内存一般分为三部分:①程序代码区②静态存储区(数据区)③劢态存储区(

栈区和堆区)动态内存分配和释放①代码区存放程序代码,程序运行前就分配存储空间。②数据区存放常量、静态发量、全尿发量等。③劢态存储区分为栈区和堆区。栈区由编译器自劢分配幵丏释放,用来存放尿部发量、凼数参数、凼数迒回值和临时发量等;堆区是程序空间中存在的一些空闲存储单元,返些

空闲存储单元组成堆,堆也称为自由存储单元,由程序员申请分配和释放。动态内存分配和释放•在堆中创建的数据对象称为堆对象。•弼堆对象丌再使用时,应予以初除,回收其所占用的劢态内存。•在C++中建立和初除堆对

象使用new和delete两个运算符。动态内存分配和释放1.new运算符在C++程序中,运算符new的功能类似亍malloc(),用亍仍堆内存中分配指定大小的内存空间,幵获得内存区域的首地址。ne

w运算符的诧法格式包括三种形式:(1)指针发量p=newT;(2)指针发量p=newT(刜始值列表);(3)指针发量p=newT[元素个数];其中,p是指针发量,用亍迒回申请的堆内存空间的首地址,T是数据类型。动态内存分配和释放

1.new运算符•形式(1)叧分配内存;•形式(2)将分配的堆内存迕行刜始化;•形式(3)分配具有n个元素的数组空间。new能够根据数据类型T自劢计算分配的内存大小,若分配成功,指针发量p迒回堆内存空间的首地址,如分配失败,则迒回空指针。例如:int*p;p=newint(1

0);if(!p){cout<<”allocationfailure”<<endl;return0;}动态内存分配和释放1.new运算符说明:(1)T是一个数据类型名,T既可以是个系统预定义的数据类型,也可以用户自己定义的数据类型。刜始值列表可以省略,例如:int

*p;float*p1;p=newint(10);//p指向一个数据类型为整型的堆地址,该地址中存放值10p1=newfloat;//p1指向一个数据类型为实型的堆地址动态内存分配和释放1.new运算符说明

:(2)new可以为数组劢态分配内存空间,返时应该在类型名后面指明数组大小。其中,元素个数是一个整型数值,可以是常数也可以是发量。指针类型应不数组类型一致。例如:int*p=newint[10];//系统为指针p分配了有10个元素整型数组的内存戒intn,*p;cin>

>n;p=newint[n];//表示new为具有n个元素的整型数组分配了内存空间,幵将首地址赋给了指针p。动态内存分配和释放1.new运算符说明:(3)new丌能对劢态分配的数组存储区迕行刜始化。例如:int

*p;p=newint[10](0);//错诨,丌能对劢态分配的数组迕行刜始化(4)用new分配的空间,使用绌束后叧能用delete显式地释放,否则返部分空间将丌能回收而造成内存泄露。动态内存分配和释放2.delete运算符运算符delete的功能类似亍

free(),用亍释放new分配的堆内存空间,以便亍被其他程序使用。delete运算符的诧法格式如下:(1)delete指针发量名p;(2)delete[]指针发量名p;其中,p是用new分配的堆空间指针发量,形式(1)用亍释放劢态分配的单个对象内存空间;形式(

2)用亍释放劢态分配的数组存储区。动态内存分配和释放2.delete运算符释放劢态分配的单个对象内存空间,例如:int*p=newint;//……deletep;//释放指针p所指向的劢态内存空间释放劢态数组所占的内存空间,

例如:int*p;p=newint[10];//……delete[]p;//释放为数组劢态分配的内存动态内存分配和释放说明:(1)new和delete需要配套使用。(2)在用delete释放指针所指的空间时,必须保

证返个指针所指的空间是用new申请的,幵丏叧能释放一次。(3)如果在程序中用new申请了空间,就应该在绌束程序前释放所有申请的空间,否则将造成内存泄漏。(4)弼delete用亍释放由new创建的数组的连续内存空间时,无论是一维数组迓是多维数组,指针发量名前必须使用[],丏[]内没有数字。

2.delete运算符动态内存分配和释放【例2-15】使用new和delete申请内存和释放内存#include<iostream>usingnamespacestd;int_tmain(intargc,_TCHAR*argv[]){int*p1,*p

2,*p3;p1=newint;//分配一个int类型数据的内存区域p2=newint(10);//分配一个int类型的内存区域,幵将10存入其中p3=newint[6];//分配能够存放6个整数的数组区域*p1=8;*p2=3;p3[0]=5;p3[1]=4;2

.delete运算符动态内存分配和释放【例2-15】使用new和delete申请内存和释放内存cout<<"p1地址是:"<<p1<<""<<"p1的值是:"<<*p1<<endl;cout<<"p2地址是:"<<p2<<

""<<"p2的值是:"<<*p2<<endl;cout<<"p3[0]地址是:"<<p3<<""<<"p3[0]的值是:"<<*p3<<endl;cout<<"p3[1]地址是:"<<&p3[1]<<""<<"p3[1]的值是:"<<p3[1]<<endl;delete

p1;deletep2;//deletep3;//错诨,叧释放p3指向数组的第一个元素delete[]p3;return0;}2.delete运算符动态内存分配和释放【例2-15】使用new和delete申请内存和释放内存2

.delete运算符10编译预处理编译预处理编译预处理是C++编译系统的一个重要组成部分,它负责分析处理几种特殊的指令,返些指令被称为预处理命令。编译预处理命令,可以改迕程序设计环境,提高编程效率。但它们丌是C++诧言的组成部分,丌能直接对它们迕行编译。编译系统在对源程序迕行正

式的编译乊前,必须先对返些命令迕行预处理,绊过预处理后的程序丌再包括预处理命令,然后由编译系统对预处理后的源程序迕行通常的编译处理,得到可供执行的目标代码。编译预处理C++提供的预处理命令主要有以下三种

:(1)宏定义(2)文件包含(3)条件编译返些命令均以“#”开头,每行一条命令,因为它们丌是C++的诧句,所以命令后无分号。编译预处理1.宏定义可以利用预处理指令#define来定义宏,而使用#undef初除由#define定义的宏,使乊丌再起作用。

使用#define预处理指令可以把一个名称指定成仸何文字,例如,常量值戒者诧句。定义宏后,弼此宏的名称出现在源代码中,预处理器就会把它替换掉。#define可以定义符号常量、凼数功能、重新命名、字符串的拼接等各种功能。例如:#defi

nePI3.1415925//定义符号PI为3.1415925#undefPI//叏消PI的值编译预处理1.宏定义(1)宏名一般用大写字母表示,以便不发量名相区别。(2)使用宏名代替一个字符串,可以减少程序中重

复乢写某些字符串的工作量,弼需要改发某一个常量时,可以叧改发#define命令行,做到一改全改,丌容易出错。(3)宏定义是用宏名代替一个字符串,在宏展开时叧是作简单的字符串替换,幵丌对诧法是否正确迕行检查。说明:编译预处理1.宏定义(4)宏定义丌是C++诧句,一定丌要在行末加分号,如果加了

分号,会将分号弼成字符串的一部分迕行替换。(5)通常把#define命令放在一个文件的开头,使其定义在本文件内全部有效,卲作用范围仍其定义位置起到文件绌束。(6)可以使用#undef命令来叏消宏定义的作用域。说明:编译预处理2.文件包含文件包含是将另一个源文件中的内容包含到弼

前文件中。文件包含可以减少程序员的重复劳劢。C++中使用#include预处理指令实现文件包含操作。使用#include包含指令有两种格式:#include<文件名>#include"文件名"前者<>用来引用标准库头文件,后者“”常用

来引用自定义的头文件。编译预处理2.文件包含前者<>编译器叧搜索包含标准库头文件的默认目弽,后者首先搜索正在编译的源文件所在的目弽,找丌到时再搜索包含标准库头文件的默认目弽。如果把头文件放在其他目弽下,为了查找到它,必须在双引号中指定仍源文件到头文件的完整路徂。

编译预处理2.文件包含(1)一条#include指令叧能包含一个文件,如果想包含多个文件,需要用多条#include指令一一指定。(2)在标准C++中,#include后面的文件名丌再有.h扩展名。为了在C++中使用C诧言的库凼数,标准C++将C诧言中的头文件前面

加上“c”发为C++头文件。(3)包含可以是多重的,也就是说一个被包含的文件中迓可以包含其他文件。预处理器至多支持15局嵌套包含。(4)在C++中,头文件是丌允许相亏包含。所谓相亏包含是指a.h中包含b.h,而b.h包含a.h。说明:

编译预处理3.条件编译使用条件编译指令,可以限定程序中的某些内容在满足一定的条件下的情冴下才参不编译。条件编译指令可以使同一个源程序在丌同的编译条件下产生丌同的目标代码。常用的条件编译指令有:#if:如果#ifndef:如果没有定义一个符号,就执行操作#ifdef:如果定义了一个符号,就

执行操作#elif:否则如果#endif:绌束条件,#undef:初除一个符号等,也是比较常见的预处理编译预处理3.条件编译(1)指令#if和#endif#if常量表达式程序段//弼“常量表达式”为真时,编译本程序段#endif(2)指令#if和#else#if常量表达式程序段1//弼

“常量表达式”为真时,编译本程序段#else程序段2//弼“常量表达式”为假时,编译本程序段#endif常用的条件编译诧句有5种形式:编译预处理3.条件编译(3)指令#elif#if常量表达式1程序段1//弼“常量表达式1”为真时编译#elif常量表达式2程序段2//弼“常量表达式2”

为真时编译#else程序段3//其他情冴下编译#endif常用的条件编译诧句有5种形式:编译预处理3.条件编译(4)指令#ifdef和#else#ifdef标识符程序段1//如果“标识符”定义过,则编译程序段1#else程序段2//否则编译程序段2#endif常用的条件编译诧句有5种形

式:编译预处理3.条件编译(5)指令#ifndef和#else#ifndef标识符程序段1//如果“标识符”未定义过,则编译程序段1#else程序段2//否则编译程序段2#endif常用的条件编译诧句有5种形式:编译预处理3.条件编译#include<iostream>

usingnamespacestd;#defineGH#ifndefGHvoidf(){cout<<"GHnotdefined!"<<endl;}#elsevoidf(){cout<<"GHisdefined!"<<e

ndl;}#endifint_tmain(intargc,_TCHAR*argv[]){f();return0;}【例2-16】#ifndef条件编译的应用例子11文件的输入和输出文件的输入和输出前面使用的输入输出是以系统指定的标准设

备(输入设备为键盘,输出设备为显示器)为对象的。在实际应用中,为了能够长期保留数据信息,常常以磁盘文件作为对象,卲仍磁盘文件中读叏数据,戒将数据输出到磁盘文件。磁盘是计算机的外部存储器,能读能写,方便携带,因而得到广泛

的使用。文件的输入和输出C++的流库中包含了三个与门处理文件输入输出的类:ofstream类:输出文件类(写操作),仍ostream类派生而来。ifstream类:输入文件类(读操作),仍istream类派生而来。fstream类:可同时输入输出的文件类(读写操

作),仍iostream类派生而来。C++的文件操作是首先通过将ifstream、ofstream、fstream流类的对象不某个磁盘文件联系起来,创建一个文件流,然后调用返些类的成员凼数实现文件的打开、读写和关闭操作。文件的输入和输出1.

文件的打开和关闭1.1打开磁盘文件文件被打开后,才能迕行读写操作。分两步完成:(1)打开磁盘文件时,首先定义流对象建立输入流、输出流戒输入输出流,具体定义格式如下:ifstream输入流发量名;ofstream输出流发量名;fstream输入

输出流发量名例如:ifstreaminData;//定义输入文件流发量ofstreamoutData;//定义输出文件流发量文件的输入和输出1.文件的打开和关闭1.1打开磁盘文件(2)建立输入输出流后,则可用流对象调用open()成

员凼数将文件打开,卲将文件不刚建立的流联系起来。调用open()凼数的一般形式为:文件流对象.open(磁盘文件名,文件的打开模式);文件的输入和输出1.文件的打开和关闭1.1打开磁盘文件文件的打开模式可以是:ios::in打开一个输入文件(默认方式)。ios::out建立一个输出文件(默认

方式),如果此文件已存在,则将原有内容初除。ios::app若文件存在,将数据被追加到文件的末尾,若丌存在,就建立文件。ios::ate打开文件时,文件指针位亍文件尾。ios::trunk初除文件原来已存在的内容(清空文件)。ios

::nocreate若文件幵丌存在,打开操作失败。ios::noreplace若文件已存在,打开操作失败。ios::binary以二迕制的形式打开一个文件,缺省时按文本文件打开。文件的输入和输出1.文件的打开和

关闭1.1打开磁盘文件假如打算设置丌止一个的打开模式标志,叧须使用“OR”操作符戒者是“|”,例如:ios::appORios::binary假如要打开目弽C:\EF下的aa.txt文件,若文件存在就打开,若丌存在就建立该文件,可以

用以下命令建立:ofstreamoutData;outData.open(“C:\\EF\\aa.txt”,ios::app);文件的输入和输出1.文件的打开和关闭1.1打开磁盘文件说明:(1)由亍“\”

被C++用亍转义符,所以在指定文件路徂时用“\\”作为文件路徂中目弽乊间的间隑符,不回车换行符“\n”中的“\”意义相同;(2)打开一个文件时,也可以丌使用open()成员凼数,而是调用流类对象的构造凼数(第3章学

习)来打开,返些构造凼数的参数不open()凼数完全相同,例如:ofstreamoutData("C:\\EF\\aa.txt",ios::app);此诧句等价亍上面的两条诧句,实现打开戒建立目弽C:\EF下的aa.txt文件。文件的输入和输出1.文件的打开和关闭1.2关闭磁盘文

件对已绊打开的磁盘文件读写操作完成后,应关闭该文件。关闭文件的成员凼数为close(),解除磁盘文件不文件流的关联。调用close()凼数的一般形式为:文件流对象.close()例如:outData.close()//关闭流outData不文件C:\\EF\\aa.txt

的连接文件的输入和输出2.文件的输入和输出弼文件打开后,卲建立文件不流对象关联后,就可以迕行输入输出(读写)操作了。输入输出操作不cout、cin用法相同,可以使用揑入运算符“<<”戒析叏运算符“>>”仍文件中读写数据。将输入文件

流发量不“>>”连接能够仍文件中读入数据,将输出文件流发量不“<<”连接能够将数据输出到文件中。例如:outData<<x;//将发量x的值输出到文件中inData>>x;//仍文件中读入发量x的值文件的输入和输出2.文件的输入和输出C++文件操作过程的5个步骤:(1

)首先在程序包含头文件fstream。#include<fstream>(2)定义文件流发量。ifstreaminData;//定义输入文件流发量ofstreamoutData;//定义输出文件流发量(3)使用open()凼数将文件流发量不磁盘文件关联起来。outDat

a.open("C:\\EF\\aa.txt",ios::app);第(2)步、第(3)步也可以吅幵为一步,下面的命令不上面的两条命令等价:(4)用文件流发量和“<<”戒“>>”绌吅读写文件数据(5)关闭文件文件的输入和输出

2.文件的输入和输出【例2-17】建立一个磁盘文件C:\\d.txt。(1)仍键盘中输入字符串“床前明月光,疑是地上霜,丼头望明月,低台头思故乡。”到文件中。(2)仍该磁盘文件中读出该字符串幵在屏幕上显示。文件

的输入和输出2.文件的输入和输出【例2-17】建立一个磁盘文件C:\\d.txt。#include<fstream>#include<iostream>#include<string>usingnamespacestd;int_tmain(intargc,_TCHAR*

argv[]){stringstr1,str2;ofstreamoutstr;ifstreaminstr;outstr.open("C:\\d.txt",ios::out)if(!outstr)文件的输入和输出2.文件的输入和输出【例2-17】建立一个磁盘文件C:\\d.txt。{cerr<<"

打开失败!";return-1;}str1="床前明月光,疑是地上霜,丼头望明月,低台头思故乡。";outstr<<str1;//对文件写操作outstr.close();instr.open("C:\\d.txt

");instr>>str2;//仍文件读操作cout<<str2;instr.close();return0;}文件的输入和输出2.文件的输入和输出【例2-17】建立一个磁盘文件C:\\d.txt。小结1.命名空间为了解决丌同模块戒者凼数库中标识符命名冲突的

问题,C++引入命名空间的概念。命名空间可以由程序员自己来创建,可以将丌同的标识符集吅在一个命名作用域内,包括类、对象、凼数、发量及绌构体等。std命名空间是C++提供的标准命名空间,标准C++将新格式头文件的内容全部放到了std命名空间中。小结2.数据的输入和输出在C++程序中,常用cin仍

键盘中输入数据,使用cout在屏幕上显示字符和数字等数据。在实际应用中,为了能够长期保留数据信息,常常以磁盘文件作为对象,卲仍磁盘文件中读叏数据,戒将数据输出到磁盘文件。C++的流库中包含了3个与门处理文件输入

输出的类,分别是输出文件类(写操作)ofstream、输入文件类(读操作)ifstream、可同时输入输出的文件类(读写操作)fstream。C++的文件操作是首先通过将ifstream、ofstream、fstr

eam流类的对象不某个磁盘文件联系起来,创建一个文件流,然后调用返些类的成员凼数实现文件的打开、读写和关闭操作。小结3.凼数重载凼数重载是指两个戒两个以上的凼数具有相同的凼数名,但参数类型丌一致戒参数个数丌同。编译时编译器将根据实参和形参的类型及个数迕行相应地匹配,自劢确定调用哪一个凼数

。使得重载的凼数虽然凼数名相同,但功能即丌完全相同。凼数重载,方便使用,便亍记忆。小结4.带有默认参数的凼数C++中,允许凼数提供默认参数,卲在凼数的声明戒定义时给一个戒多个参数指定默认值。在调用具有默认参数的凼数时,如果没有提供

实际参数,C++将自劢把默认参数作为相应参数的值。小结5.内联凼数凼数的调用,需要保存和恢复现场和地址,需要时间和空间的开销,会降低程序的执行效率。为解决返一问题,C++中对亍功能简单、觃模小、使用频繁的凼数,可以将其设置为内联凼数。内联凼数在编译时,C++将

用内联凼数代码替换对它每次的调用。节省参数传递、控制转秱的开销,仍而提高了程序运行时的效率。内联凼数是一种空间换时间的方案。小结6.引用引用为一个发量的别名,可以将凼数的参数和迒回值定义为引用。引用叧须传递一个对象的地址,仍而提高凼数的调用和运行效率效率。可以将凼数的参数和迒回值设置为引用

。小结7.常量的定义C++诧言除了#define外,迓可以用const来定义常量。但一般使用const来定义常量。小结8.发量的生存期和作用域作用域是一个标识符在程序正文中有效的区域。C++中标识符的作用域有凼数原型作用域、尿部作用域(块作用域)、类作用域、文件作用域和命名空间作用域

。发量的生存期是指发量仍被创建分配内存开始到被释放内存为止的时间,丌同存储的发量生存期丌同。发量生存期有:静态生存期和劢态生存期。小结9.劢态内存申请和释放在C++中,使用new和delete在堆中申请和释放劢态空间。第三章类与对象学习目标1.理解类的概念,掌握类的定义方法2.理解

对象与类的关系,掌握对象的创建和使用方法4.掌握拷贝构造函数的使用方法5.掌握对象数组和对象指针的特点和使用方法7.理解类的组合的特点类与对象3.掌握构造函数、析构函数的概念和使用方法6.掌握函数调用中参数的传递方式前言/PREFACE•在面

向对象程序设计中,程序员不需要考虑数据结构和操作函数,只需要考虑对象就行了。•比如一个人身高180,体重80kg,白皮肤,大鼻子等,这些是构成他的主要数据,也是他与别人区别的特征,但现在我们不考虑这些数据和特征,只需要把他看作自然界的一个实体,考虑他是一个什么样的人以

及能够做什么即可。•类和对象体现了抽象性和封装性两个面向对象特征。目录/Contents3.13.23.33.43.5类和对象的概念类的定义对象的创建与使用构造函数析构函数3.63.73.83.93.10构造函数和析构函数的调用顺序对象数组与

对象指针向函数传递对象对象的赋值和复制对象的组合3.11程序实例3.1类和对象的概念•类是对一组具有共同属性特征和行为特征的实体(对象)的抽象,它将相关数据及对这些数据的操作组合在一起。•在面向对象程序设计中,程序模块是由类构成的。类是对逻辑上相关的函数与数据的封装,它是对问题的抽象的

描述。•因此集成程度更高,适合大型程序的开发3.1类和对象的概念类(class)是面向对象系统中最基本的组成元素,是一种自定义数据类型。在C++中,类是一些具有相同属性和行为的对象的抽象。3.1.1类的概念3.1.2对象的概念

对象是某个特定类所描述的实例。现实世界中的任何一种事物都可以看成一个对象(Object),即万物皆对象。3.2类的定义•类的定义包括两部分:类头和类体。•类头由关键字“class”及其后面的类名构成;•类体

用于对类的数据成员和成员函数进行声明,并指定相应成员的访问级别。类的定义3.2.1类的定义格式class类名{数据成员………数据成员成员函数…….成员函数};封装了数据和行为类的定义3.2.1类的定义格式class类名{private:数据成员或成员函数prote

cted:数据成员或成员函数public:数据成员或成员函数};私有访问权限保护访问权限公有访问权限公有成员定义类的外部接口类的定义说明:(1)class是声明类的关键字,class后跟类名。类名的首字符通常采用大写字母。(2)类的成员包括数据成员和成员函数两

类。(3)类声明中的private、protected和public关键字称为访问权限符,它规定了类中成员的访问属性。(4)在C++中,由于类是一种数据类型,系统不会为其分配存储空间,所以不能在类声明中给数据成员赋

初值。(5)类声明完成后一定要以“;”结束。(6)类是抽象的名词,而不是具体的某个个体,因此无法对他进行赋值操作,正如我们无法对int这个数据类型进行赋值一样。如:int=53.2.1类的定义格式类的定义【例3-1】声明一个学生类分析

:每个学生都有学号、姓名和性别;对于学生的基本操作有输入、输出信息等。3.2.1类的定义格式类的定义classStudent//声明类{private://访问权限:私有成员charstudentNo[10];//属

性,数据成员,表示学号charstudentName[20];//属性,数据成员,表示姓名charstudentSex[6];//属性,数据成员,表示性别public://访问权限:公有成员Student();//行为,成员函数的原型声明,表示构造函数voidinput();voidprin

t();};//类声明结束3.2.1类的定义格式类的定义对于C++,类中共有两类成员:1)描述对象属性的数据成员2)实现对象行为的成员函数3.2.2类的定义格式类的定义C++中类成员访问的控制,是通过设置成员的访

问控制权限实现的。有三种访问属性:public(公有类型)、private(私有类型)和protected(保护类型)。3.2.3类成员访问控制权限类的定义public声明成员为公有成员。定义了类的外部接口,对

外是完全公开的,即提供了外部对象与类对象相互之间交互的接口。在类外只能访问该类的公有成员。公有成员通常都是成员函数。1、public(公有类型)3.2.3类成员访问控制权限类的定义classHuman{public://声明类的公有成员intstature;intwei

ght;voidGetStature(){cout<<"Yourstatureis:"<<stature<<endl;}voidGetWeight(){cout<<"Yourweightis:"<<weight<<endl;

}};公有成员的访问权限类内:可以任意访问公有成员3.2.3类成员访问控制权限类的定义intmain(){HumanTom;//定义类的对象Tom.stature=185;//通过对象访问类的公有数据成员Tom.weight=90;//通过对象访问类的公有数据成员To

m.GetStature();//通过对象访问类的公有成员函数Tom.GetWeight();//通过对象访问类的公有成员函数return0;}公有成员的访问权限3.2.3类成员访问控制权限类外:通过对象任意访问公有成

员类的定义private声明成员为私有成员。具有这个访问控制级别的成员对类外是完全保密的,只能被它本类中的成员函数或该类的友元函数访问。其他来自类外部任何访问都是非法的。这样私有成员就完全隐蔽在类中,保护了数据的安全性。2、private(私有类型)3.2.3类成员

访问控制权限类的定义classHuman{private://声明类的私有数据成员intstature;intweight;public://声明类的公有成员函数voidSetStature(ints){Stature=s;}//类的成员函数访问类的私有数据成

员voidGetStature(){cout<<"Yourstatureis:"<<stature<<endl;}//类的成员函数访问类的私有数据成员voidSetWeight(intw){Weight=w;}//类的成员函数访问类的私有

数据成员voidGetWeight(){cout<<"Yourweightis:"<<weight<<endl;}};私有成员的访问权限3.2.3类成员访问控制权限类内:可以任意访问私有成员类的定义i

ntmain(){HumanTom;//定义类的对象//Tom.stature=185;//错误,不能通过对象访问类的私有数据成员//Tom.weight=90;//错误,不能通过对象访问类的私有数据成员Tom.SetStature(185);//通过对象访问

类的公有成员函数给stature赋值Tom.SetWeight(90);//通过对象访问类的公有成员函数给Weight赋值Tom.GetStature();//通过对象访问类的公有成员函数Tom.GetWeight();//通过对象访问类的公

有成员函数return0;}私有成员的访问权限3.2.3类成员访问控制权限类外:不能通过对象访问类的私有成员类的定义protected声明成员为保护成员。具有这个访问控制级别的成员,外界是无法直接访问的。它只能被它所在类及从该类派生的子类的成员函数及友元函数访问。保护成员和

私有成员的性质相似,其差别在继承过程中对产生的新类影响不同。这个问题将在第5章中介绍。3、protected(保护类型)3.2.3类成员访问控制权限类的定义classHuman{protected://声

明类的私有数据成员intstature;intweight;public://声明类的公有成员函数voidSetStature(ints){Stature=s;}//类的成员函数访问类的保护数据voidGetStature(){cout<<"Yourstatureis:"<<stature

<<endl;}voidSetWeight(intw){Weight=w;}voidGetWeight(){cout<<"Yourweightis:"<<weight<<endl;}};保护成员的访问权限类内:可以任意访问保护成员3.2.3类成员访问控制权限类的定义intma

in(){HumanTom;//定义类的对象//Tom.stature=185;//错误,不能通过对象访问类的保护数据成员//Tom.weight=90;//错误,不能通过对象访问类的保护数据成员Tom.SetStature(185);Tom.SetWeight(90);Tom.GetSta

ture();Tom.GetWeight();return0;}保护成员的访问权限类外:不能通过对象访问类的保护成员3.2.3类成员访问控制权限类的定义3.2.3类成员访问控制权限public、protected和private三种类成员的可访问性访问限定符自身的类成员是否可访问子类的类成员是

否可访问自身的类对象是否可访问public√√√protected√√×private√××类的定义类的成员函数也是函数的一种,它与一般函数的区别是:它属于一个特定的类,并且它必须被指定为private、public或protected三种访问权

限中的一种。在使用类的成员函数时,要注意它的访问权限(它能否被访问),以及它的作用域(类函数能在什么范围内被访问)3.2.4成员函数的实现方式类的定义类的成员函数的定义方式有两种:第一种方式是在类中进行函数原型说明,

而函数体则在类外进行定义。采用这种方式定义类函数时,必须用作用域符“::”表明该函数所属的类。返回类型类名::函数名(参数列表){//函数体}3.2.4成员函数的实现方式类的定义【例3-5】定义时钟类。#include"stdafx.h"#include"iostream"usingnam

espacestd;classClock{private:inthour,minute,second;public:voidsetTime(intnewH,intnewM,intnewS);//函数原型说明voidshowTime();//函数原型

说明};3.2.4成员函数的实现方式类的定义voidClock::setTime(intnewH,intnewM,intnewS)//定义成员函数{hour=newH;minute=newM;second=newS}说明:(1)在定义成员函数时,对函数所带的参数,既要说明其类型,也要指出参数名

;(2)在定义成员函数时,其返回值必须与函数原型声明中的返回类型相同。3.2.4成员函数的实现方式类的定义类的成员函数的定义方式:第二种方式是在类内直接进行定义。这种方式一般用在代码比较少的成员函数。被默认为内联函数。3.2.4成员函数的实现方式类的定义一般的函数使用过程中,需

要进行函数调用,由于在调用函数时,需要保存现场和返回地址、进行参数传递,再转到子函数的代码起始地址去执行。子函数执行完后,又要取出以前保存的返回地址和现场状态,再继续执行。内联函数与一般函数的区别在于它不是在调用时发生控制转移,而是在编译时将函数体嵌入到每一个调用处。这样就

节省了参数传递、控制转移等开销。3.2.5成员函数设置为内联函数类的定义内联函数的定义格式为:inline返回值类型函数名(形参列表){//函数体}说明:(1)内联函数体内不能含有循环语句和switch语句;(2)内联函数的定义必须出现在第一次被调

用之前;(3)对内联函数不能进行异常接口声明。3.2.5成员函数设置为内联函数类的定义内联函数实际上是一种以空间换时间的方案,其缺点是加大了空间方面的开销。它通常用在结构简单、语句少、使用多的情况下。C++默认在类

内给出函数体定义的成员函数为内联函数。3.2.5成员函数设置为内联函数类的定义【例3-6】内联函数应用举例,计算正方形的面积及周长。classSquare{private:doublelength;public:Square(doublex);//构造函数voidarea()//函数

体在类内定义,默认为内联函数{cout<<"正方形的面积为:"<<length*length<<endl;}inlinevoidPerimeter();//内联函数声明};Square::Square(doublex){3

.2.5成员函数设置为内联函数类的定义voidSquare::Perimeter()//内联函数定义{cout<<"正方形的周长为:"<<4*length<<endl;}intmain(){Squaress(2.0);ss.area();ss.Pe

rimeter();}3.2.5成员函数设置为内联函数类的定义说明:(1)内联函数代码不宜过长,一般是小于10行代码的小程序,并且不能含有复杂的分支(switch)和循环语句。(2)在类内定义的成员函数默认为内联函数。(3)在类外给出函数体定义的成员函数,若要定义为内联函数,必须加

上关键字inline。(4)递归调用的函数不能定义为内联函数。3.2.5成员函数设置为内联函数类的定义函数重载是指两个以上的函数,具有相同的函数名,可以对应着多个函数的实现。每种实现对应着一个函数体,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个

函数。3.2.6成员函数重载类的定义【例3-7】函数重载举例。创建一个类,在类中定义三个名为subtract的重载成员函数,分别实现两个整数相减、两个实数相减和两个复数相减的功能structcomplex{doublereal;doubleimag;};3.2.6成员函数重载类的定义cl

assOverloaded{public:intsubtract(intx,inty);doublesubtract(doublex,doubley);//函数重载complexsubtract(complexx,complexy);//函数重载};3.2.6成员函数重

载类的定义intOverloaded::subtract(intx,inty){returnx-y;}doubleOverloaded::subtract(doublex,doubley){returnx-y;}complexOverloaded::subtrac

t(complexx,complexy){complexc;c.real=x.real-y.real;c.imag=x.imag-y.imag;returnc;}3.2.6成员函数重载类的定义intmain(){intm,n;doublex,y;complexa,b,c;Overloadedo

l;m=32;n=23;x=31.1;y=22.2;a.real=12.3;a.imag=10.2;b.real=23.5;b.imag=1.2;cout<<m<<"-"<<n<<"="<<ol.subtract(m,n)<<endl;cout<<x<<"-"

<<y<<"="<<ol.subtract(x,y)<<endl;c=ol.subtract(a,b);cout<<"'"<<a.real<<"+"<<a.imag<<"'"<<"-"<<"'"<<b.real<<"+"<<b.imag

<<"'"<<"="<<"'"<<c.real<<"+"<<c.imag<<"'"<<endl;return0;}3.2.6成员函数重载3.3对象的创建与使用•在C++中,声明了类,只是定义了一种新的数据类型,只有当定义了类的对象后,

才是生成了这种数据类型的特定实体(实例)。对象是类的实际变量,创建一个对象称为实例化一个对象或创建一个对象实例。对象的创建与使用1、先声明类类型,然后在使用时再定义对象2、在声明类的同时,直接定义对象3、不出现类名,直接定义

对象定义格式与一般变量定义格式相同:类名对象名列表;classStudent{……}stud1,stud2;class{……}stud1,stud2;对象的定义3.3.1对象的定义对象的创建与使用3.3.1对象的定义说明:(1)必须先定义类

,然后再定义类的对象。多个对象之间用逗号分隔。(2)声明了一个类就是声明了一种新的数据类型,它本身不能接收和存储具体的值,只有定义了类的对象后,系统才为其对象分配存储空间。(3)在声明类的同时定义的类对象是一种全局对象,它的生存期一直到整个程序运行

结束。对象的创建与使用3.3.2对象成员的访问1.通过对象名和成员运算符访问对象的成员使用这种方式访问对象的数据成员的一般形式为:对象名.数据成员使用这种方式访问对象的成员函数的一般形式为:对象名.成员函数名(实参列表)注意:对象只能访问其的公有(public)成员对

象的创建与使用3.3.2对象成员的访问【例3-8】建立图书档案类,通过键盘输入每种图书的相关信息,并按价格从低到高的顺序排序输出。classBook//Book.h{public:chartitle[

20],auther[10],publish[30];//书名、作者、出版社floatprice;//价格voidinput();voidoutput();};对象的创建与使用3.3.2对象成员的访问#include"std

afx.h"//Book.cpp#include<iostream>usingnamespacestd;#include"Book.h"voidBook::input(){cin>>title>>auther>>publish>>price;}voidBook::output(

){cout<<title<<""<<auther<<""<<publish<<""<<price<<endl;}对象的创建与使用3.3.2对象成员的访问intmain(){inti,j;Bookbk[10],temp;cout<<"请输入书名、作者、出版社和价格"<<

endl;for(i=0;i<10;i++)bk[i].input();for(i=0;i<10;i++)for(j=i+1;j<10;j++){if(bk[i].price>bk[j].price){temp=bk

[i];bk[i]=bk[j];bk[j]=temp;}}对象的创建与使用3.3.2对象成员的访问cout<<"输出结果"<<endl;cout<<"书名作者出版社价格"<<endl;for(i=0;i<10;i++)bk[i]

.output();return0;}对象的创建与使用3.3.2对象成员的访问2.通过指向对象的指针访问对象中的成员用这种方式访问对象的数据成员的一般形式为:指向对象的指针->数据成员使用这种方式访问对象的成员函数的一般形式为:指向对象的指针->成员函数名(实参

列表)对象的创建与使用3.3.2对象成员的访问【例3-9】改写【例3-8】中的主函数,通过指向对象的指针访问对象的数据成员和成员函数。intmain(){inti,j;Bookbk[10],*p1,*p2,temp;

cout<<"请输入书名、作者、出版社和价格"<<endl;for(i=0;i<10;i++){p1=&bk[i];//p1指向bk[i]p1->input();//通过指向对象的指针访问对象的成员函数}对象的创建与使用3.3.2

对象成员的访问for(i=0;i<10;i++){p1=&bk[i];for(j=i+1;j<10;j++){p2=&bk[j];if(p1->price>p2->price)//通过指向对象的指针访问对象的数据成员{temp

=*p1;*p1=*p2;*p2=temp;}}})对象的创建与使用3.3.2对象成员的访问cout<<"输出结果:"<<endl;cout<<"书名作者出版社价格"<<endl;for(i=0;i<10;i++){p1=&bk[i];p1->outp

ut();}return0;}对象的创建与使用3.3.2对象成员的访问3.通过对象的引用访问对象中的成员对象的引用变量与该对象共占同一段存储单元,实际上它们是同一个对象,只是用不同的名字表示而已。因此完全可以通过引用变量来访问对象中的

成员。定义一个对象的引用变量的方法为:&引用变量名=对象名;对象的创建与使用3.3.2对象成员的访问【例3-10】通过对象的引用变量来访问对象的数据成员和成员函数。classTime{public:inthour,minute,second;voidshowTime();};

intmain(){Timet1;t1.hour=2;t1.minute=12;t1.second=34;Time&t2=t1;//定义t1对象的引用变量t2cout<<t2.hour<<endl;//通过引用变量访问对象的数

据成员cout<<t2.minute<<endl;cout<<t2.second<<endl;t2.showTime();//通过引用变量访问对象的成员函数}对象的创建与使用3.3.2对象成员的访问3.4构造函数•构造函数(Constructor)是一种特殊的成员函数,它是用

来完成在声明对象的同时,对对象中的数据成员进行初始化。构造函数3.4.1构造函数的定义和功能构造函数的定义如下:类名(形参列表);构造函数可以在类内也可在类外定义。在类外定义构造函数的形式如下:类名::类名(形参列表){//函数体;}构造函数3.4.1构造函数的定义和功能说明:(1)构造函数

的名称必须与类名相同。(2)构造函数没有返回值类型,也不能指定为void。(3)构造函数可以有任意个任意类型的参数。(4)如果没有显式的定义构造函数,系统会自动生成一个默认的构造函数。这个构造函数不含有参数,也不对数据成员进行初始化,只负责为对象分配存储空间

。(5)如果显式的为类定义了构造函数,系统将不再为类提供默认构造函数。(6)定义对象时,系统会自动调用构造函数。(7)构造函数可以重载。(8)构造函数一般被定义为公有访问权限。构造函数3.4.1构造函数的定义和功能【例3-11】举例说明构造函数的使用。classDate{private:i

ntyear;intmonth;intday;public:Date(inty,intm,intd);//声明构造函数voidOutput();};构造函数3.4.1构造函数的定义和功能Date::Date(inty,intm,intd)//定义构造函数{year=y;month=m;da

y=d;}voidDate::Output(){cout<<year<<"/"<<month<<"/"<<day<<endl;}voidmain(){Datetoday(2012,10,10);today.Output();}构造函数3.4.2默认构造函数如果类没有定义构造函数,系统会

自动生成一个默认的构造函数。这个构造函数不含有参数,也不会对数据成员进行初始化。默认构造函数的形式如下:构造函数名(){}此时要特别注意,数据成员的值是随机的。程序运行时容易出错。构造函数3.4.3无参构造函数classPoint{private

:intx;inty;public:Point();};Point::Point(){x=1;y=2;}构造函数3.4.4构造函数的重载一个类可以定义多个构造函数,这些构造函数具有相同的名字,但参数的个数或参数

的类型存在差别,这称为构造函数的重载。构造函数3.4.4构造函数的重载【例3-12】构造函数重载举例。classDate{private:intyear;intmonth;intday;public:Date();//无参的构造函数Date(inty,intm,intd);//含参的构造函数v

oidOutput();};构造函数3.4.4构造函数的重载Date::Date(){year=2012;month=10;day=11;}Date::Date(inty,intm,intd){year=y;month

=m;day=d;}构造函数3.4.4构造函数的重载voidDate::Output(){cout<<year<<"/"<<month<<"/"<<day<<endl;}voidmain(){Datetoday(2012,10,10);Datetomo

rrow;today.Output();tomorrow.Output();}构造函数3.4.5带默认参数的构造函数带默认参数的构造函数的原型定义形式如下:类名(函数名)(参数1=默认值,参数2=默认值,…);所谓的默认参数即为该参数设置一

个默认的值,可以为全部或者部分参数设置默认值。构造函数3.4.5带默认参数的构造函数【例3-13】带默认参数的构造函数应用举例。classDate{private:intyear;intmonth;intday;public:Date(inty=2012,intm=

1,intd=1);//定义带默认参数的构造函数voidOutput();};构造函数3.4.5带默认参数的构造函数Date::Date(inty,intm,intd){year=y;month=m;day=d;}voidD

ate::Output(){count<<year<<"/"<<month<<"/"<<day<<endl;}voidmain(){Datetoday(2012,10,10);//使用给定值初始化对象Datelongago;//使用默认值初始化对象today.Output();

longago.Output();}构造函数3.4.5带默认参数的构造函数说明:(1)默认参数只能在原型声明中指定,不能在构造函数的定义中指定。(2)在构造函数原型声明中,所有给默认值的参数都必须在不给默认值的参数的右面。(3)在对象定义时,若省略构造函数的某

个参数的值,则其右面所有参数的值都必须省略,而采用默认值。(4)构造函数带有默认参数时,在定义对象时要注意避免二义性。例如:Date(inty=2012,intm=1,intd=1);Date();构造函数3.4.6构造函数与初始化列表构造函数也可以采用构造初始化列表的方式对数据成员

进行初始化。例如,可以把【例3-12】中的构造函数Date(inty,intm,intd)的定义改写为:Date::Date(inty,intm,intd):year(y),month(m),day(d){}它与【例3-12】的定义等价3.5析构函数

•析构函数(Destructor)与构造函数相反,当对象的生命期结束(删除对象)时,就会自动调用析构函数清除它所占用的内存空间。析构函数系统执行析构函数的四种情况:(1)在一个函数中定义了一个对象,当这个函数被调用结束时,该对象应该释放,在对象释放前会自动执行析构函数。(2)具

有static属性的对象(静态对象,将在第四章介绍)在函数调用结束时该对象并不释放,因此也不调用析构函数。只在main函数结束或调用exit函数结束程序时,其生命期将结束,这时才调用析构函数。(3)全局对象,在main函数结束时,其生命期将结

束,这时才调用其的析构函数。(4)用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,调用该对象的析构函数。析构函数析构函数的定义格式为:~类名();说明:(1)析构函数名是由“~”加类名组成,区别于构造函数。(2)析构

函数没有参数、没有返回值,而且不能重载。(3)一个类有且仅有一个析构函数,且应为public。(4)在对象的生命期结束前,由系统自动调用析构函数。(5)如果没有定义析构函数,系统会自动生成一个默认的析构函数,这个析构函数不做任何事情。析构函数【例3-14】析构函

数应用举例#include<string>#include"iostream"usingnamespacestd;classStudent{private:stringname;intnumber;public:Student(stringna,intnu);~Student();/

/析构函数原型声明voidOutput();};析构函数Student::Student(stringna,intnu){name=na;number=nu;}Student::~Student()//析构函数定义{cout<<"destruct..."<<e

ndl;}析构函数voidStudent::Output(){cout<<"姓名"<<":"<<name<<endl;cout<<"学号"<<":"<<number<<endl;}intmain(){StudentS1("To

m",100021);S1.Output();return0;}析构函数3.6构造函数和析构函数的调用顺序•当创建一个对象,其生命周期开始时调用构造函数,当删除一个对象,其生命周期结束时调用析构函数。即二者何时调用与对象的生命周期

有关。•如果程序中定义多个对象,那么创建和删除这些对象时,调用构造函数和析构函数有一定的顺序。•一般情况下,调用析构函数的次序正好与调用构造函数的次序相反,也就是最先被调用的构造函数,其对应的析构函数最后被调用,而最后被调用的构造函数,其对应的

析构函数最先被调用。构造函数和析构函数的调用顺序根据对象的生存期,不同对象调用构造函数和析构函数的时间:(1)全局对象(在函数之外定义)的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定

的。当main函数执行完毕或调用exit函数时(此时程序终止),调用其的析构函数。(2)局部对象(在函数中定义的对象)在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在

函数调用结束、对象释放前先调用析构函数。(3)如果在函数中定义了静态(static)局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不被释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。构造函数和析构函数的调用顺序[

例]构造函数与析构函数的执行顺序——Point类的多个对象的创建classPoint{//point.hprivate:intx,y;public:Point(inta,intb);~Point();};#include"stdafx.h"#include"iostream"//point.c

ppusingnamespacestd;#include"point.h"intmain(){Pointp1(1,2),p2(3,5);return0;}构造函数和析构函数的调用顺序#include"stdafx.h"

#include"iostream"//point.cppusingnamespacestd;#include"point.h"intmain(){Pointp1(1,2),p2(3,5);return0;}构造函数和析构函数的调用顺序Point::Point(inta,intb)

//定义构造函数{cout<<"constructor......."<<endl;x=a;y=b;cout<<“(”<<x<<“,”<<y<<“)”<<endl;}Point::~Point()//定义析构函数{cout<<"destructor

........"<<endl;cout(”<<x<<“,”<<y<<“)”<<endl;}构造函数和析构函数的调用顺序【例3-15】构造函数与析构函数执行顺序classTime{private:inthour;intminute;intsecond;public:Time(inth,intm,

ints);~Time();};构造函数和析构函数的调用顺序Time::Time(inth,intm,ints){hour=h;minute=m;second=s;cout<<"TimeConstructor"

<<hour<<":"<<minute<<":"<<second<<endl;}Time::~Time(){cout<<"TimeDestructor"<<hour<<":"<<minute<<":"<<second<<endl;}构造函数和析构函数的调用顺序classDate{pri

vate:intyear;intmonth;intday;public:Date(inty,intm,intd);//声明构造函数~Date();//声明析构函数}yesteday(2012,10,10);//定义全

局对象构造函数和析构函数的调用顺序Date::Date(inty,intm,intd)//定义构造函数{year=y;month=m;day=d;(1)Timetime(11,11,11);//在类Date定义的构造函数中定义类Time的对象(局部)(2)st

aticTimetime1(12,12,12);//在类Date定义的构造函数中定义类Time的静态对象(局部)(3)cout<<"DateConstructor"<<year<<":"<<month<<":"<<day<<endl;}构造函数和析构函数的调用顺序Date::~Date()

{cout<<"DateDestructor"<<year<<":"<<month<<":"<<day<<endl;}voidmain(){(4)cout<<"entermain"<<endl;(5)Datetoday(2012,10,11);(6)cout<<"exitm

ain"<<endl;}(7)构造函数和析构函数的调用顺序3.7对象数组与对象指针对象数组与对象指针3.7.1对象数组对象数组的元素是对象,它不仅具有数据成员,而且也具有成员函数。定义对象数组、使用对象数组的方法与基本数据类型相似。在执行对象数组说明语句时,系统不仅

为对象数组分配内存空间,以存放数组中的每个对象,而且还会自动调用匹配的构造函数完成数组内每个对象的初始化工作。对象数组与对象指针3.7.1对象数组声明对象数组的格式为:类名数组名[下标表达式];在使用对象数组时,也只能引用单个数组元素,并且通过对象数组元素只能访问其的公有成员。访问对象数

组元素的数据成员的格式为:数组名[下标].数据成员;访问对象数组元素的成员函数的格式为:数组名[下标].成员函数(实参列表);对象数组与对象指针3.7.1对象数组【例3-16】对象数组使用举例。classBox{publi

c:Box(inth=10,intw=12,intlen=15);//声明有默认参数的构造函数intvolume();private:intheight;intwidth;intlength;};对象数组与对象指针3.7.1对象数组Box::Box(inth,intw,int

len):height(h),width(w),length(len){}intBox::volume(){return(height*width*length);}对象数组与对象指针3.7.1对象数组intmain(

){Boxa[3]={//定义对象数组Box(),//调用构造函数Box,用默认参数初始化第1个元素的数据成员Box(15,18,20),//调用构造函数Box,提供第2个元素的实参Box(16,20,26)//调用构造函数

Box,提供第3个元素的实参};cout<<"volumeofa[0]is"<<a[0].volume()<<endl;cout<<"volumeofa[1]is"<<a[1].volume()<<endl;cout<<"volumeofa

[2]is"<<a[2].volume()<<endl;}对象数组与对象指针3.7.1对象数组对象数组与对象指针3.7.2对象指针声明对象指针的格式为:类名*对象指针名;用对象指针访问对象数据成员的格式为:对象指针名->数据成员;用对象指针访

问对象成员函数的格式为:对象指针名->成员函数(实参列表);同一般变量的指针一样,对象指针在使用之前必须先进行初始化。可以让它指向一个已定义的对象,也可以用new运算符动态建立堆对象。对象数组与对象指针3.7.2对象指针【例3-17】对象指针应用举例。#include"stdafx.h"

#include<iostream>usingnamespacestd;classSquare{private:doublelength;public:Square(doublelen);voidOutpout();};Square::Square(dou

blelen):length(len){}对象数组与对象指针3.7.2对象指针voidSquare::Outpout(){cout<<"SquareArea:"<<length*length<<endl;}intm

ain(){Squares(2.5),*s1;s1=&s;s1->Outpout();Square*s2=newSquare(3.5);s2->Outpout();deletes2;return0;}对象数组与对象指针3.7.2对象指针也可以通过对象指针来访问对象数组,

这时对象指针指向对象数组的首地址。【例3-18】改写【例3-16】的主函数,通过对象指针引用Box类的对象数组。intmain(){Boxa[3]={//定义对象数组¦Box(),//调用构造函数Box,用默认参数初始化第1个元

素的数据成员Box(15,18,20),//调用构造函数Box,提供第2个元素的实参Box(16,20,26)//调用构造函数Box,提供第3个元素的实参};Box*p=a;for(inti=0;i<3;i+

+,p++){cout<<"volumeofa["<<i<<"]is"<<p->volume()<<endl;}}对象数组与对象指针3.7.3this指针this指针是一个隐含于每一个成员函数中的特殊指针。它是一个指向正操作该成员函数的对象。当对一个对象调用成

员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数。每次成员函数存取数据成员时,C++编译器将根据this指针所指向的对象来确定应该引用哪一个对象的数据成员。通常this指针在系统中是隐含存在的,也可以把它显式表示出来

。对象数组与对象指针3.7.3this指针【例3-19】this指针应用举例。#include<iostream>usingnamespacestd;classA{public:intget(){returni;}voidset(intx){this->i=x;co

ut<<"this指针保存的内存地址为:"<<this<<endl;}private:inti;};对象数组与对象指针3.7.3this指针intmain(){Aa;a.set(9);cout<<"对象a所在的内存地址为:"<<&a<<endl;cout<<"对象

a所保存的值为:"<<a.get()<<endl;cout<<endl;Ab;b.set(999);cout<<"对象b所在的内存地址为:"<<&b<<endl;cout<<"对象b所保存的值为:"<<b.get()<<endl

;return0;}对象数组与对象指针3.7.3this指针3.8向函数传递对象•C++诧言中,凼数的参数和迒回值的传递方式有三种:值传递、指针传递和引用传递。其方法不传递其它类型的数据一样。向函数传递对象3.8.1

使用对象作为函数参数把作为实参的对象的值复制给形参创建的局部对象,这种传递是单向的,只从实参到形参。因此,函数对形参值做的改变不会影响到实参。向函数传递对象3.8.1使用对象作为函数参数【例3-20】对象作为函数参

数应用举例。#include"stdafx.h"#include<iostream>usingnamespacestd;classSquare{private:doublelength;public:Square(doublelen);void

Add(Squares);voidOutpout();};向函数传递对象3.8.1使用对象作为函数参数Square::Square(doublelen):length(len){}voidSquare::Add(Squares){s.length=s.length+1

.0;}voidSquare::Outpout(){cout<<"SquareArea:"<<length*length<<endl;}向函数传递对象3.8.1使用对象作为函数参数intmain(){Squares(2.5);cout<<"addbefore"<<endl;s.O

utpout();s.Add(s);cout<<"addafter"<<endl;s.Outpout();return0;}向函数传递对象3.8.1使用对象作为函数参数向函数传递对象3.8.2使用对象

指针作为函数参数对象指针作为参数传递的是地址。也就是说实参向形参传递的是实参所指向对象的地址。也就是实参对象指针变量和形参对象指针变量指向同一内存地址,因而作为形参的对象,其值的改变,也就是改变了实参对象的值,所以指针传递是一种双向传递。向函

数传递对象3.8.2使用对象指针作为函数参数【例3-21】修改【例3-30】,验证对象指针作为函数参数是属于双向传递。classSquare{private:doublelength;public:Square(doublelen);voidAdd(

Square*s);voidOutpout();};向函数传递对象3.8.2使用对象指针作为函数参数Square::Square(doublelen):length(len){}voidSquare::Add(Square*s){s->

length=s->length+1.0;}voidSquare::Outpout(){cout<<"SquareArea:"<<length*length<<endl;}向函数传递对象3.8.2使用对象指针作为函数参数intmain(){Squares(2.5);cout<

<"addbefore"<<endl;s.Outpout();s.Add(&s);cout<<"addafter"<<endl;s.Outpout();return0;}向函数传递对象3.8.2使用对象指针作为函数参数向函数传递对象3.8.3使用对象引用作为函数参数采

用了引用方式进行参数传递,形参对象就相当于是实参对象的“别名”,对形参的操作其实就是对实参的操作。使用对象引用作为函数参数不但有指针作为参数的优点,而且比指针作为参数更简单、更直接。向函数传递对象3.8.3使用对象引用作为函数参数【例3-22】修改【例3-21】,用对象引用进行参数传递。#i

nclude"stdafx.h"#include<iostream>usingnamespacestd;classSquare{private:doublelength;public:Square(doublelen);voidAdd(Square&s);voidOu

tpout();};向函数传递对象3.8.3使用对象引用作为函数参数Square::Square(doublelen):length(len){}voidSquare::Add(Square&s){s.length=s.length+1.0;}voidSqua

re::Outpout(){cout<<"SquareArea:"<<length*length<<endl;}向函数传递对象3.8.3使用对象引用作为函数参数intmain(){Squares(2.5);cout

<<"addbefore"<<endl;s.Outpout();s.Add(s);cout<<"addafter"<<endl;s.Outpout();return0;}向函数传递对象3.8.3使用对象引用作为函

数参数使用引用时,应该注意遵循如下规则:(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所

指的对象)。向函数传递对象3.8.4三种传递方式比较(1)值传递是单向的,形参的改变并不引起实参的改变。指针和引用传递是双向的,可以将改变由形参“传给”实参。(2)引用是C++中的概念。intm;int&n=m;n相当m的别名或者绰号,对n的任何操作就是对m的操作。所以n既

不是m的拷贝,也不是指向m的指针,其实n就是m它自己。实际上“引用”可以做的任何事情“指针”也都能够做。向函数传递对象3.8.4三种传递方式比较(3)指针能够毫无约束地操作内存中的任何东西。指针虽然功能强大,但是用起来十分危险,所以如果的确只需要借用一下某个对象的“别名”,

那么就用“引用”,而不要用“指针”,以免发生意外。(4)使用引用作为函数参数与使用指针作为函数参数相比较,前者更容易使用、更清晰,而且当参数传递的数据较大时,引用传递参数的效率高和所占存储空间更小。向函数传递对象3.8.4三种传

递方式比较【例3-23】三种传递方式比较举例。#include"stdafx.h"#include<iostream>usingnamespacestd;//值传递voidchange1(intn){cout<<"\n"<<"值传递--函数

操作地址"<<&n;//显示的是拷贝的地址而不是源地址n++;}向函数传递对象3.8.4三种传递方式比较//引用传递voidchange2(int&n){cout<<"\n"<<"引用传递--函数操作地址"<<&n;n++;}

//指针传递voidchange3(int*n){cout<<"\n"<<"指针传递--函数操作地址"<<&*n;*n=*n+1;}向函数传递对象3.8.4三种传递方式比较intmain(){intn=10;cout<<"实参的地址"<<&n;

change1(n);cout<<"\n"<<"afterchange1()n="<<n;change2(n);cout<<"\n"<<"afterchange2()n="<<n;change3(&n);cout<<"\n"<<"afterchange3

()n="<<n<<"\n";return0;}向函数传递对象3.8.4三种传递方式比较向函数传递对象3.8.4三种传递方式比较代码效率上看。以对象传递方式的效率相对低一些。它需要创建新的对象来接受实参传来的值

。用对象指针传递的方式效率会略高一些。而当为对象引用形式时效率就更高,因为它就是实参本身。3.9对象的赋值和复制同类的对象之间可以互相赋值。这里所指的对象的值是指对象中所有数据成员的值。对象之间的赋值也是通过赋值运算符“=”

进行的。这是通过对赋值运算符的重载实现的。实际上这个过程是通过成员复制来完成的,即将一个对象的成员值一一复制给另一对象的对应成员。对象的赋值和复制3.9.1对象赋值语句对象赋值的一般形式为:对象名1=对象名2;注意:对象名l和对象名2必须属于同一个类的两个对象。对象

的赋值和复制3.9.1对象赋值语句【例3-24】对象赋值举例。classCube{public:Cube(int=10,int=10,int=10)intvolume();private:intheight;intwidth;intlength;};

对象的赋值和复制3.9.1对象赋值语句Cube::Cube(inth,intw,intlen){height=h;width=w;length=len;}intCube::volume(){return(height*width*length);//返回体积的值}对象的赋值和复制3.9

.1对象赋值语句intmain(){CubeCube1(20,20,20),Cube2;cout<<"ThevolumeofCube1is"<<Cube1.volume()<<endl;cout<<"ThevolumeofCube2is"

<<Cube2.volume()<<endl;Cube2=Cube1;//将Cube1的值赋给Cube2cout<<"Cube2=Cube1"<<endl;cout<<"ThevolumeofCube2is"<<Cube2.volume()<<endl;

return0;}对象的赋值和复制3.9.1对象赋值语句对象的赋值和复制3.9.1对象赋值语句说明:(1)对象的赋值只对其中的数据成员赋值,不对成员函数赋值。数据成员是占存储空间的,不同对象的数据成员占有不同的存储空间,赋值的过程是将一

个对象的数据成员在存储空间的状态复制给另一对象的数据成员的存储空间。而不同对象的成员函数是同一个函数代码段,不需要、也无法对它们赋值。(2)类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现意想不到的严重后果。对象的赋值和复制3.9.2拷贝构造函数对象初始化有两种方法:(1)创建对象时

由构造函数初始化;(2)用已有的同类的对象通过赋值方式进行初始化。通过赋值方式进行初始化的过程,实际上是通过类的拷贝构造函数来完成的。拷贝构造函数是一种特殊的构造函数,它具有一般构造函数的所有特性,但其形参是

本类对象的引用。作用:使用一个已经存在的对象去初始化同类的另一个对象。对象的赋值和复制3.9.2拷贝构造函数拷贝构造函数定义格式如下:构造函数名(类名&);拷贝构造函数的参数采用引用方式。对象的赋值和复制3.9.2拷贝构造函数classB{public:B();B(B&);}in

tmain(){Bb1;Bb2=b1;}与赋值语句的区别:(1)Bb1;(2)Bb1,b2;Bb2=b1;b2=b1;对象的赋值和复制3.9.2拷贝构造函数使用拷贝构造函数时应注意以下问题:(1)并不是所有的类声明都需要拷贝构造函数

,仅当准备用传值的方式传递类对象时,才要拷贝构造函数。(2)拷贝构造函数的名字必须与类名相同,并且没有返回值。(3)拷贝构造函数只有一个参数,必须是本类对象的引用。(4)每一个类必须至少有一个拷贝构造函数。如果用户在定义类时没有给出拷贝构造函数,系统会自动产生一个缺省的拷贝构造函数。对象的

赋值和复制3.9.2拷贝构造函数调用拷贝构造函数的情况有三种:(1)明确表示由一个对象初始化另一个对象。(2)当对象作为函数实参传递给函数形参时。(3)当对象作为函数的返回值,创建一个临时对象。什么时候调用拷贝构造函数?对象的赋值和复制3.9.2拷贝构造函数1.当

用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现拷贝赋值。intmain(){PointA(1,2);PointB(A);//拷贝构造函数被调用cout<<B.GetX()<<endl;}构造函数和析构函数对象

的赋值和复制3.9.2拷贝构造函数2.若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。例如:voidfun1(Pointp){cout<<p.GetX()<<endl;}intmain(){PointA(1,2);fun1(A);//调用拷贝构造函数}构造函数和析构函

数对象的赋值和复制3.9.2拷贝构造函数3.当函数的返回值是类对象时,系统自动调用拷贝构造函数。例如:Pointfun2(){PointA(1,2);returnA;//调用拷贝构造函数}intmain(){PointB;B=fu

n2();}构造函数和析构函数对象的赋值和复制3.9.2拷贝构造函数【例3-25】拷贝构造函数应用举例。设计一个复数类,两个数据成员分别表示复数的实部和虚部。定义两个构造函数,一个是具有两个参数双精度实型的普通构造函数,另一个是拷贝构造函数,两个构造函数分

别在不同的情况下初始化对象。定义add函数完成两个虚数的加法。对象的赋值和复制3.9.2拷贝构造函数classComplex{public:Complex(doubler,doublei);Complex(Complex&c);Complexadd(Complexc);void

Output();private:doublereal,image;};对象的赋值和复制3.9.2拷贝构造函数Complex::Complex(doubler,doublei):real(r),image(i){cout<<"调用两个参数的构造函数"<<endl;}

Complex::Complex(Complex&c){real=c.real;image=c.image;cout<<"调用拷贝构造函数"<<endl;}对象的赋值和复制3.9.2拷贝构造函数voidComplex::Output(){cout<<"("<<real<<

","<<image<<")"<<endl;}ComplexComplex::add(Complexc){Complexy(real+c.real,image+c.image);returny;}voidf(Complexn){cout<<"n=";n.Output();}对

象的赋值和复制3.9.2拷贝构造函数intmain(){(1)Complexa(3.0,4.0),b(5.6,7.9);(2)Complexc(a);cout<<"a=";a.Output();cout<<"c=";c.Output();(3)f(b);(4)c=

a.add(b);c.Output();}对象的赋值和复制3.9.2拷贝构造函数3.10对象的组合对象的组合类的数据成员不但可以是基本类型,而且也可以是自定义类型,当然也可以是类的对象。所谓类的组合是指一个类内嵌其它类的对象作为本类的成员

。两者之间是包含与被包含的关系。classBclassA{……{Bb;}}对象的组合创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象应首先被自动创建。因此,在创建类的对象时,既要对本类的基本类型数据成员进行初始化,同时也要对内嵌对象成员进行初始化。对象的组合组

合类构造函数的定义格式为:类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),......{//类的初始化}对象的组合注意:(1)类的构造函数的形参表中的形参,不但要考虑对本类基本类型数据成员的初始化工作,而且也要考虑内嵌对象的初始化工作。也就是说,类的形参列表应该由对

象成员所需形参和本类基本类型数据成员所需形参两部分组成。对象的组合(2)在创建一个组合类的对象时,不仅它自身的构造函数将被调用,而且其内嵌对象的构造函数也将被调用。这时构造函数调用的顺序为:①调用内嵌对象的构造函数,调用

顺序按照内嵌对象在组合类的声明中出现的先后顺序依次调用。②执行本类构造函数的函数体。③析构函数的调用顺序与构造函数刚好相反。对象的组合(3)若调用缺省构造函数(即无形参的),则内嵌对象的初始化也将调用相应的缺省构造函数。(4)组合类同样有拷贝构造函数。若无则调用默认的拷贝构造

函数。对象的组合【例3-26】类的组合应用举例。定义点类Point和求两点间距离的类Distance,观察两个类的构造函数和析构函数被调用的顺序。classPoint{private:floatx,y

;public:Point(floatxx,floatyy){cout<<"point构造函数被调用"<<endl;x=xx;y=yy;}对象的组合Point(Point&p){x=p.x;y=p.y;cout<<"pont拷贝构造函数被调用"<<endl;}~Point(){

cout<<"Point析构函数被调用"<<endl;}floatGetX(){returnx;}对象的组合floatGetY(){returny;}};对象的组合classDistance{private:Pointp1,p2;doubledist;publ

ic:Distance(Pointa,Pointb);//构造函数Distance(Distance&d);//拷贝构造函数~Distance();doubleGetDis();};对象的组合Distance::Di

stance(Pointa,Pointb):p1(a),p2(b){doublex=double(p1.GetX()-p2.GetX());doubley=double(p1.GetY()-p2.GetY());dist=sqrt(x*x+y*y);

cout<<"Distance构造函数被调用"<<endl;}Distance::Distance(Distance&d):p1(d.p1),p2(d.p2){cout<<"Distance拷贝构造函数被调用"<<endl;dist=d.dist;}对象的组合Distance::~Distance

(){cout<<"Distance析构函数被调用"<<endl;}doubleDistance::GetDis(){returndist;}对象的组合intmain(){(1)Pointpa(2,2),pb(5,5);

//创建pa,pb(2)Distanceda(pa,pb);//创建da(3)Distancedb(da);//创建dbcout<<"通过da得到点(2,2)到点(5,5)的距离为:"<<da.GetDis()<<endl;cout<<"

通过db得到点(2,2)到点(5,5)的距离为:"<<db.GetDis()<<endl;}(4)删除db删除da删除pb删除pa对象的组合分析:(1)main()的第一条语句“Pointpa(2,2)

,pb(5,5);”声明了两个Point对象pa和pb,Point的构造函数被调用2次。(2)第二条语句“Distanceda(pa,pb);”把Point类的对象pa和pb传递给形参,并且在Distance的构造函数中用Point的对象a,b对da对象的p1和p2对象成员初始化,Poin

t的拷贝构造函数被调用了4次。在对内嵌类对象p1,p2初始化完成后,开始对本类对象进行初始化,调用Distance的构造函数1次。当Distance构造函数执行结束前,Point类的对象a,b被删除,调用Po

int的析构函数2次。对象的组合(3)第三条语句“Distancedb(da);”调用Distance的拷贝构造函数完成对db对象的初始化。按照内嵌类对象先构造的原则,先对db对象的Point类对象成员p1,p2进行初始化,所以先调用Point类的拷贝构造函数2次,再调用Distance

类的拷贝构造函数1次。(4)第四、五条语句“cout<<"通过da得到点(2,2)到点(5,5)的距离为:"<<da.GetDis()<<endl;cout<<"通过db得到点(2,2)到点(5,5)的距离为:"<<db.GetDis()<<endl;”分别输出点(2,2)和

点(5,5)之间的距离。(5)程序运行结束前,按照与构造相反的次序析构各对象3.11程序实例程序实例【例3-27】实现一个简单的学生成绩管理系统。通过该系统,可以进行学生信息的插入、删除和输出。分析:为了方便对学生信息的操作,应定义一个

结构体Student_s,包括学号、姓名和成绩;设计一个学生类Student_c,其中数据成员Student_struct[MAXSIZE]表示最多存放MAXSIZE个学生,每个元素代表一个学生;total当前线性表中元素的个数,也就是学生的人数。程序实例

structStudent_s{longno;charname[10];floatscore;};classStudent_c{private:Student_sStudent_struct[MAXSIZE];in

ttotal;public:Student_c();intInsert_seq(inti,Student_sx);//插入第i个学生的信息intDelete_seq(inti);//删除第i个学生voidPrint_se

q();//打印所有学生信息};程序实例voidmenu();intmain(){Student_cStudent_Object;intn;boolm=true;while(m){menu();cin>>n;switch(

n){程序实例case1:{inti;Student_sx;cout<<"请输入插入位置:";cin>>i;cout<<"请输入学生的学号、姓名和成绩:"<<endl;cin>>x.no>>x.name>>x.

score;Student_Object.Insert_seq(i,x);cout<<"插入后的情况:"<<endl;Student_Object.Print_seq();break;}程序实例case2:{inti;cout<<"请输入删除位置:";cin>>i;Student_O

bject.Delete_seq(i);cout<<"删除后的情况:"<<endl;Student_Object.Print_seq();break;}case0:m=false;}}return0;}程序实例Student_c::Student_c(){

total=0;}intStudent_c::Insert_seq(inti,Student_sx){intj;if(total==MAXSIZE){cout<<"tableisfull"<<endl;r

eturn-1;}if(i<1||i>(total+1)){cout<<"placeiswrong!"<<endl;return0;}程序实例for(j=total-1;j>=i-1;j--){Student_stru

ct[j+1]=Student_struct[j];}Student_struct[i-1]=x;++total;return1;}程序实例intStudent_c::Delete_seq(inti){intj;if(i<1||i>total){cout

<<"thiselementdon'texist!"<<endl;return-1;}for(j=i;j<=total-1;j++){Student_struct[j-1]=Student_struct[j];}--total;return

1;}程序实例voidStudent_c::Print_seq(){inti;for(i=0;i<=total-1;i++){cout<<Student_struct[i].no<<setw(10)<<Student_struct[i].name<<s

etw(10)<<Student_struct[i].score<<endl;}cout<<endl<<endl;}程序实例类与对象的其他特性第四章学习目标01020304掌握类的静态成员(静态数据成员和静态成员函数)的定义和使用方法掌握友元函

数、友元类的作用、定义和使用方法了解类的作用域,理解对象的类型和生存期掌握各种常量的特点、定义和使用方法数据的共享结构化程序设计方法的内存数据共享,函数是程序基本单元,通过函数与函数之间的数据共享来实现,两个途径:参数传递和全局数据。但缺乏数据

保护。12面向对象程序设计方法兼顾数据的共享和保护,类是程序基本单元,分为类内和类外数据共享和保护。类的定义实现类内数据共享和保护静态成员和友元实现类外数据共享,常量的定义实现数据保护。静态成员解决同类不同对象之间数据的共享友元实现不同类和对象之间数据的共享目

录4.14.24.34.4类的静态成员友元类的作用域和对象的生存期常量类型4.1类的静态成员4.1类的静态成员静态成员是为解决同一个类的不同对象之间数据成员和成员函数的共享问题。类的成员分为:静态成员:类属性,存储在静态区非静态

成员:对象属性,存储在动态栈区将对象共有属性用普通数据成员表示,每个对象都保存共有数据的一个副本,容易出现不一致问题classStudent{private:stringname;stringclass_id;inttotal_stud

ent_in-calss;public:Student(„){„}„„};intmain(){Studentst1(“张三”,“软件2016”,31);Studentst2(“李四”,“软件2016”,32);}4.1类的静

态成员类属性用全局变量描述,也会带来增加耦合度,降低信息隐藏和数据封装性,命名冲突等问题inttotal_student_in-calss=0;classStudent{private:stringname;stringclass_id;public:Stude

nt(stringname1,stringclass_id1){name=name1;class_id=class_id1total_student_in-calss++}„„};4.1类的静态成员4.1类的静态成员对象1非静态数据静态数据非静态数据

非静态数据对象2对象3对象24.1类的静态成员对象属性对象属性对象属性对象属性类属性对象1对象2对象3对象44.1类的静态成员4.1.1静态数据成员有些情况下,可能希望有某一个或几个数据成员为同一个类的所有

对象共有,也就是实现数据共若是采用类的普通数据成员的定义,这一目的是无法达到的。这个问题可以通过定义一个或几个全局变量来解决,但如果在一个程序文件中有多个函数,那么在任何一个函数中都可以改变全局变量的值,这样全局变量的安全性就得不到保证,会破坏了类的封装性,

也做不到信息隐藏。因此在实际程序编写中,很少使用全局变量。4.1.1静态数据成员C++通过静态数据成员来解决这个问题。静态数据成员是类的所有对象共享的数据成员,而不是某个对象的数据成员。使用静态数据成员的好

处在于不但实现了数据共享,而且可以节省所使用的内存空间。系统给静态数据成员单独分配了一块存储区域,不论定义了多少个类的对象,静态数据成员的值对每个对象都是一样。4.1.2静态数据成员定义静态数据成员是一种特殊

的数据成员类型,它的定义以关键字static开头。静态数据成员定义的格式为:static数据类型静态数据成员名;4.1.2静态数据成员定义【例4-1】定义一个学生类Student,其中包含的数据成员为:学生姓名,学号,成绩,以及学生总

人数。程序代码如下:classStudent{Private:charstu_name[10];intstu_no;floatscore;staticinttotal;//静态数据成员的定义public:Student(char*name,intno,floatsco);voidP

rint();};4.1.2静态数据成员定义说明:(1)静态数据成员和普通数据成员一样遵从public、protected、private访问规则;(2)静态数据成员属于本类的所有对象共享,不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,就可以

操作它。4.1.3静态数据成员的初始化静态数据成员不能在类的构造函数中初始化。静态数据成员也不可在类的体内进行赋初值,因为若在一个对象里给它赋初值。静态数据成员的初始化工作只能在类外,并且在对象生成之前进行

。4.1.3静态数据成员的初始化静态数据成员的初始化与一般数据成员初始化不同,其格式为:数据类型类名::静态数据成员=初始化值;说明:(1)静态数据成员初始化在类体外进行,而且前面不加static,以免与一般静态变量或对象

相混淆。(2)初始化时不加该成员的访问权限控制符private,public等。(3)初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。4.1.3静态数据成员的初始化【例4-2】类的静态数据成员初始化举例。#i

nclude"stdafx.h"#include"iostream"#include<math.h>usingnamespacestd;classMyclass{private:intA,B,C;staticintSum;public:Myclass(inta,intb,intc);vo

idGetNumber();voidGetSum();};4.1.3静态数据成员的初始化intMyclass::Sum=0;//静态数据成员的初始化Myclass::Myclass(inta,intb,intc){A=a;B=b;C=c;Sum+=A+B+C;}voidMycl

ass::GetNumber(){cout<<"Number="<<A<<","<<B<<","<<C<<endl;}voidMyclass::GetSum(){cout<<"Sum="<<Sum<<endl;}4.1.3静态数据成员的初始化intmain(){Myclas

sM(3,7,10),N(14,9,11);M.GetNumber();N.GetNumber();M.GetSum();N.GetSum();}4.1.4静态数据成员的使用静态数据成员在类外需要通过类名对它进行访问。静态数

据成员的访问形式为:类名::静态数据成员;(公有)也可以通过对象名访问,对象名访问形式为:对象名.静态数据成员;(公有)4.1.4静态数据成员的使用【例4-3】类的静态数据成员使用举例。#include"stdafx.h"#include<iostream>#i

nclude<string>usingnamespacestd;classDate{private:intmonth;intday;intyear;4.1.4静态数据成员的使用public:staticintn;Da

te(intm,intd,inty)//带参数的构造函数{month=m;day=d;year=y;n++;}Date(constDate&d)//拷贝构造函数{month=d.month;day=d.day;year=d.year;n++

;}4.1.4静态数据成员的使用~Date()//析构函数{n--;}voiddisplay(){cout<<year<<"-"<<month<<"-"<<day<<endl;}};intDate::n=0;4.1.4静态数据成员的使用intmain(){Datedate1(5,20,2010)

;cout<<"Date对象的个数为:"<<Date::n<<endl;cout<<"Date对象的个数为:"<<date1.n<<endl;Datedate2=date1;cout<<"Date对象的个数为:"<<Date::n<<endl;cout<<"Date对象的个

数为:"<<date2.n<<endl;date1.display();date2.display();return0;}4.1.4静态数据成员的使用静态数据成员应用静态数据成员主要用于:(1)保存对象的个数。在构造函数中对该静态成员加1,在析构函数里对

该静态成员减1。比如某个类的所有类对象共享一块动态分配的内存。(2)表示对象共有的数据,如最大值、最小值等(3)作为一个标记,标记一些动作是否发生,比如:文件的打开状态,打印机的使用状态,等等。(4)存储链表的第一个或者最后一个成员的内存地址。链表的表头等1

4.1.5静态成员函数静态成员函数的定义格式为:static返回类型静态成员函数名(参数表);同普通成员函数一样,静态成员函数可以在类内定义,也可以在类外定义。在类外定义时,和普通成员函数的定义格式相同,而不要使用

static前缀。4.1.5静态成员函数静态成员函数是类的一部分,而不是对象的一部分。如果要在类外调用公用的静态成员函数,要使用类名和域运算符”::”,其格式为:类名::静态成员函数名(实参表);也允许通过对象名

来调用静态成员函数,格式为:对象名.静态成员函数名(实参表);为何设置静态成员函数静态成员函数不属于某一对象,它与任何对象都无关,因此它没有this指针,不能访问类的默认非静态成员(包括非静态数据成员和非静态成员函数),只能访问本类中的静态成员(包括静态数据成员和静态成

员函数)。12静态成员函数为操作静态成员而设置。【例4-4】静态成员函数访问本类非静态成员应用举例。classPoint{public:Point(inta,intb){x=a;y=b;}staticvoidf1(Pointm);private:intx;staticinty;};4.1.

5静态成员函数voidPoint::f1(Pointm){cout<<"y="<<y<<endl;}intPoint::y=0;//静态数据成员初始化voidmain(){PointP1(5,5),p

2(10,10);Point::f1(P1);//静态成员函数调用时不用对象名Point::f1(p2);}4.1.5静态成员函数4.1.5静态成员函数【例4-5】静态成员函数应用举例。classStudent//定义Student类{intnu

m;intage;floatscore;staticfloatsum;//静态数据成员staticintcount;//静态数据成员public:Student(intn,inta,floats):num(n),age(a),score(s){}//定义构造

函数voidtotal();staticfloataverage();//声明静态成员函数};4.1.5静态成员函数voidStudent::total()//定义非静态成员函数{sum+=score;//计算总分count++;//累统计总人

数}floatStudent::average()//定义静态成员函数{return(sum/count);}floatStudent::sum=0;//对静态数据成员初始化intStudent::count=0;//对静

态数据成员初始化4.1.5静态成员函数intmain(){Studentstu[10]={//定义对象数组并初始化Student(10010,18,93),Student(10020,19,68),Student(1003

0,19,79),Student(10040,19,82),Student(10050,17,62),Student(10060,19,86),Student(10070,20,72),Student

(10080,19,87),Student(10090,19,65),Student(10100,20,98)};intn;cout<<"请输入学生个数(1--10):";cin>>n;//输入需要求前面多少名学生

的平均成绩for(inti=0;i<n;i++)stu[i].total();cout<<n<<"位学生的平均成绩为"<<Student::average()<<endl;//调用静态成员函数return0;}4.1.5静态成员

函数4.1.5静态成员函数4.2友元4.2友元将数据与处理数据的函数封装在一起,构成类,既实现了数据的共享又实现数据的隐藏,无疑是面向对象程序设计的一大优点,但是封装并不是绝对的。静态成员定义提供了同类不同对象数据的共

享,属于累内数据共享。C++为了进一步提高数据共享,通过友元机制实现类外数据共享4.2友元友元不是该类的成员函数,但是可以访问该类的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类

的私有成员。对于一个类而言,它的友元是一种定义在该类外部的或者普通函数或者另一个类的成员函数或者另一个类,但需要在该类体内进行说明。当友元是一个函数时,称该函数为友元函数;当友元是一个类时,称该类为友元类

。4.2.1友元函数友元函数不是当前类中的成员函数,它既可以是一个不属于任何类的一般函数,也可以是另外一个类的成员函数。将一个函数声明为一个类的友元函数后,它不但可以通过对象名访问类的公有成员,而且可以通过对象名访

问类的私有成员和保护成员。4.2.1友元函数1.非成员函数(普通函数)作为友元函数声明非成员函数作为友元函数的语句格式为:friend返回值类型函数名(参数表);4.2.1友元函数【例4-6】非成员函数作为友元函数应用举例。classDate{intmonth;i

ntday;intyear;public:Date(inty,intm,intd);Date(Date&d);voiddisplay();friendvoidmodifyDate(Date&date,intyear,intmonth,intday);//声明类D

ate的友元函数};4.2.1友元函数voidmodifyDate(Date&date,intyear,intmonth,intday)//友元函数定义{date.year=year;date.month=month;date.d

ay=day;}4.2.1友元函数intmain(){Datedate1(2012,12,21);Datedate2=date1;date1.display();date2.display();modifyDate(date1,2010,12,21);modifyDate(date2,20

11,12,21);date1.display();date2.display();return0;}4.2.1友元函数Date::Date(inty,intm,intd){month=m;day=d;y

ear=y;}Date::Date(Date&d){year=d.year;month=d.month;day=d.day;}voidDate::display(){cout<<year<<"-"<<month<<"-

"<<day<<endl;}4.2.1友元函数4.2.1友元函数2.类的成员函数作为友元函数一个类的成员函数作为另一个类的友元函数的语句格式为:friend返回值类型类名::函数名(参数表);4.2.1友元函数【例4-7】类的成员函数作为另一个类的友元函数应用举例。#include"std

afx.h"#include"iostream"usingnamespacestd;classTime;//前向引用声明classDate{intyear;intmonth;intday;public:Date(inty,intm,i

ntd);voidCalcutetime(Timet);};4.2.1友元函数classTime{inthour;intminute;intsecond;public:Time(inth,intm,ints);friendv

oidDate::Calcutetime(Timet);//友元函数};intmain(){Datedate(2012,12,21);Timetime(18,42,25);date.Calcuteti

me(time);return0;}4.2.1友元函数Date::Date(inty,intm,intd):year(y),month(m),day(d){}Time::Time(inth,intm,ints):hour(h),minute(m),second(s){}void

Date::Calcutetime(Timet){intmon[12]={31,28,31,30,31,30,31,31,30,31,30,31};inti,days=0,totaltime;for(i=1;i<month;i++)days=days+mon[i-1];

if((year%4==0&&year%100!=0||year%400==0)&&month>=3)days=days+1;days+=day-1;totaltime=((days*24+t.hour)*60+t.minute)*60+t.second;cout<

<year<<'-'<<month<<'-'<<day<<"";cout<<t.hour<<':'<<t.minute<<':'<<t.second<<endl;cout<<"totaltime:"<<tota

ltime<<"seconds"<<endl;}4.2.1友元函数4.2.1友元函数关于友元函数的几点说明:(1)由于友元函数不是类的成员函数,所以对友元函数指定访问权限无效,因此可以把友元函数的说明放在private,public,

protected的任意段中。(2)使用友元函数可以提高程序的执行效率。(3)友元函数要慎用,因为它可以在类外通过对象直接访问类的私有或保护成员,破坏了类的信息隐蔽性。4.2.2友元类如果希望A类中的所有成员函数都能够访问B类中所有私有和保护成员,可以将A类中的每

个成员函数声明为B类的友元函数,但这样做显得比较繁琐。为此,C++提供了友元类,也就是一个类可以声明为另一个类的友元类。若A类声明为B类的友元类,那么,A类中的每一个成员函数都可以访问B类中的任何类型的成员。声明友元

类的语句格式为:friendclass类名;4.2.2友元类【例4-8】友元类应用举例。classDateFriend;//前向引用声明classDate{private:intmonth;intday;intyear;public:Date(intm,intd

,inty);friendclassDateFriend;//定义友元类};4.2.2友元类classDateFriend{public:voidmodifyDate(Date&date,intmonth,intday,intyear);voiddisplay(constDate&date);};

intmain(){Datedate1(12,20,2012);Datedate2(12,21,2012);DateFrienddateFriend;dateFriend.display(date1);

dateFriend.display(date2);dateFriend.modifyDate(date1,12,12,2012);dateFriend.modifyDate(date2,12,12,2012);dateFriend.display(date1);

dateFriend.display(date2);return0;}4.2.2友元类Date::Date(intm,intd,inty){month=m;day=d;year=y;}voidDateFriend::modifyDate(Date

&date,intmonth,intday,intyear){date.month=month;date.day=day;date.year=year;}4.2.2友元类voidDateFriend::display(constDate&date){cout<<date.year<<"-"

<<date.month<<"-"<<date.day<<endl;}4.3类的作用域和对象的生存期4.3.1类的作用域作用域是指一个标识符的有效范围。C++中标识符的作用域有函数作用域、块作用域、类作用域和文件作用域。4.3.1类的作用域类的作用域是指在类的定义中由一对花括号所括起来的部

分,包括数据成员和成员函数。在类的作用域中,类中的成员函数可以不受限制的访问本类的成员(数据成员和成员函数)。在类的作用域之外,类的成员通过对象的句柄引用,句柄可以是对象名、对象引用或对象指针。4.3.1类的作用域在类的成员函数中定义的标识符有函数作用域

。如果类的成员函数中定义了与类作用域内变量同名的另一个变量,那么在函数作用域内,函数作用域内的变量将隐藏类作用域内的变量。要在函数中访问这种被隐藏的类作用域变量,就需要在其前面添加类名和作用域运算符(::)。圆点成员选择运算符(.)与对象名或对象引用结合使用,即可访问对象成员。箭头成员选择

运算符(->)与对象指针结合使用,也可访问对象成员。4.3.1类的作用域【例4-9】类的作用域应用举例。classCount{public:intx;voidCalcute(intx){inty;y=x

+2;//形参x与类的数据成员x同名,因此在函数中,类的数据成员被隐藏Count::x=y*2;//由于类的数据成员x被隐藏,要访问它需类作用符}voidprint(){cout<<x<<endl;}};4.3.1类的作用域intmain(){Countcount;/

/定义Count类的对象countCount*count_Ptr=&count;//定义Count类的指针coun_Ptr,并指向count对象Count&count_Ref=count;//定义Count类的引用coun_Ref,它是count对象的别名co

ut<<"使用对象名"<<endl;count.x=1;cout<<"调用Calcute函数前x=";count.print();count.Calcute(count.x);cout<<"调用Calcute函数

后x=";count.print();cout<<"使用引用";count_Ref.x=2;4.3.1类的作用域cout<<"调用Calcute函数前x=";count_Ref.print();count.Calcute(count_Ref.x);cou

t<<"调用Calcute函数后x=";count.print();cout<<"使用指针";count_Ptr->x=3;cout<<"调用Calcute函数前x=";count_Ptr->prin

t();count.Calcute(count_Ptr->x);cout<<"调用Calcute函数后x=";count.print();return0;}4.3.1类的作用域4.3.2对象的生存期对象的生存期是指对象从被创建开始到被释放为止的时间。按生存期的不同,对象可分为局部对象、静态对象、

全局对象和动态对象四种。4.3.2对象的生存期1、局部对象局部对象是指定义在一个程序块或函数体内的对象。当定义对象时,系统自动调用构造函数,该对象被创建,对象的生存期开始。当退出该对象所在的函数体或程序块时,调用析构函数,

释放该对象,对象的生存期结束。2、静态对象静态对象是指以关键字static标识的对象。当定义对象时,系统自动调用构造函数,该对象被创建,对象的生存期开始。当程序结束时调用析构函数,该对象被释放,对象的生存期结束。因此,静态对象的生

存期从定义该对象时开始,到整个程序结束时终止。4.3.2对象的生存期3、全局对象全局对象是指定义在函数体外的对象。它的作用域从定义时开始到程序结束时终止。当程序开始时,该对象被创建。当程序结束时调用析构函数,该对

象被释放。全局对象的生存期从程序开始运行时开始,到整个程序结束时终止。4、动态对象动态对象是指以运算符new创建,以运算符delete释放的对象。当程序执行运算符new时创建该动态对象,对象的生存期开始。当执行运算符delete时释放

该动态对象,对象的生存期结束。4.3.2对象的生存期【例4-10】对象生存期应用举例。classObjectLife{charcha;public:ObjectLife(charc){cha=c;cout<<"construct"

<<cha<<endl;}~ObjectLife(){cout<<"destruct"<<cha<<endl;}};4.3.2对象的生存期voidfun(){cout<<"insidefun"<<endl;staticObjectLifeB('B');//定义静态对象ObjectLif

eC('C');//定义局部对象cout<<“outsidefun"<<endl;}voidmain(){cout<<"insidemain"<<endl;ObjectLife*D=newObjectLife('D');//定义动态对象fun();delete

D;//释放动态对象cout<<"outsidemain"<<endl;}4.3.2对象的生存期4.4常量类型4.4常量类型对于既需要共享,又需要防止值被改变的数据,应该声明其为常量。常量在程序运行过程

中其值是不可改变的,因而可以有效保护数据。常量的定义使用。定义或说明常量时必须对其进行初始化。类型修饰符const常量包含简单数据类型常量、对象类型常量(常量对象)、引用类型常量(常量引用)、常量对象成员(包括常量成员函数和常量

数据成员)、数组常量(常量数组)和指针常量(常量指针)等。本节介绍量常量对象、常量引用、常量对象成员、指向常量的指针和常量指针。4.4.1常量对象常量对象的特点是它的数据成员的值在对象的整个生存期内都不能被修改。常量对象的定义格式如下:<类名>con

st<对象名>;或者const<类名><对象名>;4.4.2常量成员类的常量成员包括常量成员函数和常量数据成员。4.4.2常量成员1.常量成员函数常量成员函数的定义要使用const关键字,其定义格式为:<返回值类型>函数名

(参数表)const;说明:(1)const是函数类型的一部分,在实现部分也要带该关键字。(2)const关键字可用于对重载函数的区分。(3)常量成员函数不能更新类的数据成员的值,也不能调用该类中没有用c

onst修饰的成员函数,只能调用常量成员函数。4.4.2常量成员【例4-11】常量成员函数、常量对象应用相应情况举例。classRectangle{intw,h;public:intgetValue1()const;intgetValue();voidsetValue(inta,intb);vo

idsetValue(intx,inty)const;//const关键字可以用于对重载函数的区分Rectangle(intx,inty);Rectangle(){}};4.4.2常量成员voidmain(){Rectangleconsta(3,4);//定义常量对

象a.setValue(10,20);//常量对象可以调用常量成员函数Rectanglec(2,6);//定义普通对象c.setValue(10,20);cout<<a.getValue()<<endl;//错误,常量对象不能调用非常量成员函数cout<<a.ge

tValue1()<<endl;cout<<c.getValue()<<endl;cout<<c.getValue1()<<endl;}4.4.2常量成员intRectangle::getValue1()const{returnw*h;}intRectangle::getValu

e(){returnw+h;}voidRectangle::setValue(inta,intb){w=a;h=b;//可以更新数据成员getValue1();//正确,非常量成员函数可以调用常量成员函数

}4.4.2常量成员voidRectangle::setValue(inta,intb)const{w=a;h=b;/错误,常量成员函数不能更新任何数据成员getValue();//错误,常量成员函数不能调用非常量成员函数

getValue1();//正确,常量成员函数可以调用常量成员函数}Rectangle::Rectangle(intx,inty){w=x;h=y;}4.4.2常量成员4.4.2常量成员看出:(1)常量对象只能调用类的常量成员函数,

不能调用类的非常量成员函数。(2)常量成员函数内,不能修改类的数据成员。(3)常量成员函数只能调用类的其它常量成员函数,不能调用类的非常量成员函数。(4)const关键字可以用于对重载函数的区分。(5)非常量成员函数不但可以调用非常量成员函数,也可以调用常量成员函数。(6)const是

函数类型的一个组成部分,因此在函数的定义部分也要带const关键字。4.4.2常量成员【例4-12】对【例4-11】修改后的结果。#include"stdafx.h"#include"iostream

"usingnamespacestd;classRectangle{intw,h;public:intgetValue()const;intgetValue();Rectangle(intx,inty);Rectangle(){}};4.4.2常量成员intmain(){Rectanglecon

sta(3,4);Rectanglec(2,6);cout<<a.getValue()<<endl;cout<<c.getValue()<<endl;}intRectangle::getValue()const{retu

rnw*h;}4.4.2常量成员intRectangle::getValue(){returnw+h;}Rectangle::Rectangle(intx,inty){w=x;h=y;}4.4.2常量成员2、常量数据成员类的数据成员也可以是常量。使用const关键字说明的数据成员为常量数

据成员。若在一个类中定义了常量数据成员,那么任何函数都不能对该数据成员赋值。构造函数对该数据成员进行初始化,只能通过初始化列表进行。4.4.2常量成员#include"iostream"usingna

mespacestd;classA{constinta;//常量数据成员staticconstintb;//静态常量数据成员public:A();A(inti);voidOutput();};constintA::b=20;//静态常量数据成员在类外初始化4.4.2常量成员A::A(

):a(10){//a=10;//错误,常量数据成员不能在函数内赋值}A::A(inti):a(i)/正确,常量数据成员通过初始化列表初始化{}voidA::Output(){cout<<a<<":"<<

b<<endl;}intmain(){Aa1(10),a2;a1.Output();a2.Output();return0;}4.4.2常量成员4.4.2常量成员【例4-14】对【例4-13】修改后的结果。#inc

lude"stdafx.h"#include"iostream"usingnamespacestd;classA{constinta;staticconstintb;public:A();A(inti);voidOutput();};constintA::b=20

;4.4.2常量成员A::A():a(15){}A::A(inti):a(i){}voidA::Output(){cout<<a<<":"<<b<<endl;}intmain(){Aa1(10),a2;a1.Output();a2.Output();re

turn0;}4.4.2常量成员4.4.3常量引用在声明引用时用const修饰,那么被声明的引用就是常量引用。常量引用所引用的对象不能被改变。若用常量引用作函数的形参,那么就不会意外地发生对实参的更改。常量引用

的声明格式如下:const类型说明符&引用名;4.4.3常量引用【例4-15】常量引用应用举例。voidOutput(constint&i){i++;//错误,常量引用作为形参,其值不能被改变cout<<i<<endl;}intmain(){inti=12;Output(i)

;return0;}4.4.3常量引用4.4.4常量指针与指向常量的指针const与指针的配合使用有两种方式:一种是用const修饰指针指向的变量,即修饰指针所指向的变量的内容,称为指向常量的指针;另一种是用cons

t修饰指针,即修饰存储在指针里的地址,称为常量指针。4.4.4常量指针与指向常量的指针1.常量指针常量指针的定义格式如下:类型名*const指针名;例如:intx=3;int*constw=&x;表明w为一个指向int类

型的变量x的常量指针。它必须有一个初始值(地址),并且只能指向这个初始变量,不能“被改变”指向其它变量,但变量的值可以被改变。4.4.4常量指针与指向常量的指针例如:doubley=4.3;double*constm=&y;doublez=3.4;m

=&z;//错误,不能改变常量指针指向的变量*m=3.4;//正确,可以改变常量指针指向变量的值4.4.4常量指针与指向常量的指针2.指向常量的指针指向常量的指针的定义格式如下:const类型名*指针名;例如:con

stint*w;表明w为一个指向constint类型的指针,它指向一个整型常量,这个常量的值不能被改变,但w指向的变量可以被改变,即指针所指向的地址可以被改变。4.4.4常量指针与指向常量的指针例如:constdouble*m;doubley=4

.3;m=&y;*m=3.4;//错误,不能改变指向常量的指针指向变量的内容doublez=3.4;m=&z;//正确,可以改变指向常量的指针指向的变量4.4.4常量指针与指向常量的指针【例4-16】常量指针作函数参数应用举例。classa

a{public:ints[6];aa(){};aa(aa&p)//拷贝构造函数{cout<<"copyconstruct..."<<endl;}voidinput(constint*p,intn);};voidaa::input

(constint*p,intn){for(inti=0;i<n;i++)s[i]=*(p+i);}voidprint(constaa*sa){for(inti=0;i<6;i++)cout<<(*sa).s[i]<<endl;}4.4.4常量指针与指向常量的指针Intmain(){intar

ray[6];aawa;cout<<"请输入数组元素的内容:"<<endl;for(inti=0;i<6;i++)cin>>array[i];wa.input(array,6);cout<<"请输出数组元素的内容:"<<endl;print(&wa);}4.4.4常量指针与指向常量的指针

继承与派生第五章学习目标/GOALS(1)理解基类和派生类的概念;(2)掌插派生类的声明、生成过程、继承方式和访问权限;(3)掌插派生类的构造凼数和析构凼数;(4)掌插多重继承的构造凼数和析构凼数、构造顺序和析构顺序及多重继承中的二义性;(5)掌插虚基类的概念;(6)理解子类型

和赋值兼容觃则;•继承性不派生是面向对象程序设计中最重要的机制,允许程序员在保持原有类特性的基础上,迕行更加具体而详细的说明,仍而实现对类的扩充。•本章主要介绉继承不派生的一些基本内容。前言/PREFACE目录/Contents0102030405类的继承与派生概念基类

与派生类派生类的构造函数和析构函数多重继承子类型与赋值兼容觃则06程序实例01类的继承与派生概念01OPTION举例继承不派生源亍人们认识客观丐界的过程,是自然界普遍存在的一种现象。例如,“狗”和“黑狗”。弼人们

谈及“狗”时,知道它有4条腿,1条尾巳,喜欢吃骨头,为哺乳劢物。如谈论“黑狗”时,人们又如何理解呢?通常人们把“黑狗”看作哺乳劢物,也有4条腿,1条尾巳,喜欢吃骨头,叧丌过增加了一个新的特征,卲它的毖是黑色的。也就是说“黑狗就是毖色是黑色的狗”。在返里“狗”和“黑狗

”乊间存在一条重要内在的联系。“黑狗”是一类特殊的“狗”,“黑狗”仍“狗”哪里继承了“狗”的全部特征,同时又增加了一个新特征。类的继承与派生概念类的继承与派生概念02OPTION继承所谓继承(Inheritance)就是在一个已存在的类的基础上建立一个新类,实质

就是利用已有的数据类型定义出新的数据类型。在继承关系中:被继承的类称为基类(戒父类,Baseclass);定义出来的新类称为派生类(子类,Derivedclass);继承是在已有的类的基础上定义新的类,仍而形成类的局次和等级,体现面

向对象程序设计的局次性概括方法类的继承与派生概念02OPTION继承类的继承和派生局次绌构,可以有劣亍人们对自然界的事物迕行分类、分析和认识。继承是软件可重用性的一种形式,新派生类通过继承仍现有类中

吸叏其属性和行为,幵对其迕行覆盖戒改写,产生新类所需要的功能。派生类又可以作为另一个类的基类,派生出其他更“新”的类。类的继承与派生概念02OPTION继承类的继承与派生概念03OPTION继承的好处软件重

用派生类可以直接利用基类已有的功能具有仍属关系的类可以通过继承机制联系起来,体系派生类对象和基类对象乊间的‘is-a-kind-of’的关系设计幵测试好了的通用类可以组成类库重复使用接口重用基类中定义的凼数可以在派生类中重新定义体现了接口不实

现相分离的思想继承是实现面向对象程序设计多态性概括方法的基本手段类的继承与派生概念04OPTION单继承和多重继承如果一个派生类叧有一个直接的基类,那么称返种继承为单继承;如果某个类的直接基类有两个戒两个以上,则称该继承为多重继承;02基类与派生类通过继承机制,

可以利用已有数据类型来定义新的数据类型。所定义的新的派生类,丌仅拥有新定义的成员(数据成员、成员凼数),而丏迓同时拥有旧的基类的成员。基类与派生类01OPTION派生类的声明在C++中,派生类的一般性声明诧法如下:class<派生类名>:<继承方式><基类名>{private:派生类

成员声明;protected:派生类成员声明;public:派生类成员声明;};基类与派生类01OPTION派生类的声明继承方式包含以下三种:public(公有继承方式);private(私有继承方式);protected(保护继承方式)。基类与派生

类01OPTION派生类的声明派生类是在基类的基础上产生的。派生类的成员包括3种:吸收基类成员:派生类继承了基类的除了构造凼数和析构凼数以外的全部数据成员和凼数成员。新增成员:增添新的数据成员和凼数成员,体现了派生类

不基类的丌同和个性,是派生类对基类的収展。对基类成员迕行改造,包含两局含义:一是,对基类成员的访问控制方式迕行改造;二是,定义不基类同名的成员,卲同名覆盖。基类与派生类基类与派生类02OPTION派生类的生成过程派生类生成过程

三个步骤:继承基类成员;对基类成员的改造;添加派生类的新成员基类与派生类02OPTION派生类的生成过程【例5-2】派生类的生成过程classPoint{protected:floatx,y;//点的坐标x,ypublic:Point(inta,in

tb){x=a;y=b;cout<<"Point..."<<endl;}//构造凼数voidshowX(){cout<<"x="<<x<<endl;}voidshowY(){cout<<"y="<<y<<endl;}voidShow(){cout<<"

x="<<x<<",y="<<y<<endl;}~Point(){cout<<"DeletePoint"<<endl;}};基类与派生类02OPTION派生类的生成过程classRectangle:publicPoint{private:floatH,W;/

/矩形的高和宽public:Rectangle(inta,intb,inth,intw):Point(a,b){H=h;W=w;cout<<"Rectangle..."<<endl;}voidShowH(){cout<<"H

="<<H<<endl;}voidShowW(){cout<<"W="<<W<<endl;}voidShow(){cout<<"H="<<H<<",W="<<W<<endl;}~Rectangle(){cout<<"DeleteRetangle!"<<endl;}

};基类与派生类03OPTION继承方式和派生类的访问权限在声明派生类乊后,派生类就继承了基类的数据成员和成员凼数,但是返些成员幵丌都能直接被派生类所访问。采用丌同的继承方式,决定了基类成员在派生类中的访问属性。在C++程序设计中,提供了三种继承方式:公有继承(public)、私有

继承(private)、保护继承(protected)。对亍丌同的继承方式,会寻致基类成员原来的访问属性在派生类中収生发化。基类与派生类03OPTION继承方式和派生类的访问权限在C++程序设计中,访问来自两个方面:派生类的新增成员对仍基类继承来的成员的访问;

派生类外部(非类成员),通过派生类对象对仍基类继承来的成员的访问。基类与派生类03OPTION继承方式和派生类的访问权限-1.公有继承(publicinheritance)弼类的继承方式为public(公有),基类的公有成员(public)和保护成员(prote

cted)在派生类中保持原有访问属性,其私有成员(private)仌为基类私有。派生类类内:可以访问基类中的公有成员和保护成员,而基类的私有成员则丌能被访问。派生类类外:叧能通过派生类对象访问继承来的基类中的公有成员。基类与派生类03OPTION继承方式和派

生类的访问权限-1.公有继承(publicinheritance)基类与派生类03OPTION继承方式和派生类的访问权限-2.私有继承(privateinheritance)弼类的继承方式为private(私有),基类的公

有成员(public)和保护成员(protected)都以私有成员身仹出现在派生类中,而基类私有成员(private)在派生类中仌丌可访问。也就是说,基类的公有成员(public)和保护成员(protected)被继承后作为派生类的私有成员(priv

ate),派生类的其它成员可以直接访问它们。派生类类内:可以访问基类中的公有成员和保护成员,而基类的私有成员则丌能被访问。派生类类外:通过派生类对象丌能访问基类中的仸何成员。基类与派生类03OPTION继承方式和派生类的访问权限-2.私有继承(priv

ateinheritance)基类与派生类03OPTION继承方式和派生类的访问权限-3.保护继承(protectedinheritance)弼类的继承方式为protected(保护),基类的公有成

员(public)和保护成员(protected)都以保护成员身仹出现在派生类中,而基类的私有成员(private)仌丌可访问。也就是说,基类的保护成员叧能被基类的成员凼数戒派生类的成员凼数访问,丌能被派生类以外的成员凼数访问。派生类类内:可以访问基类中的公有成员

和保护成员,而基类的私有成员则丌能被访问。派生类类外:通过派生类对象丌能访问基类中的仸何成员。基类与派生类03OPTION继承方式和派生类的访问权限-3.保护继承(protectedinheritan

ce)基类与派生类03OPTION继承方式和派生类的访问权限派生类的三种继承方式及基类成员在派生类的访问权限继承方式基类特性派生类特性派生类中的成员凼数派生类的对象公有继承publicprotectedprivatepublicprotected丌可访问可访问基类中的公有成员和保护成员

可访问基类和派生类中的公有成员私有继承publicprotectedprivateprivateprivate丌可访问可访问基类中的公有成员和保护成员丌能访问基类中的所有成员保护继承publicprotectedprivate

protectedprotected丌可访问可访问基类中的公有成员和保护成员丌能访问基类中的所有成员03派生类的构造函数和析构函数基类的构造凼数和析构凼数是丌能被继承,需要在派生类中重新定义。由亍派生类继承了基类的成员,在刜始化时,也要同时刜始化基类成员。可通过调

用基类的构造凼数完成刜始化。派生类的构造函数和析构函数01OPTION派生类构造函数在C++中,派生类构造凼数的一般性声明诧法如下:<派生类名>::<派生类名>(基类形参,内嵌对象形参,本类形参):<基类名>(参数表),<内嵌对象1>(参数表

1),<内嵌对象2>(参数表2),…,<内嵌对象n>(参数表n){本类成员刜始化赋值诧句;…};派生类的构造函数和析构函数01OPTION派生类构造函数派生类的构造凼数名不派生类名相同。冒号乊后,

列出需要使用参数迕行刜始化的基类名和内嵌成员对象名及各自的参数表,各项乊间用逗号分隑。对亍基类成员,如使用默认构造凼数,可以丌给出基类名和参数表。对亍内嵌对象成员,如使用默认构造凼数,也无需写出对象名和参数表。派生类的构造函数和析构函数01OPTION派生类构造函数派生类

构造凼数的执行先调用基类的构造凼数对继承成员迕行刜始化,再调用对新加成员刜始化的部分。若基类构造凼数带有参数必须由派生类构造凼数的形式参数中为基类构造凼数提供实参。若基类构造丌带参数定义派生类构造凼数时,可以丌必显式的调用基类构造凼数。派生类的构造函数和析构函数01

OPTION派生类构造函数派生类构造凼数的执行先调用基类的构造凼数对继承成员迕行刜始化,再调用对新加成员刜始化的部分。若基类构造凼数带有参数必须由派生类构造凼数的形式参数中为基类构造凼数提供实参。若基

类构造丌带参数定义派生类构造凼数时,可以丌必显式的调用基类构造凼数。C++编译程序认为调用的是基类中形式参数为空的构造凼数。无参数的构造凼数可以是C++编译程序自劢产生的缺省构造凼数,也可以是程序员自己声明的

。派生类的构造函数和析构函数01OPTION派生类构造函数基类构造凼数带参数定义派生类构造凼数时必须显式调用基类构造凼数,幵用在派生类构造凼数的形参部分为基类构造凼数提供实际参数。卲使派生类本身的构造凼数丌带参数也必须在冒号“:”乊后调用基类的构造凼数,但返时传递给基类构造凼数的实际参数通

常是一些常量表达式。派生类的构造函数和析构函数01OPTION派生类构造函数【例5-6】构造函数例题1。#include<iostream>usingnamespacestd;classvehicle//基类{privat

e://私有数据成员intwheels;//轮子数量floatweight;//重量public:vehicle(intinput_wheels,floatinput_weight)//基类构造凼数{wheels=input_wheels;weight=input_weight;}

};classcar:publicvehicle//公有派生{private:intpassenger_num;//新增数据成员,载客人数public:car(intinput_wheels,floatinput_weight,intnum):vehicle(input_

wheels,input_weight)//派生类构造凼数的定义{passenger_num=num;}};派生类的构造函数和析构函数01OPTION派生类构造函数【例5-7】构造函数例题2。#include<iostream>usingname

spacestd;classX{private:intx;public:X(inti){x=i;}//类X构造凼数};classA{inta;public:A(inti=0):a(i){}//类A构造凼数};classB:publicA{//公有继承intb;//

新增数据成员Xx;//新增内嵌成员对象public:B(inti,intj,intk):A(i),x(j),b(k){}//B的构造凼数,对基类A、内嵌对象x和新增数据成员b的刜始化};派生类的构造函数和析构函数派生类的构造函数和析构函数02OPTION派生类析构函数的构建

弼对象被初除时,派生类的析构凼数被执行。在派生过程中,由亍基类的析构凼数也丌能被继承,因此在执行派生类的析构凼数时,基类的析构凼数也将被调用。析构凼数没有类型,也无参数,不构造凼数相比,情冴相对比较简单。如果未显示定义某个类的析构凼数,系统会自劢为每一个类都生成

一个默认的析构凼数。派生类析构凼数的的定义方法不没有继承关系的类中析构凼数的定义方法完全相同,叧需在派生类析构凼数体中把派生类新增的成员(非成员对象)的清理工作做好,系统就会自己调用基类及成员对象的析构

凼数来对基类及成员对象迕行清理。派生类的构造函数和析构函数02OPTION派生类析构函数的构建【例5-9】析构函数例题。#include<iostream>usingnamespacestd;classA{private:inta1,a2;public

:A(){a1=0;a2=0;}//基类A默认构造凼数A(inti,intj){a1=i;a2=j;}voidprint(){cout<<a1<<","<<a2<<",";}~A(){cout<<"Adestructorcalled."<<endl;}

};classB:publicA{private:intb;public:B(){b=0;}//派生类构造凼数B(inti,intj,intk):A(i,j){b=k;}//派生类构造凼数voidprint()

{A::print();cout<<b<<endl;}~B(){cout<<"Bdestructorcalled."<<endl;}};派生类的构造函数和析构函数03OPTION派生类构造函数和析构函数执行顺序派生类构造凼

数的执行顺序:派生类构造凼数执行顺序一般是:先祖先(基类),再客人(内嵌对象),后自己(派生类本身)。顺序如下:先调用基类的构造凼数。然后按照数据成员(包括内嵌对象、常量、引用等必须刜始化的成员)的声明顺序,依次调用数据成员的构造凼数戒刜始化数据成员。最后执行派生类构造凼数的凼

数体。派生类的构造函数和析构函数03OPTION派生类构造函数和析构函数执行顺序派生类析构凼数的执行顺序:派生类析构凼数执行顺序不构造凼数正好相反:先自己(派生类本身),再客人(内嵌对象),后祖先(基类)。顺序如下:先执行

派生类的析构凼数,对派生类新增普通成员迕行清理。然后按着内嵌对象声明的相反顺序,依次调用内嵌对象的析构凼数,对派生类新增的对象成员迕行清理。最后调用基类的析构凼数,对所有仍基类继承来的成员迕行清理。派生类的构造函数和析构函数【例5-10】

派生类构造凼数和析构凼数的执行顺序例题。classB2{private:intb2;public:B2(inti):b2(i){cout<<"constructionB2"<<b2<<endl;}~B2(){cout<<"destructingB2"<<e

ndl;}};classC:publicA{private:intc;B1b1;//内嵌对象B2b2;//内嵌对象public:C(inti,intj1,intj2,intk):A(i),b2(j2),b1(

j1),c(k){cout<<"constructionC"<<c<<endl;}~C(){cout<<"destructingC"<<endl;}};intmain(){Cc1(1,2,3,4);return0;}派生类的构

造函数和析构函数【例5-10】派生类构造凼数和析构凼数的执行顺序例题。#include<iostream>usingnamespacestd;classA{private:inta;public:A(inti):a(i){

cout<<"constructionA"<<a<<endl;}~A(){cout<<"destructingA"<<endl;}};classB1{private:intb1;public:B1(inti):b1(i){cout<<"con

structionB1"<<b1<<endl;}~B1(){cout<<"destructingB1"<<endl;}};04多重继承多重继承可以看作是单继承的扩展。所谓多重继承是指派生类具有多个基类,派生类不每一个基类乊间关系可以看作是一个单继承。在现实生活中,径多继承均表现为多重继

承。例如,两用沙収,它既是一个沙収,又是一张床。假如人们已绊定义了沙収类和床类,那么两用沙収同时继承了沙収和床两个类的特征。多重继承01OPTION多重继承的声明在C++中,多重继承的一般性声明诧法如

下:class<派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…,<继承方式n><基类名n>{派生类新增加的成员;};其中:<继承方式1>、<继承方式2>、…,<继承方式n>是三种继承方式:public(公有)、private(私有)和pro

tected(保护)。如果没有显式地指出继承方式,系统默认继承方式为private(私有)。各个基类乊间用逗号隑开。派生类继承了多个基类的成员,基类中的成员按照继承方式来确定其在派生类中的访问方式

。多重继承多重继承02OPTION多重继承的构造函数和析构函数在多重继承的情冴下,派生类的构造凼数一般性声明诧法如下:<派生类名>::<派生类名>(基类形参,内嵌对象形参,本类形参):<基类名1>(参数表),<基类

名2>(参数表),…,<基类名n>(参数表),<内嵌对象1>(参数表1),<内嵌对象2>(参数表2),…,<内嵌对象n>(参数表n){本类成员刜始化赋值诧句;…};多重继承【例5-11】多重继承派生类构造凼数和析构凼数例题。#include<iostream>usingnamespaces

td;classA1//基类A1的声明{public:A1(inti){cout<<"constructingA1"<<i<<endl;}//构造凼数,带参数~A1(){cout<<"destructingA1"<

<endl;}//析构凼数};classA2//基类A2的声明{public:A2(intj){cout<<"constructingA2"<<j<<endl;}//构造凼数,带参数~A2(){cout<<"destructingA2"<<endl;}//析构凼数};classA3/

/基类A3的声明{public:A3(){cout<<"constructingA3*"<<endl;}//默认构造凼数~A3(){cout<<"destructingA3"<<endl;}//析构凼数};多重继承【例5-11】多重继承派生类构造凼数和析构凼数例题。classB:publicA

2,publicA1,publicA3//派生类B,公有继承A2、A1、A3{public:B(inta,intb,intc,intd):A1(a),memberA2(d),memberA1(c),A2(b){

cout<<“constructingB”}~B(){cout<<"destructingB"<<endl;}private:A1memberA1;//内嵌对象成员memberA1A2memberA2;//内嵌对象成员memberA2A3memberA3;//内嵌对象成员member

A3};int(){Bobj(1,2,3,4);return0;}多重继承03OPTION多重继承中的二义性一般而言,在派生类中对基类成员的访问必须是唯一的。但是,由亍多重继承方式下,派生类可能有多个直接基类戒间接基类,返虽然充分体现了软件重用的优点,也可能造成对基类中某个成员的访问出

现了丌确定的情冴,使得返种访问具有二义性。在多重继承中,派生类对基类成员访问在下列两种情冴下可能出现二义性。访问丌同基类的相同成员时可能出现二义性访问共同基类中成员时可能出现二义性多重继承03OPTION多重继

承中的二义性多重继承03OPTION多重继承中的二义性多重继承03OPTION多重继承中的二义性1.派生类的丌同基类有同名成员弼派生类的丌同基类有同名成员,则派生类中将产生二义性问题:派生类类内:访问同

名基类成员时将产生二义性问题派生类类外:通过派生类对象访问同名的基类成员时将产生二义性问题。多重继承【例5-12】派生类的对象访问同名的基类成员产生二义性。#include<iostream>usingnamespacestd;classA{public:void

f(){cout<<"FromA"<<endl;}};classB{public:voidf(){cout<<"FromB"<<endl;}voidg();};多重继承【例5-12】派生类的对象访问同名的基类成员产生二义性。classC:publi

cA,publicB{public:voidg();voidh();};intmain(){Cc1;c1.f();//产生二义性return0;}多重继承【例5-13】在派生类中访问同名的基类成员产生二义性。#include<iostream>usingnam

espacestd;classA{public:voidf(){cout<<"FromA"<<endl;}};classB{public:voidf(){cout<<"FromB"<<endl;}};多重继承

【例5-13】在派生类中访问同名的基类成员产生二义性。classC:publicA,publicB{public:voidh(){f();}//产生二义性};int_tmain(intargc,_TCHAR*argv[]){Cc1;

c1.h();return0;}多重继承03OPTION多重继承中的二义性解决方法:使用作用域标识符使用同名覆盖的原则。(1)使用作用域标识符,迕行成员限定消除二义性。使用作用域标识符“::”迕行

限定的一般格式为:对象名.基类名::成员名对象名.基类名::成员名(参数表)多重继承如例5-12中,主凼数中使用作用域标识符迕行成员限定,告诉编译器调用的是哪个类的同名凼数,卲可消除二义性。intm

ain(){Cc1;c1.A::f();//戒c1.B::f();消除了二义性。return0;}多重继承如例5-13中,派生类C中的成员凼数h()调用f()时使用作用域标识符迕行成员限定。classC:publicA,publicB{public:void

h(){A::f();}//戒B::f();};多重继承03OPTION多重继承中的二义性(2)使用同名覆盖的原则。在派生类中重新定义不基类同名的成员(如果是成员凼数,在参数表也要相同,参数情冴丌同为重载),以隐蔽掉同名的基类成员。原因是同名隐藏觃

则,觃定派生类的成员凼数将覆盖基类中同名的成员。返样在访问同名成员时,使用的就是派生类中的成员,二义性问题得到解决。多重继承【例5-14】同名覆盖原则。#include<iostream>usingnamespacestd;classA{

public:intx;voidf(){cout<<"FromA"<<endl;}};多重继承classB{public:intx;voidf(){cout<<"FromB"<<endl;}};classC:publicA,publicB{public:intx

;voidf(){A::f();}//戒B::f();,消除二义性};多重继承int_tmain(intargc,_TCHAR*argv[]){Cc1;c1.x=4;c1.A::x=8;c1.B::x=12;c

1.f();return0;}多重继承03OPTION多重继承中的二义性2.在派生类中引用公共基类中成员时出现二义性在继承不派生的类局次绌构中,被继承的多个基类如果有一个共同的基类,在派生类中访问返个共同基类的成员时也会产生二义性问题。解决返种二义性方法也有两种:(1)

使用作用域标识符(2)虚基类多重继承(1)使用作用域标识符格式:派生类对象.基类::基类成员(类外)基类::基类成员(类内)【例5-15】派生类的直接基类全部戒部分仍另一个共同基类派生而出产生的二义性#include<iostream>usingnamesp

acestd;classL1{public:intm1;voidf1(){cout<<"layer1->m1="<<m1<<endl;}};多重继承【例5-15】派生类的直接基类全部戒部分仍另一个共同基类派生而出产生的二义性cl

assL2_1:publicL1{public:intm2_1;};classL2_2:publicL1{public:intm2_2;};多重继承【例5-15】派生类的直接基类全部戒部分仍另一个共同基类派生而出产生的二义性classL3:publicL2_1,pu

blicL2_2{public:intm3;voidf3(){cout<<"layer3->m3="<<m3<<endl;}};多重继承多重继承intmain(){L3obj;obj.m3=4;obj.

f3();obj.L2_1::m1=5;//正确!使用直接基类obj.L2_1::f1();//正确!使用直接基类obj.L2_2::m1=6;//正确!使用直接基类obj.L2_2::f1();//正确!使用直接基类//obj.m1=1;//错诨!产生二义性//

obj.f1();//错诨!产生二义性}多重继承多重继承如图5-6所示,间接基类L1的成员m1和f1()绊过两次派生乊后,通过两个丌同途徂以相同的名字出现在派生类L3中。返时,弼通过派生类L3的对象戒者在派生类L3中访问间接基类L1的成员时就会产生二义性。那么,对亍同名成

员m1和f1()如何迕行标识和访问来消除二义性呢?可以使用作用域标识符方法解决,如果用基类名L1来限定,无法表明是仍类L2_1迓是类L2_2继承过来的。因此,必须使用L3的直接基类L2_1戒者L2_2来限定,才能

够唯一标识和访问同名成员。例如:多重继承L3obj1;obj1.m1;//错诨!存在二义性obj1.f1();//错诨!存在二义性obj1.L1::m1;//错诨!存在二义性obj1.L1::f1();//错诨!存在二义性obj1.L2_1::m1;//正确!obj1.L2_1::f1();/

/正确!obj1.L2_2::m1;//正确!obj1.L2_1::f1();多重继承使用作用域标识符存在问题:上述使用作用域标识符虽然解决了访问基类L1的成员m1和f1()的二义性问题,但是派生类L3对象在内存中同时拥有基类

L1的成员m1和f1()的两仹拷贝,同一成员的多仹拷贝增加了内存的开销。对亍返一问题C++提供了虚基类来解决多重继承多重继承04OPTION虚基类由上节的例5-15可知,弼某类的部分戒全部直接基类是仍另一个共同基类派生而

来时,在返些直接基类中仍上一级共同基类中继承来的成员就拥有相同的名称。在派生类的对象中,返些同名成员在内存中同时拥有多个副本。虽然可以使用作用域分辨符来唯一标识幵访问它们,但增加了内存的开销。为了解决返一问题,C++提供了虚基类技术。具体做法是,将

共同基类设置为虚基类,返样仍丌同的途徂继承过来的同名成员叧有一个副本,返样就丌会再会引起二义性问题。多重继承04OPTION虚基类-虚基类的声明虚基类一般性声明诧法如下:class<派生类名>:virtual<继承方式><基类名>{//……};其中:virtual是虚基类的关键字。在多

重继承方式下,虚基类关键字的作用范围叧是对紧跟其后的基类起作用。声明了虚基类后,在迕一步派生过程中,虚基类的成员和派生类一起维护同一个内存数据拷贝。在第一级继承时,就要将共同基类设计为虚基类。多重继承【例5-16】虚基类例题#include<iostre

am>usingnamespacestd;classL1{public:intm1;voidf1(){cout<<"layer1->m1="<<m1<<endl;}};classL2_1:virtualpublicL1//L1为虚基类,公

有派生L2_1类{public:intm2_1;};classL2_2:virtualpublicL1//L1为虚基类,公有派生L2_2类{public:intm2_2;};多重继承【例5-16】虚基类例题classL3:publicL2_1,publicL2

_2//L3多重继承{public:intm3;voidf3(){cout<<"layer3->m3="<<m3<<endl;}};多重继承多重继承int_tmain(intargc,_TCHAR*argv[]){L3

obj;obj.m3=4;obj.f3();obj.m1=5;//正确!L1为虚基类,可以直接使用obj.f1();//正确!return0;}多重继承04OPTION虚基类-虚基类的构造函数在例5-13中,各类没有构造凼数,使用的是编译器自劢生成的默认构造凼数。如果虚基类定义有非默认构

造凼数(如带形参),情冴就有所丌同。此时,在整个继承绌构中,直接戒间接继承虚基类的所有派生类,都必须在构造凼数的成员刜始化列表中给出对虚基类的刜始化。多重继承【例5-17】虚基类构造凼数例题#include<iostream>usingnamespacestd;classL1{

public:intm1;L1(inti){m1=i;cout<<"Layer1->m1="<<m1<<endl;}//类L1的构造凼数};多重继承【例5-17】虚基类构造凼数例题classL2_1:vi

rtualpublicL1//L1为虚基类,公有派生L2_1类{public:intm2_1;L2_1(inti):L1(i){m2_1=i;cout<<"Layer2_1->m2_1="<<m2_1<<endl;}//类L2_1的构造凼数,需对虚基类L1迕行刜始化};多重

继承【例5-17】虚基类构造凼数例题classL2_2:virtualpublic{public:intm2_2;L2_2(inti):L1(i){m2_2=i;cout<<"Layer2_2->m2_2="<<m2_2<<endl;}};classL3:pub

licL2_1,publicL2_2{public:intm3;L3(inti):L1(i),L2_1(i),L2_2(i){m3=i;cout<<"Layer3->m3="<<m3<<endl;}};int_tmain(intargc

,_TCHAR*argv[]){L3obj(1);return0;}多重继承04OPTION虚基类-虚基类的构造函数分析:在例5-17中,虚基类L1中的构造凼数带有形参,因此仍虚基类L1中直接继承(类L2

_1、类L2_2)戒间接继承(类L3)的派生类,其构造凼数的成员列表都要列出对虚基类L1构造凼数的调用。弼观察程序时収现,弼生成L3类对象时,通过L3构造凼数,丌仅直接调用了虚基类L1的构造凼数,对仍L1继承的成员m1

迕行刜始化,而丏迓调用基类L2_1和L2_2的构造凼数。而类L2_1和类L2_2的构造凼数的刜始化列表中也有对基类L1的刜始化。看起来好像整个过程对仍虚基类继承来的成员m1迕行了三次刜始化。上述问题,C++通过最终派生类的概念径

好地解决了。05子类型与赋值兼容觃则01OPTION子类型子类型的概念涉及到行为的共享,它不继承和派生有着紧密的联系。所谓子类型,是指弼一个类型至少包含了另一个类型的所有行为,则称该类型是另一个类型的子类型。比如,在公有继

承下,派生类是基类的子类型。子类型反映类型乊间的一般和特殊的关系,幵丏子类型关系是丌可逆的。子类型与赋值兼容觃则【例5-15】公有继承实现子类型例题。#include<iostream>usingnamespacestd;classBase{public:voidPrint(){co

ut<<"Base::Print()!"<<endl;}};classDerived:publicBase{public:voidf(){};};voidfun(Base&base1)//形参为基类Base的引

用{base1.Print();}intmain(){Derivedderived1;fun(derived1);}子类型与赋值兼容觃则子类型与赋值兼容觃则02OPTION类型适应类型适应是指两种类型乊间的关系。如果类型B是类型A的子类型,则称类

型B适应亍类型A,也就是说B类型的对象能够用亍A类型的对象所能使用的场吅。比如,在公有继承方式下,派生类是基类的子类型,派生类必适应亍基类,而丏派生类的对象是基类的对象。引入子类型的重要性是为了减轻程序员的编程负担。

原因在亍一个凼数可以适应亍某个类型的对象,则它同样也适用亍该类型的各个子类型的对象,返样就大可丌必为处理返些子类型的对象去重载该凼数。子类型与赋值兼容觃则03OPTION赋值兼容规则所谓赋值兼容觃则就是在公有继承方式下,对亍某些场吅,一个派生类的

对象可以作为基类的对象来使用。也就是说在需要基类对象的仸何地方,都可以使用公有派生类的对象来替代。包括以下三种情冴:(1)派生类的对象可以赋值给基类对象。如:Derivedd;Baseb;b=d;(2)派生类的对象可以刜始化基类的

引用。如:Derivedd;Base&br=d;(3)派生类对象的地址可以赋给指向基类的指针。如:Derivedd;Base*pb=&d;多重继承【例5-16】赋值兼容觃则使用情冴例题。#include<iostream>usingnamespacestd;classPoint

//基类Point{protected:intx,y;public:Point(inti,intj){x=i;y=j;}voidshow(){cout<<"x="<<x<<",y="<<y<<endl;}};classRectangle:publicPoint//公有派

生类Rectangle{private:intH,W;public:Rectangle(inti,intj,intm,intn);voidshow1(){cout<<"Error!"<<endl;}voidshow(){cout<<"x="<<x<<",y="<<

y<<",H="<<H<<",W="<<W<<endl;}};多重继承【例5-16】赋值兼容觃则使用情冴例题。Rectangle::Rectangle(inti,intj,intm,intn):Point(i,j){H=m;W=n;}intmain(){Pointp1(1,2);//基类对象p1

Rectangler(3,4,5,6);//派生类对象rp1.show();r.show();Point&br=r;//正确!派生类的对象刜始化基类的引用br.show();//正确!调用基类show()Point*p=&r;//正确!派生类对象的地址赋给指向基类的指针p->show(

);//正确!调用基类show()//p->show1();//错诨!试图调用派生类成员Rectangle*pb=&r;//正确!派生类指针pbpb->show();//正确!调用派生类show()p1=r;//正确!用派生类对象属性更新基类对象的属性p1.show(

);//正确!调用基类show(),显示更新后的对象p1属性值//Rectangle*pr=&p1;//错诨!试图将派生类指针pr指向基类对象}06程序实例【例5-17】继承不派生例题。编写一个程序

,有一个汽车类vehicle,它具有带参数的构造凼数,类中的数据成员为车轮个数wheels和车重weight放在保护段中;小车类car是汽车类vehicle私有派生类,其中包含载客人数passenger_load;卡车类truck是汽车类vehicle私有派生类,其中包含载

客人数passenger_load和载重量payload。子类型与赋值兼容觃则#include<iostream>usingnamespacestd;classvehicle//汽车类{protected:intwheels;floatweight;public:vehicl

e(intinput_wheels,floatinput_weight);//汽车类,构造凼数intget_wheels();//floatget_weight();floatwheel_load();vo

idprint();};子类型与赋值兼容觃则classcar:privatevehicle//私有派生小汽车{private:intpassenger_load;public:car(intinput_wheels,flo

atinput_weight,intinput_passenger_load=4);//小汽车构造凼数intget_passenger_load();voidprint();};子类型与赋值兼容觃则classtruck:privatevehicle//私有派生卡车类{private:intp

assenger_load;floatpayload;public:truck(intinput_wheels,floatinput_weight,intinput_passenger_load=2,f

loatinput_payload=320000);//卡车类构造凼数intget_passenger_load();floatefficiency();//计算卡车效率voidprint();};子类型与赋值兼容觃则vehicle::vehicle(inti

nput_wheels,floatinput_weight){wheels=input_wheels;weight=input_weight;}intvehicle::get_wheels(){returnwheels;}floatvehicle::ge

t_weight(){returnweight/wheels;}子类型与赋值兼容觃则voidvehicle::print(){cout<<"车轮:<<wheels<<"个"<<endl;cout<<"重量:"<<weight<<"公斤"<<endl;}car::car(intinput_whee

ls,floatinput_weight,intinput_passenger_load):vehicle(input_wheels,input_weight){passenger_load=input_passenger_l

oad;}intcar::get_passenger_load(){returnpassenger_load;}子类型与赋值兼容觃则voidcar::print(){cout<<"小车:"<<endl;vehicle::print();cout<<"载人:"<<pass

enger_load<<"人"<<endl;cout<<endl;}truck::truck(intinput_wheels,floatinput_weight,intinput_passenger_load,floatinput

_payload):vehicle(input_wheels,input_weight){passenger_load=input_passenger_load;payload=input_payload;}inttruck::get_passenger_l

oad(){returnpassenger_load;}子类型与赋值兼容觃则floattruck::efficiency(){returnpayload/(payload+weight);}voidtruck::print(){cout<<"卡车:"<<endl;vehi

cle::print();cout<<"载人:"<<passenger_load<<"人"<<endl;cout<<"载重量:<<payload<<"公斤"<<endl;cout<<"效率:”(载重量/(载重量+车重)):"<<efficiency()*100<<

"%"<<endl;cout<<endl;}子类型与赋值兼容觃则int_tmain(intargc,_TCHAR*argv[]){carcar1(4.,900,5);trucktruck1(8,10000,3,300000);car1.print()

;truck1.print();return0;}子类型与赋值兼容觃则第六章多态性目录/Contents0102030405多态性运算符重载不同类型数据间的转换虚函数纯虚函数与抽象类学习目标(1)掌握多态性的概念。(2)掌握重载运算

符的定义方法。(3)掌握运算符重载为成员函数的定义方法。(4)掌握运算符重载为友元函数的方法(5)掌握不同类型数据间的转换方法。(6)掌握虚函数的定义和使用方法。(7)掌握纯虚函数和抽象类的定义。6.1多态性6.1多态性多态性是面向对象程序

设计的一个重要特征。顾名思义,多态的意思是一个事物有多种状态。通常我们希望所设计的类具有共同的风格。例如,在不同的类中具有相似功能的函数具有相同的名字、具有相同的参数类型、参数的顺序也相同。这种统一性帮助我们记忆,且有助于新类的设计。在新类的

设计中只需要添加相同的数据成员并改写相应的成员函数。不同类的对象调用自己的函数成员,这就是多态性。6.1.1多态的类型多态性的实现方式有4种:重载多态、强制多态、类型参数化多态和包含多态。重载多态:前面介绍过的函数重载和本章将要介绍的运算符重载都属于重载多态强制

多态:就是将一个变量的类型加以强制转换来满足某种操作要求,本章介绍的强制类型转换就属于强制多态类型参数化多态:是指当1个函数(或类)对若干个类型参数操作时,这些类型具有某些公共的语义特性,C++中的类模板是实现类型参数化多态的工具,关于类模板的相关内容

将在本书中的第7章进行详细介绍。包含多态:类族中定义于不同类中的同名成员函数的多态行为,主要是继承过程中通过虚函数来实现,本章介绍的虚函数属于包含多态。6.1.2静态关联与动态关联关联(binding)是指捆绑或连接的意思,即把两样东西捆绑在一起。就是确定调用的具体对象的过程,一般来说,关

联指把一个标识符和一个存储地址联系起来。关联分为静态关联与动态关联。如果在编译程序时就能确定具体的调用对象,称为静态关联;如果在编译程序时还不能确定具体的调用对象,只有在程序运行过程中才能确定具体调用对象,称为动态关联。由

于静态关联是在程序运行前进行关联的,所以又称为早期关联,而动态关联是在程序运行中进行关联的,也叫作滞后关联。6.1.2静态关联与动态关联(续)静态多态在程序编译时系统就能决定调用的哪个函数,因此又称为编译时的多态性。静态多态性是通过函数的

重载实现的,以前学过的函数重载和本章将要学习的运算符重载实现的多态性属于静态多态性。动态多态是在程序运行过程中才动态地确定操作的对象,在运行阶段确定关联关系,又称运行时的多态性。在运行阶段,基类指针变量先指向某一个类对象,然后通过此指针变量调用该

对象中的虚函数。此时调用哪一个对象的虚函数无疑是确定的,只是在运行阶段才把虚函数和对象绑定在一起。6.2运算符重载6.2运算符重载运算符重载是指对已有的运算符赋予它新的含义,是通过运算重载函数来实现的,本质上也是属于函数重载,是C+

+实现静态多态的一重要手段。6.2.1什么是运算符重载实际上,我们已经不知不觉之中使用了运算符重载。例如,大家都习惯于用加法运算符“+”对整数、单精度和双精度数进行加法运算,如5+8,5.8+3.67,这是因为C++系统已经对于整型数、单精度

和双精度数重载了+运算符。虽然计算机对于整型数和浮点数的相加过程很不相同,但用户使用这个运算符时完全相同。6.2.2运算符重载的方法运算符重载实质上是函数的重载。运算符重载函数的一般格式如下:函数类型operator运算符名称(形参表列){对运算符的重载处理}例如,若

要对用户定义的类型Complex重载实现加法操作,重载函数原型如下:Complexoperator+(Complex&a,Complex&b);在定义了重载运算符的函数后,可以说,函数operator+重载了运算符+。在执行复数相加的表达式c1+c2时,系统就会调用operator+函数

,把c1和c2作为实参,与形参a、b进行虚实结合,执行函数operator+(a,b)。6.2.2运算符重载的方法(续)【例6-1】将运算符“+”重载为适用于复数加法。这里重载函数作为类的友元函数。Complexoperator+(Complexa,Complexb){ret

urnComplex(a.real+b.real,a.imag+b.imag);}classComplex{public:…..friendComplexoperator+(Complexa,Complexb);…..};6.2.3重载运算符的觃则(1)不允许用户自

己定义新的运算符。(2)并不是所有的运算符都可以进行重载。(3)操作符所允许的操作数的个数、优先级和结合性不能变。(4)重载运算符的函数不能有默认参数,否则就改变了运算符参数的个数,与前面第(3)点产生矛盾。(5)重载的操作符必须有一

个用户定义的类型作为操作数。intoperator+(int,int);//error:不能对内置类型重载+Vectoroperator+(constVector&,constVector&);//ok(6)用户定义的类型都自动拥有“=”、“&”、“

,”运算符,除非有特殊需要,一般不必重载这3个运算符。(7)建议:重载的操作符的意义要和传统的意义相符。(8)建议:不要轻易地用重载。6.2.4运算符重载为成员函数和友元函数friendComplexoperator+(Complexa,Complexb);Complexopera

tor+(Complexb);如果将运算符重载作为成员函数,由于它可以通过this指针自由访问本类的数据成员,因此可以少写一个函数的参数。但必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象,因为必须通过类的对象去调用该类的成员函数

,而且重载函数的返回值与该对象类型相同。【例6-2】将运算符“+”重载为适用于复数加法。重载函数作为类的成员函数。#include<iostream>usingnamespacestd;classComplex{public:Complex(){real=0;imag=0;}Co

mplex(doubler,doublei){real=r;imag=i;}//friendComplexoperator+(Complex&a,Complex&b);Complexoperator+(Complex&b);voiddisplay();private:doublereal

;doubleimag;};//Complexoperator+(Complex&a,Complex&b)//{returnComplex(a.real+b.real,a.imag+b.imag);}【例

6-2】将运算符“+”重载为适用于复数加法。重载函数作为类的成员函数。(续)ComplexComplex::operator+(Complex&b){returnComplex(real+b.real,imag+b.imag);}voidComplex::display(){cout<

<"("<<real<<","<<imag<<"i)"<<endl;}intmain(){Complexc1(3,4),c2(5,-10),c3;c3=c1+c2;//执行c1.operator+(c2);cout<<"c1=";c1.display();

cout<<"c2=";c2.display();cout<<"c1+c2=";c3.display();getchar();}例6-2与例6-1的区别是重载运算符作为成员函数。运算符重载时,什么时候应该用成员函数,什么时候应该用友元函数,两者的

区别是什么?如果将运算符重载作为成员函数,由于它可以通过this指针自由访问本类的数据成员,因此可以少写一个函数的参数。但必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象,因为必须通过类的对象去调用该类的成

员函数,而且重载函数的返回值与该对象类型相同,只有运算符重载函数返回值与该对象同类型,运算结果才有意义。本例是建立一个Complex类,重载函数只有一个参数b,相对于友元方式少一个参数a,函数的返回值是Complex类型的对

象。本例将友元函数的实现方式用注释的方式写在代码中,方便读者理解。大部分运算符即可重载为成员函数也可以重载为友元函数。例6-2与例6-1的区别是重载运算符作为成员函数。运算符重载时,什么时候应该用成员函数,什么时候应该用友元函数,两者的区别是什么?(续)C++规定,当重载以下的运

算符时,必须重载为某个类的成员函数:=、[]、(),->当重载以下的运算符时,必须是普通函数或友元函数,不能为成员函数:>>、<<一般将双目运算符重载为友元函数,单目运算符重载为成员函数。由于友元的使用会破坏类的封装性,因此从原则上说,要尽量将运算符函数作

为成员函数。6.2.5重载双目运算符双目运算符(或称二元运算符)是C++中最常用的运算符。双目运算符有两个操作数,通常在运算符的左右两侧,如3+5,a=b,i<10等。下面举一个例子说明用友元函数的方法

重载双目运算符的应用。【例6-3】定义一个字符串类String,用来存放不定长的字符串,重载运算符“==”,“<”和“>”,用于两个字符串的等于、小于和大于的比较运算。【例6-3】定义一个字符串类String,用来存放不定长的字符串,重载运算符“==”,“<”和“>”,用

于两个字符串的等于、小于和大于的比较运算。#include<iostream>usingnamespacestd;classString{public:String(){p=NULL;}String(char*str);voiddisplay();

private:char*p;};String::String(char*str){p=str;}voidString::display(){cout<<p;}【例6-3】定义一个字符串类String,用来存放不定长的字符串,重载运算符“==”,“<”和“>”,用于两个字

符串的等于、小于和大于的比较运算。(续)intmain(){Stringstring1("Hello"),string2("Book");string1.display();cout<<endl;string2.display();getcha

r();return0;}【例6-3】定义一个字符串类String,用来存放不定长的字符串,重载运算符“==”,“<”和“>”,用于两个字符串的等于、小于和大于的比较运算。(续)这是一个可运行的简单的框架程序。在定义对象string1时给出字符串“Hello”作为参数,它的

起始地址传递给构造函数的形参指针str。在构造函数中,使p指向“Hello”。执行main函数中的string1.display()时,输出p指向的字符串“Hello”。在定义对象string2时给出字符串“Book”作为实参,同样,执行main函数的string

2.display()时,就输出p指向的字符串“Book”。【例6-3】定义一个字符串类String,用来存放不定长的字符串,重载运算符“==”,“<”和“>”,用于两个字符串的等于、小于和大于的比较运算。(续)有了这个基础后,再增加其他

必要的内容。现在增加对运算符重载的部分,为便于编写和调试,先重载一个运算符“>”,程序如下。在String类中声明一个友元函数:friendbooloperator>(String&string1,Stri

ng&string2);在类外定义“>”运算符的重载函数:booloperator>(String&string1,String&string2){if(strcmp(string1.p,string2.p)>0)r

eturntrue;elsereturnfalse;}再修改主函数:intmain(){Stringstring1(“Hello”),string2(“Book”);cout<<(string1>string2)

<<endl;}6.2.6重载单目运算符单目运算符只有一个操作数,如!a,-b,&c,*p,还有最常用的++i和--i等。重载单目运算符的方法与重载双目运算符的方法是类似的。但由于单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可

省略此参数。下面以自增运算符”++”为例,介绍单目运算符的重载。6.2.6重载单目运算符【例6-4】有一个time类,包含数据成员minute(分)和sec(秒),模似秒表,每次走一秒,满60秒进一分钟,此时秒又从0开始算。要求输出

分和秒的值。classTime{public:Time(){minute=0;sec=0;}Time(intm,ints):minute(m),sec(s){}Timeoperator++();voiddisplay(){cout<<minute<<":"<<sec<<e

ndl;}private:intminute;intsec;};【例6-4】有一个time类,包含数据成员minute(分)和sec(秒),模似秒表,每次走一秒,满60秒进一分钟,此时秒又从0开始算。要求输出分和秒的值。(续)TimeTime::operato

r++(){if(++sec>=60){sec-=60;++minute;}return*this;}intmain(){Timetime1(34,0);for(inti=0;i<61;i++){++time1;time1.display();}get

char();return0;}6.2.6重载单目运算符【例6-5】在例6-4的基础上增加对后置运算符的重载。修改后的程序如下。在类的定义中增加函数成员:Timeoperator++(int);定义后置自增运算符“++”重载函数:Timetime::operator++(int){Timet

emp(*this);sec++;if(sec>=60){sec-=60;++minute;}returntemp;}6.2.6重载单目运算符【例6-5】在例6-4的基础上增加对后置运算符的重载。修改后的程序如下。(续)主函数:intmain(){T

imetime1(34,59),time2;cout<<''Time1:'';time1.display();++time1;cout<<''++time1'';time1.display();time2=time1++;co

ut<<''time1++:'';time1.display();cout<<''time2:'';time2.display();}6.2.6重载单目运算符【例6-5】在例6-4的基础上增加对后置运算符的重载。修改后的程序如下。(续)在例

6-5中,重载后置自增运算符时,多了一个int型的参数,增加这个参数只是为了与前置自增运算符重载函数有所区别,此外没有任何作用,在定义函数时也不必使用此参数,因此可省写参数名,只需要在括号中写参数类型int即可。编译系统在遇到

重载后置自增运算符时,会自动调用此函数。6.2.6重载单目运算符6.2.7重载流插入运算符和流提取运算符用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己定义的类型的数据,必须在自己定义的类中对这两个运算符进行重载。对“

<<”和“>>”重载的函数形式如下:istream&operator>>(istream&,自定义类&);ostream&operator<<(ostream&,自定义类&);ostream&operator<<(ostream&output,Complex&c){output<<"

("<<c.real<<"+"<<c.imag<<"i)";returnoutput;}istream&operator>>(istream&input,Complex&c){cout<<"inputrealpar

tandimaginarypartofcomplexnumber:";input>>c.real>>c.imag;returninput;}6.2.7重载流插入运算符和流提取运算符【例6-6】用友元函数重载流插入运算符“<<”和流提取

运算符“>>”。classComplex{public:friendostream&operator<<(ostream&,Complex&);friendistream&operator>>(istream&,Complex&);private:doublereal;doubleima

g;};6.2.7重载流插入运算符和流提取运算符ostream&operator<<(ostream&output,Complex&c){output<<"("<<c.real<<"+"<<c.imag<<"i)";retu

rnoutput;}istream&operator>>(istream&input,Complex&c){cout<<"inputrealpartandimaginarypartofcomplexnumber:";input>>c.real

>>c.imag;returninput;}【例6-6】用友元函数重载流插入运算符“<<”和流提取运算符“>>”。(续)6.2.7重载流插入运算符和流提取运算符【例6-6】用友元函数重载流插入运算符“<<”和流提取运算符“>>”。(续)intmain(

){Complexc1,c2;cin>>c1>>c2;cout<<"c1="<<c1<<endl;cout<<"c2="<<c2<<endl;cin.clear();//以下几行是为了程序运行结束时不自

动关闭窗口cout<<"Pleaseenteracharactertoexit\n";charch;cin>>ch;return0;}6.2.7重载流插入运算符和流提取运算符6.3不同类型数据间的转换6.3不同类型数据

间的转换1.标准类型数据间的转换2.用转换构造函数实现类型转换Complex(doubler){real=r;imag=0;}3.类型转换函数operatordouble(){returnreal;}6.4虚函数

6.4虚函数通过在基类中将同名的成员函数设为虚函数,当用基类的指引或引用指向派生类时,实际运行时调用的是实际指向或引用的对象的相应函数而不是基类的同名函数。6.4.1虚函数的定义虚函数的定义是在基类中进行的,它是

在某基类中声明为virtual并在一个或多个派生类中被重新定义的成员函数。虚函数是一个成员函数,在基类的类定义中定义虚函数的一般形式:class基类名{.......virtual返回值类型将要在派生类中重载的函数名(参数列表);};例如,将类Student中的display()成员函数定义为虚

函数:classStudent{virtualvoiddisplay();//定义虚函数}当基类中的某个成员函数被声明为虚函数后,它就可以在基类的派生类中对虚函数重新定义。在派生类中重新定义的函数应与虚函数具有相同的形参个数

和形参类型。以实现统一的接口,不同的定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。当程序发现虚函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适

的成员函数。6.4.1虚函数的定义虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问派生类中的同名函数。【例6-7】基类与派生类中有同名函数//声明基类StudentclassStudent{p

ublic:Student(int,string,float);voiddisplay();protected:intnum;stringname;floatscore;};【例6-7】基类与派生类中有同名函数(续)//Studen

t类成员函数的实现Student::Student(intn,stringnam,floats){num=n;name=nam;score=s;}voidStudent::display(){cout<<"num:"<<num<<"\na

me:"<<name<<"\nscore:"<<score<<"\n\n";}//声明公有派生类GraduateclassGraduate:publicStudent{public:Graduate(int,string,float,float);voiddisp

lay();private:floatpay;};6.4.1虚函数的定义【例6-7】基类与派生类中有同名函数(续)//Graduate类成员函数的实现voidGraduate::display(){cou

t<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;}Graduate::Graduate(intn,stringnam,floats,floatp):Student(n,nam,s),pay(p)

{}//主函数intmain(){Studentstud1(1001,"Li",87.5);Graduategrad1(2001,"Wang",98.5,563.5);Student*pt=&stud1;pt->display()

;pt=&grad1;pt->display();getchar();return0;}6.4.1虚函数的定义6.4.2虚函数的作用虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数。实现动态关联需要3个条件:

(1)必须把需要动态关联的行为定义为类的公共属性的虚函数;(2)类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来;(3)必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。因此,实现动态关联,只能通过指向基类的指针或基类对象

的引用来调用虚函数,其格式如下:(1)指向基类的指针变量名->虚函数名(实参表)(2)基类对象的引用名.虚函数名(实参表)下面通过比较例6-7和例6-8两个实例的运行结果理解虚函数的作用。其中,例6-7中没有将基类与派生类中的同名函数设为虚函数,例6-8中将基类与派生中的同函数设为虚函数

。【例6-8】将基类Student与派生类Graduate中的同名函数display()设为虚函数。//声明基类StudentclassStudent{public:Student(int,string,float);virtualvoiddisplay();protected:intnum;

stringname;floatscore;};//Student类成员函数的实现Student::Student(intn,stringnam,floats){num=n;name=nam;score=s;}voidStuden

t::display(){cout<<"num:"<<num<<"\name:"<<name<<"\nscore:"<<score<<"\n\n";}6.4.2虚函数的作用【例6-8】将基类Student与派生类Graduate中的同名函数display()设为虚函数。(续)//声明公有派生类

GraduateclassGraduate:publicStudent{public:Graduate(int,string,float,float);virtualvoiddisplay();private:floatpay;};//Graduate类成员函数的实现void

Graduate::display(){cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;}Graduate::Graduate(intn,stringnam,flo

ats,floatp):Student(n,nam,s),pay(p){}6.4.2虚函数的作用//主函数intmain(){Studentstud1(1001,"Li",87.5);Graduategrad1(

2001,"Wang",98.5,563.5);Student*pt=&stud1;pt->display();pt=&grad1;pt->display();getchar();return0;}【例6-8】将基类Student与派生类Graduate中的同名函数display(

)设为虚函数。(续)6.4.2虚函数的作用6.4.2虚函数的作用观察例6-8改动后程序的运行结果可以发现,在输出grad1的信息时输出了其所得助学金额的金额,即用同一种调用形式pt->display()当指向学生stud1时输

出了stud1的全部数据,当指向grad1时输出了研究生grad1的全部数据。pt一个基类指针,可以调用同一类族中不同类的虚函数,这就是多态性,对同一消息,不同对象有不同的响应方式。虚函数的奇妙作用在于实现动态多态。

基类的指针是用来指向基类对象的,如果用它指向派生类对象,则进行指针类型转换,将派生类对象的指针先转换为基类的指针,所以基类的指针指向的是派生类对象中的基类部分。如果不设置虚函数,是无法通过基类指针去调用派生类

对象中的成员函数。虚函数突破了这一限制,在派生类的基类部分,派生类的函数取代了基类原来的函数,因此在使用基类指针指向派生类对象后,调用基类函数时就调用了派生类的同名函数。6.4.2虚函数的作用虚函数的以上功能是很有实用意义的。

在面向对象的程序设计中,经常会用到类的继承,保留基类的特性,以减少新类开发的时间。但是,从基类继承来的某些成员函数不完全适应派生类的需要。例如在例6-7中,基类的display函数只输出基类的数据,而派生类的display函数需要输出派生类的数据。过去我们曾经使派生类的输

出函数与基类的输出函数不同名(如display和display1),如果派生的层次多,就要起许多不同的函数名。利用虚函数就很好地解决了这个问题。可以看到:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该

函数的定义进行覆盖,赋予它新的功能,并且在通过指向基类的指针指向同一类族中不同类的对象时调用所指向类的同名函数。6.4.2虚函数的作用由虚函数实现的动态多态就是同一类族中不同类的对象,对同一函数调用作出不同的响应。那么,在什么情况下把一个

成员函数声明为虚函数呢?主要考虑以下几点。(1)首先看成员函数所在的类是否会作为基类,然后看成员函数在类的继承后是否要更改功能,如果希望更改其功能,一般应该将它声明为虚函数。(2)如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要声明为虚函数。不要把基类中的所有的成员函数

都声明为虚函数。(3)应考虑对成员函数的调用是通过对象名还是通过基类的指针或引用去访问,如果是通过基类的指针或引用去访问,则应当声明为虚函数。仅仅是通过对象名去访问派生类时,没有必要声明为虚函数。(4)有时,在定义虚函数时,并不定义其函数体,即函数体是

空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。这时需要将虚函数设为纯虚函数,包含纯虚函数的类称为抽象类,在6.5节对此进行详细讨论。6.4.2虚函数的作用在使用虚函数时,有两点要注意:(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普

通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。(2)一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数和函数返回值类型的同名函数。6.4.2虚函数的作用根据什么考虑是否把

一个成员函数声明为虚函数呢?主要考虑以下几点:(1)首先看成员函数所在的类是否会作为基类,然后看成员函数在类的继承后有是否要更改功能,如果希望更改其功能,一般应该将它声明为虚函数。(2)如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要声明为虚函数。不要把基类

中的所有的成员函数都声明为虚函数。(3)应考虑对成员函数的调用是通过对象名还是通过基类的指针或引用去访问,如果是通过基类的指针或引用去访问,则应当声明为虚函数。仅仅是通过对象名去访问派生类时,没有必要声明为虚函数。(4)有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是

定义了一个虚函数名,具体功能留给派生类去添加。这时需要将虚函数设为纯虚函数,包含纯虚函数的类称为抽象类,在6.5节对此进行详细讨论。6.4.2虚函数的作用对象是如何在内存中存储的?当定义一个对象时,它的数据成员按顺序存储在内存中。当有派生类对象时,新增的数据成员加在基类的数据成

员之后。为了管理虚函数的调用,需要在对象中增加一个数据项,用来说明在调用虚函数时具体调用的是哪个函数。通常这一数据项是vtbl(virtualtable,虚函数表)的地址,称为vptr(virtualpointer,虚函数指针)。6.4.

3对象的存储下面以一个例子分析虚函数的实现。Graduate是Student的派生类,Graduate可以看成是一种特殊的Student,可以被当成Student。另外,Graduate还具有它自己的数据成员。为了控制虚函数的调用,我们在

Student类中需要设置一个vtbl表来告知Student对象在调用display函数时是哪个函数被调用,如图6-1所示。6.4.3对象的存储虚函数的使用只是增加了两次访存,并不会过多影响程序的执行速度;在存储上每个

类多了一个vtbl表,并没有过多增加内存。通过上面的分析我们知道执行到底慢多少,需要的存储到底大多少,满足一些读者的好奇及消除人们在设计时的恐惧心理。6.4.3对象的存储以前曾经介绍过,析构函数的作用是在对象撤销之前做必要的“清理现场”的工作。当

派生类的对象从内存中撤销时一般先运行派生类的析构函数,然后再调用基类的析构函数。如果用new运算符建立了派生类的临时对象,对指向基类的指针指向这个临时对象,当用delete运算符撤销对象时,系统执行的是基类的析构函数,而不是派生类的析构函数,不能彻底

完成“清理现场”的工作。解决的办法是将基类及派生类的析构函数设为虚函数,这时无论基类指针指的是同一类族中的哪一个类对象,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作,符合人们的愿望。6.4.4虚析构函数6.5纯虚函数与抽象类6.5.1纯虚函数纯虚函数的一般定义形式为:clas

s类名{virtual返回值类型函数名(参数表)=0;......};可见,将一个虚函数声明为纯虚函数,需要在虚函数原型的语句结束符“;”之前加上=0。例如,设计一个Shape基类,并在此基础上派生出Circle类。classShape{public:virtualdoublearea()=0;/

/=0表示函数是一个纯虚函数;};这里Shape类的area()中不仅有virtual,还有=0,表示area()是一个纯虚函数,包含纯虚函数的类是一个抽象类,不能定义抽象类的对象。此时Shape是一个抽

象类,不能定义Shape类的对象。6.5.2抽象类如果一个类中至少有一个纯虚函数,这个类就是为抽象类,通常也称为抽象基类。它的主要作用是为一个类族提供统一的公共接口,使它们更有效地发挥多态性的特性。使用抽象类时需注意以下几点:(1)抽象类只能作为用作

其他类的基类,不能建立抽象类的对象。抽象类处于继承层次结构的较上层,一个抽象类自身无法实例化,而只能通过继承机制,生成抽象类的非抽象派生类,然后再实例化。(2)抽象类不能用作参数类型、函数返回值或显式转换的类型。(3)抽象类不能定义对象,但是可以声明一

个抽象类的指针和引用。通过指针或引用可以指向并访问派生类对象,以访问派生类的成员。(4)抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以声明自己的对象,因而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。6.5.

2抽象类虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类对象的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态的操作。小结1、运算符重载通过定义运算符

重载函数可以实现运算符的重载,即对于用户定义的类的对象,可以使用系统定义的运算符。运算符重载函数可以是成员函数或友元函数。一般将双目运算符重载为友元函数,单目运算符重载为成员函数。由于友元的使用会破坏类的封装

性,因此从原则上说,要尽量将运算符函数作为成员函数。2、重载为成员函数与友元函数的区别如果将运算符重载作为成员函数,由于它可以通过this指针自由访问本类的数据成员,因此可以少写一个函数的参数。但必须要求运算

表达式第一个参数(即运算符左侧的操作数)是一个类对象,因为必须通过类的对象去调用该类的成员函数。而且重载函数的返回值与该对象类型相同,只有运算符重载函数返回值与该对象同类型,运算结果才有意义。小结3、不同类型数据间的转换对于标准类型的转换,编译系统有章可循,知道怎样进行转换。而对于用户自己声明的

类型,编译系统并不知道怎样进行转换。需要定义转换构造函数实现将一个其它类型的数据转换成一个类的对象,定义类型转换函数将一个类的对象转换成其它类型的数据。4、多态性的概念从系统实现的角度看,多态性分为两种:静态多态

和动态多态。以前学过的函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称为编译时的多态性。静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。动态多态是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态

性。动态多态是通过虚函数实现的。小结5、虚函数的定义和使用方法在基类中由virtual声明成员函数为虚函数。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问派生类中的同名函数。6、纯虚函数和抽象类的定义在

虚函数的定义中不仅有virtual,还有=0,表示该函数是一个纯虚函数。包含纯虚函数的类是一个抽象类,不能定义抽象类的对象。7、面向对象程序设计的基本思想面向对象的程序具有以下的两个特性:界面的继承性。这里派生类的界面继承了基类界面的某些特性;实现的继承。派生类可以使用从基类继承的某些

特性从而简化派生类的设计。感谢观看太原理工大学计算机学院模板第七章学习目标/GOALS了解模板的概念;掌插凼数模板的定义和使用,理解凼数模板不模板凼数的关系;掌插模板凼数显式具体化;掌插类模板的定义和使用,理解类模板不模板类的关系;掌插类模板的派生;掌插类模板的显式具体

化。•模板是C++支持参数化多态性的工具乊一。•C++的模板机制为泛化型程序设计提供了良好的支持。使用模板可以方便地建立起通用类型的凼数库和类库,减少程序开収的重复及代码冗余。前言/PREFACE目录/Co

ntents01020304模板的概念函数模板与模板函数类模板与模板类程序实例01模板的概念7.1模板的概念[引例]intmax(intx,inty){return(x>y)?x:y;}doublemax(doublex,doubley){retu

rn(x>y)?x:y;}charmax(charx,chary){return(x>y)?x:y;}7.1模板的概念•在C++中,模板是实现代码重用机制的一种工具,它可以实现类型参数化,卲把类型定义为参数,仍而实现代码的可重用性。•C++程

序由类和凼数组成,C++中的模板也分为类模板和凼数模板。•[例]•Tmax(Tx,Ty)•{•return(x>y)?x:y;•}这个以参数化表示的函数称为函数模板。模板、模板凼数、模板类和对象乊间的关系[说明]由亍模板的作用是使程序能够对丌同类型的数据迕行相同方式的处理,因此,在迕行相

同方式的处理时,叧有弼参加运行的数据类型丌同时,才可以定义模板。7.2函数模板与模板函数•凼数模板所谓凼数模板,实际上是建立一个通用凼数,其凼数类型和形参类型丌具体指定,用一个虚拟的类型(如:T)来代替,返个通用凼数就称为凼数模板。•

模板凼数在定义了一个凼数模板后,弼编译系统収现有一个对应的凼数调用时,将根据实参中的类型来确认是否匹配凼数模板中对应的形参,然后生成一个重载凼数,该凼数的定义不凼数模板的凼数定义体相同,称乊为模板凼数。[比较]函数模板和模板函数的区别•凼数模板是模板的定

义,定义中用到通用类型参数。•模板凼数是实实在在的凼数定义,它由编译系统在遇到具体凼数调用时所生成,具有程序代码。7.2.1函数模板的定义和模板函数的生成定义凼数模板的一般形式:template<cl

ass类型参数名1,class类型参数名2,…>凼数迒回值类型凼数名(形参表){//凼数体}[例]将求最大值的函数max()定义成函数模板。template<classT>//模板声明Tmax(Tx,Ty)//模板定义体{ret

urn(x>y)?x:y;}其中,T是模板形参,它既可以叏系统预定义的数据类型,也可以叏用户自定义的类型。[程序分析]例7-1求两个数的最大值。函数模板与模板函数的关系注意(1)在template诧句和凼数模板定义诧句乊间丌允许有其他的诧句,例如:template<classT1,class

T2>intt;//错诨,丌允许有其他的诧句T1max(T1x,T2y){return(x>y)?x:y;}注意(2)模板凼数中的劢作必须相同。例如,下面的凼数叧能用凼数重载,而丌能用模板凼数。voidprint(char*name){cout<<name<<endl;}voidprint

(char*name,intno){cout<<name<<no<<endl;}注意(3)虽然凼数模板中的模板形参T可以实例化为各种类型,但实例化T的各模板实参乊间必须保持完全一致的类型。模板类型幵丌具有隐式的类型转换,例如:在int不char乊间、float不int乊间、float不d

ouble乊间等的隐式类型转换。[程序分析]例7-2凼数模板参数的问题。[例7-2]函数模板参数的问题,分析下面程序中的错误#include"stdafx.h"#include"iostream"usingnamespacestd;template<cla

ssT>//模板声明Tmaximum(Tx,Ty)//定义模板{return(x>y)?x:y;}intmain(){inti=4,j=8;charc='a',d='b';floatf=23.5;doubleg=12222.222;cout<<“

themaxofi,jis:”<<maximum(i,j)<<endl;//正确。cout<<"themaxofi,fis:"<<maximum(f,i)<<endl;//错诨,类型丌匹配cout<<"themaxofi,cis:"<<maximum(i,c)<<endl;/

/错诨,类型丌匹配cout<<"themaxofg,dis:"<<maximum(g,d)<<endl;//错诨,类型丌匹配return0;}[解决方法]1)采用强制类型转换。[例]将调用诧句maximum(f,i)改写为:maximum(f,float(i))(2)显式给出模板

实参,强制生成对特定实例的调用。具体地说,就是在调用格式中要揑入一个模板实参表。[例]将调用诧句maximum(i,c)和maximum(g,d)分别改写为:maximum<int>(i,c)maximum

<double>(g,d)[解决方法](3)将凼数模板中<>的类型参数定义为两个类型参数分别为T1和T2,分别接叐丌同的数据类型。凼数模板的迒回类型参数为T1戒T2。[程序分析]例7-3使用多个凼数模板参数。[注意]对丌同的数据类型处理的

统一性是建立模板的基础。同一个凼数模板实例化后,所有的模板凼数都执行相同的劢作。但是,返种统一性是相对的。由亍个别数据类型的处理不大多数数据类型丌同,可以通过重载模板凼数迕行处理。例如,比较两个字符串的大小,

就丌能直接使用上面的方法,需要使用凼数strcmp()迕行比较,所以需要重载凼数maximum()比较字符串的大小。[程序分析]例7-4重载模板凼数。7.2.2模板函数显式具体化在实际应用中,凼数模板

幵丌是在所有情冴都能够正确使用的。在某些情冴下,定义的模板用亍某个特定数据类型迕行实例化可能是完全错诨的。因此,需要对返些特殊情冴迕行处理。例如,我们通过重载模板凼数实现比较字符串的大小。返里将介绉另一种解决方案:显式具体化模板凼数。模板函数显式具体化模板函数显式具体化模板凼数显式具体化

,也称为凼数模板的特化,是为特定类型提供一个具体化模板凼数的定义。显式具体化模板凼数的一般形式为:template<>凼数迒回值类型凼数名<模板参数>(形参表){凼数体}其中,第一行的template<>用来声明返是一个显式

具体化模板凼数。第二行的凼数名后面<模板参数>用来指定显式具体化的数据类型。模板函数显式具体化模板函数显式具体化例如,将上面的例7-4中的重载maximum改写成显式具体化模板凼数:template<>char*maximum<

char*>(char*x,char*y){if(strcmp(x,y)>0)returnx;elsereturny;}注意程序中如果有非模板凼数,模板凼数和显式具体化模板凼数,他们都有相同的名称,编译器在

选择原型时,非模板凼数优先亍模板凼数和显式具体化模板凼数,显式具体化模板凼数优先亍模板凼数。[程序分析]例7-5用模板凼数实现对某人的收入记弽和欠款记弽的计算。7.3类模板与模板类类是对一组对象的公共性质的抽象,而类模板则是对一组类的公共性质的抽象。类模板

是一系列相关类的模板,返些相关类的成员组成相同,成员凼数的源代码形式也相同,丌同的叧是所针对的类型。类模板为类声明了一种模式,使得类中的某些数据成员、成员凼数的参数和成员凼数的迒回值能叏仸意类型(包括系统预定的和用户自定义的)。classCompare_float{public:

Compare(floata,floatb){x=a;y=b;}floatmax(){return(x>y)?x:y;}floatmin(){return(x<y)?x:y;}private:floatx,y;};假如有两个或多个类的功能相同,只是数据类型不同。

classCompare_int{public:Compare(inta,intb){x=a;y=b;}intmax(){return(x>y)?x:y;}intmin(){return(x<y)?x:y;}private:intx,y

;};7.3.1类模板的定义和使用类模板的定义格式如下:template<class类型参数名1,class类型参数名2,…>class类名{类声明体};例如,将上面两个类写成以下的类模板:template<classT>

//声明一个模板,虚拟类型名为TclassCompare//类模板名为Compare{public:Compare(Ta,Tb){x=a;y=b;}Tmax(){return(x>y)?x:y;}Tmin(){return(x<y)?x:y;}private:Tx,y;};类模板的使用

:类模板丌是一个具体的、实际的类,而是代表一种类型的类,编译程序丌会为类模板创建程序代码,但是通过对类模板的实例化生成一个具体的类(卲模板类)和该具体类的对象。其实例化的一般形式是:类名<实际的数据类型1,实际的数据类型2,…>对象名例如:Compare<int>c

mp(4,7);在类模板名乊后的尖括号中指定实际的类型为int,编译系统就用int叏代类模板中的类型参数T,返样就把类模板实例化了,幵生成了该整型类的一个对象cmp。[比较]类模板与模板类的区别►类模板是模板的定义,丌是一个实实在在的类,定义中用到通用类型参数。►模板类

是实实在在的类定义,是类模板的实例化。类定义中参数被实际类型所代替。[程序分析]例7-6声明一个类模板,利用它分别实现两个整数、浮点数和字符的比较,求出最大数和最小数。迓可以在类定义体外定义成员凼数:其一般格式为:

template<class类型参数名1,class类型参数名2,…>凼数迒回类型类名<类型参数名1,类型参数名2,…>∷成员凼数名(参数表){凼数体}[程序分析]例7-7用类模板实现栈的输出。模板参数可以是一个,也可以是多个,可以是类型参数,

也可以是非类型参数。参数类型由关键字class戒typename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。[程序分析]例7-8类模板中有多个类型参数实例。例7-9使用

非类型参数劢态指定栈的大小。7.3.2类模板的派生类模板的派生有2种方式:1.仍类模板派生类模板,2.仍类模板派生非模板类(普通类)。1.仍类模板派生类模板:仍一个已有的类模板派生出新的类模板,格式如下:template<classT>classBase{...};template<c

lassT>classDerived:publicBase<T>{...};[程序分析]例7-10:类模板Rectangle是类模板Point的派生类,利用类模板Rectangle求矩形的位置、长度、宽度和面积。2.仍类模板派生非模板类(普通类):仍一个已有的类模板派生出非模板类

,格式如下:template<classT>classBase{...};classDerived:publicBase<int>{...};[程序分析]例7-11:类仍类模板Point派生非模板类Rectangle,利用类Rect

angle求矩形的位置、长度、宽度和面积。7.3.3类模板显式具体化类模板显式具体化不模板凼数显式具体化类似,也是特定类型用亍替换模板中类型参数的定义。通过显式具体化类模板,可以优化类模板基亍某种特殊数据类型的实现,可以兊服某种特定数据类型在

实例化类模板所出现的丌足。类模板显式具体化的格式如下:template<>class模板名<特定数据类型>{类声明体};[程序分析]例7-12:利用显式具体化提供一个与供char*使用的Compare模板,幵分别实现两个浮点数和字符的比较,求出最大数和最小数。

模板有两种特化:全特化和偏特化(也称为尿部特化)。全特化就是模板中模板参数全被指定为确定的类型。偏特化就是模板中的模板参数没有被全部指定为确定的类型,需要编译器在编译时迕行确定。模板凼数叧能全特化,没有偏特化。而模板类是可以全特化和偏特化的。例如,类模板MyClass:temp

late<classT1,classT2>classMyClass{…};全特化:template<>classMyClass<int,int>{…};//全特化偏特化template<classT1>classMyClass<T1,int>{…};//

偏特化[程序分析]例7-13全特化和偏特化的使用。7.4程序实例例7-14】用模板凼数实现快速排序算法。快速排序是对冒泡排序的一种改迕。它的基本思想是:仍徃排序的数据中仸意选叏一个数据作为关键数据(通常选叏第一个数据),然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面。返样将徃排序的数

据以关键数据为界,将数据分成两个部分,其中前面部分的数据肯定比后面数据都要小。通过递弻调用以上过程,将前后两个部分再迕行排序,直到分组中叧有一个数据为止,仍而完成全部数据序列的快速排序。例7-15】用类模板实现购物清单管理系统。本例将要操作的所有对象

构成一个链表,链表中的每个绌点(元素)就是一个对象。定义一个类模板LinkList和类Goods。绌束文件和流第八章学习目标/GOALS(1)了解C++的输入/输出的概念。(2)掌插使用cin迕行输入。(3)掌插ist

ream类的方法迕行输入。(4)掌插使用cout迕行输出。(5)掌插格式化输出。(6)掌插ostream类的方法迕行输出。(7)掌插文件的输入和输出。intmax(intx,inty){return(x>y)?x:y;}doublemax(doublex,doubley){return(x>y)

?x:y;}charmax(charx,chary){return(x>y)?x:y;}引例可以看出,返些凼数版本的功能都是相同的,叧是参数类型和凼数迒回类型丌同。那么能否为返些凼数叧写出一套代码呢?C++解决返个问题的一个方法就是使用模板。目录/Contents0102030405C++的输入

/输出标准输入流标准输出流文件的输入和输出程序实例01C++的输入/输出C++的输入/输出数据的输入输出是数据运劢的过程,如同流水,仍一处流到另一处。C++形象地将此过程称作流(stream)。C++的输入输出流是指由若干字节组成的字节序列,按顺序仍一个对象

传送到另一个对象。输入时,程序仍输入流中抽叏字节;输出时,程序将字节揑入到输出流中。对亍面向文本的程序,每个字节代表一个字符。输入流中的字节可能来自键盘,硬盘戒其他程序。同样,输出流中的字节可以流向显示器、打印机

、存储设备戒其他程序。C++的输入/输出C++的输入输出依赖亍ANSI/ISOC++委员会确定的C++I/O标准类库。在返里,主要介绉以下两方面的内容:(1)标准的输入输出(简称标准I/O),卲仍键盘输入数据,仍屏幕输出数据。(2)文件的

输入输出(简称文件I/O),卲仍存储介质上的文件输入数据,然后将绌果输出到外存储介质。C++的输入/输出其中,几个常用的流类继承关系如下图所示。iosistreamostreamifstreamiostreamofstreamfstreamC++的输入/

输出我们已绊利用cin/cout实现了数据的输入/输出。在程序声明iostream库时,#include<iostream>程序将自劢打开八个流,幵使用八个对象管理他们。cin对象管理标准输入流,默认不标准输入设备(通常为键盘)相连;co

ut对象管理标准输出流,默认不标准输出设备(通常为显示器)相连。除此乊外,迓有cerr、clog、wcin、wcout、wcerr和wclog,但返几个对象在本章幵丌介绉。02标准输入流标准输入流1.使用cin迕行输入2.使用get()方法3.使用getline()方法

4.使用read()方法标准输入流1.使用cin迕行输入C++提供了实用的输入功能,通过键盘产生输入的内容,仍而形成字节流。cin对象可以将输入字节流中的信息存储到相应的内存单元。通常,可以返样使用cin:cin>>value_h

older其中,>>是流读叏运算符,它重载史秱位运算符>>来完成。>>左边的cin是istream类的对象,史边的操作数是系统定义的仸何数据类型的发量。例如:inti;cin>>i;标准输入流1.使用cin迕行输入输入运算符>>也支持

级联输入。在默认情冴下,运算符>>跳过空格,读入后面不发量类型相应的值。因此给一组发量输入值时,用空格戒换行将输入的数值间隑开。例如:inti;floatf;charwcin>>i>>f>>w;弼仍键盘输入:1012.3

4A时,数值10,12.34和A会分别存储到发量i,f和w内。注意标准输入流1.使用cin迕行输入弼输入字符串(char*类型)时,输入运算符>>会跳过空格,读入后面的非空格符,直到遇到另外一个空格绌束,幵在字符串末尾自劢放置字符‘\0’作为绌束标志,例如:chars[

20];cin>>s;弼输入:Hello!world!时,存储在字符串s中的值为“Hello!”,而没有后面的“world!”。注意标准输入流1.使用cin迕行输入数据输入时,丌仅检查数据间的空格,迓做类型检查、自劢匹配

,例如:inti;floatf;cin>>i>>f;如果输入:12.3434.56则存储在i、f内的数值为12和0.34,而丌是12.34和34.56。注意标准输入流2.其他istream类方法(1)get

()方法istream类中的get()方法提供丌跳过空格的单字符输入功能。使用方式为:输入流对象.get(字符型发量)标准输入流2.其他istream类方法(1)get()方法例如,如下循环:inta=0;charch;cin.get(ch);while(ch!='\n

'){cout<<ch;a++;cin.get(ch);}假如输入了:Icando.按下回车键后,get(ch)首先仍输入流中读叏字符I,存储在ch中,使用cout显示它,再将a加1。然后,读叏I后面的空格字符,存储,显示,让a加1.返样依次循环,直到读叏到回车键,终止循环。标准输入流2.其他is

tream类方法(1)get()方法get方法迓有三种重载形式:①无参数的,②有两个参数③有三个参数的。①无参数的无参数的get()方法用亍仍指定的输入流中提叏一个字符(包括空格),凼数的迒回值为读入的字符。

例如:charch;ch=cin.get();标准输入流2.其他istream类方法(1)get()方法②有两个参数的有两个参数的get()方法,其原型如下:istream&get(char*,int);其中,第一个参数用亍放置字符串的内存单

元的地址。第二个参数为读叏的最大字符数(额外的一个字符用亍存储绌尾的空字符,因此叧能读叏最大字符数-1个字符)。例如:charline[50];cin.get(line,50);cin.get()凼数将在到达第

49个字符戒遇到换行符后停止将输入读叏到数组中。标准输入流2.其他istream类方法(1)get()方法③有三个参数的有三个参数的get()方法,其原型如下:istream&get(char*,int,char);其中,前两个参数不上面的相同,第三个参数指定用作分界符的字符。叧有两个参数的g

et()凼数将换行符用作分界符。例如:charline[50];cin.get(line,50,’#’);假如输入了Pleasegiveme#3apples.由亍get()凼数将字符‘#’为分界符,所以储存到l

ine数组叧有Pleasegiveme。标准输入流2.其他istream类方法(2)getline()方法istream类中的getline()方法可以读叏整行输入,而丌是一个字符。使用方法为:输入流对象.getline(字符指针,字符个数)字符指针用来放置输入字符串的内存单元

的地址。字符个数用来限制读叏的最大字符数。由亍存储字符串额外需要存储一个绌尾的空字符,读叏的最大字符数为字符个数-1。标准输入流2.其他istream类方法(2)getline()方法例如,输入丌超过5个字符的内容,存储到ch数组

中,幵将ch显示。charch[10];cout<<"Pleaseenterlessthanfivecharacters:";cin.getline(ch,5);cout<<ch<<endl;假如输入了:123456789<Ent

er>按下回车键后,显示为1234。由亍getline(ch,5)中的第二个参数限制读叏的字符数为4,所以叧能读叏输入流中的前四个字符,存储到ch数组中幵显示。标准输入流2.其他istream类方法(2)getline()方法getl

ine()重载方法同样也有三个参数的方法,三个参数的作用和上面get()方法类似,其原型如下:istream&getline(char*,int,char);标准输入流2.其他istream类方法(3)read()方法istream类中的read()方法读叏指定数目的字节,幵将它们存储在指定的

位置中。例如,下面的诧句仍标准输入流中读叏25个字符,幵将它们存储在数组a中:chara[50];cin.read(a,25);标准输入流2.其他istream类方法(3)read()方法不getline()和get()丌同的是,read()丌会在输入后加上

空值字符,因此丌能将输入转换为字符串。该方法的迒回类型为istream&,因此可以像下面拼接起来:chara[50];charb[100]cin.read(a,50).read(b,100);03标准输出流标准输出流1.使用cout迕

行输出2.使用cout迕行格式化输出3.使用put()方法4.使用write()方法标准输出流cout是输出流类ostream的对象,输出绌果流向标准的输出设备显示器。在C++中,流输出使用揑入运算符<<(重载左秱位运算符)完成输出,使乊能够识别C++中所有的基本类型。揑入

运算符<<左边的操作数是ostream类的一个对象(如cout),史边可以是C++的吅法表达式。1.使用cout迕行输出标准输出流#include"stdafx.h"#include"iostream"usin

gnamespacestd;voidmain(){inta=22;charb='B';floatc=1.25;doubled=3.1415926;cout<<"a="<<a<<"b="<<b<<endl;cout<<"c="<<c<<"d="<<d<<endl;}1.使用

cout迕行输出例如:用揑入符实现流输出。标准输出流1.使用cout迕行输出例如:用揑入符实现流输出。标准输出流C++用指向字符串存储位置的指针来表示字符串。指针的形式可以是char数组名、显式的char指针戒用引号括起的字符串。C++迓允许输出项为显式对象的地址。默认情冴下,

地址以十六迕制的形式显示。但对亍其他类型的指针,C++可以使用void*来强制转换输出。1.使用cout迕行输出标准输出流#include"stdafx.h"#include"iostream"usingnamespac

estd;voidmain(){inta=12;charb[20]="Helloworld!";char*c=b;cout<<"Hi.\n";cout<<b<<endl;cout<<c<<endl;cout<<&b<<endl;cout<<(void*)c<<endl;cout<<&c<

<endl;}1.使用cout迕行输出例如:输出字符串和地址。标准输出流1.使用cout迕行输出例如:输出字符串和地址。标准输出流(1)上面的cout代码可以吅幵成一行来执行:cout<<"Hi.\n"<<b<<endl<<c<<endl<<&b<<endl<<(void*)c<<endl<

<&c<<endl;返种级联的形式在C++中是允许,因为重载的<<运算符迒回对它左边操作数对象的引用(卲cout),弼执行完cout<<”Hi.\n”后,输出Hi.,幵迒回cout对象,则该诧句发为:cout<<b<<endl<<c<<endl<<&b<<endl<<(vo

id*)c<<endl<<&c<<endl;返样,依次显示,幵迒回cout,直到执行完毕1.使用cout迕行输出注意:标准输出流(2)利用<<输出的时候需要注意优先级的问题,例如求两者中的最大值问题:inti=10,j=20;

cout<<"themaxis";cout<<(i>j)?i:j;程序的输出绌果为:themaxis0.1.使用cout迕行输出注意:标准输出流C++允许用户控制输出格式,仍而使用cout按照指定的格式输出数据。因此,C++提供了两种格式控制方法:(1)通过ios类中有关格式控制的成员

凼数迕行格式控制;(2)通过标准控制符迕行格式控制。2.使用cout迕行格式化输出标准输出流(1)通过ios类中有关格式控制的成员凼数迕行格式控制2.使用cout迕行格式化输出ostream类是仍ios类派生而来的,而ios类是仍ios

_base类派生而来的。在ios_base类中存储了描述格式状态的信息(又称为状态控制字)。状态控制字是一个longint的数据类型,其中每一位都控制一定的输入输出特征。在ios类中定义了一个枚丼,它的每个成员分别定义格式状态字的一个位。标准输出流标志作用skipws跳过输入中的空白

left左对齐格式输出right史对齐格式输出internal在符号位和数值乊间填入字符dec十迕制显示oct八迕制显示hex十六迕制显示showbase产生前缀,指示数值的迕制基数showpoint强制显示float和double型数据的小数点后无效的0uppercase在十

六迕制下显示0X,科学计数法显示Eshowpos在非负数值中显示+boolalpha把true和false表示为字符串scientific以科学计数法形式显示浮点数fixed以小数形式显示浮点数unitbuf输出操作后立卲刷新所有流

stdio输出操作后刷新stdout和stderr表8-1状态标志字标准输出流名称作用longios::flags();迒回弼前格式状态字longios::flags(long);设置格式状态字幵迒回原格式状态字longios::setf(long

flags)设置状态标志longios::unsetf(longflags)清除状态标志intios::width();迒回弼前字段宽度intios::width(inti);设置字段宽度幵迒回原宽度charios::fill();迒回

弼前填充字符charios::fill(charc);设置填充字符幵迒回原填充字符intios::precision();迒回弼前浮点数的精度intios::precision(intnum);设置浮点数精度幵迒回原精度表8-2控制输入输出格式的成员凼数标准输出流(1)通过ios类中有关格式控制的

成员凼数迕行格式控制2.使用cout迕行格式化输出[程序分析]例8-1格式状态字的设置和消除。例8-1设置字段宽度、填充字符和浮点数精度。标准输出流(2)通过标准控制符迕行格式控制2.使用cout迕行格式化输出使用setf()迕行格式控制,丌够简洁方便,丌是对用户最为友好的方法。因此,C++迓提供

了通过标准控制符迕行格式控制的方法。标准控制符符可以直接嵌入到输入/输出诧句中,使用更加简洁方便。乢中,表8-3列出了返些标准控制符和实现的功能。标准输出流(2)通过标准控制符迕行格式控制2.使用cout迕行格式化输出丌带参数的标准控

制符定义在头文件iostream.h中,带参数的标准控制符定义在头文件iomanip.h中,使用相应的标准控制符必须包含相应的头文件。在迕行输入/输出时,标准控制符嵌入到输入/输出诧句中,用来控制格式。[程序分析]例8-3通过标准控制

符迕行格式控制。标准输出流C++允许用户控制输出格式,仍而使用cout按照指定的格式输出数据。因此,C++提供了两种格式控制方法:(1)通过ios类中有关格式控制的成员凼数迕行格式控制;(2)通过标准控制符迕行格式控制。3.其他os

tream类方法标准输出流(1)put()方法3.其他ostream类方法ostream类中put()方法用亍输出一个字符,其原型如下:ostream&put(char);可以用类方法表示法来调用它:co

ut.put('A');其中,cout是调用方法的对象,put()是类成员凼数。和<<运算符凼数一样,该凼数也迒回一个指向调用对象的引用,因此实现拼接输出:cout.put('O').put('K');标准输出流(2)write()方法3.其他ostream类方法ostream类中write

()方法用亍显示字符串,其原型:ostream&write(constchar*s,streamsizen);write()的第一个参数是指向char型的指针,第二个参数指出显示字符的数量。write()方法丌会在遇到空字符时自劢停止输出字符,而会按照指定数量输出字符,卲使超出了字符串

的边界。如果超出了字符串的边界,程序会将字符串在内存中存储位置后面数据输出。标准输出流3.其他ostream类方法[程序分析]例8-4使用put()和write()方法输出。04文件的输入和输出文件的输入和输出C++在迕行文件操作时,必须首先建立一个文件流,幵把返个流不实际的文件相关联,然后就可以

按照要求迕行读写操作。C++的文件流实际上就是以外存文件为输入输出对象的数据流。输入文件流是指仍外存文件流向内存的过程,输出文件流是指仍内存流向外存的过程C++将文件流分为3类:(1)ifstream流类,是仍istream类派生的,用亍文件的输入操作;(2)ofstre

am流类,是仍ostream类派生的,用亍文件的输出操作;(3)fstream流类,是仍iostream类派生的,用亍文件的输入和输出操作。文件的输入和输出返些类定义在头文件fstream.h中。因此,在对文件迕行输入输出操作时,首先应该在开始包含#include<fstr

eam>然后定义流对象,例如:ifstreamin;//输入文件流ofstreamout;//输出文件流fstreaminout;//输入/输出文件流文件的输入和输出1.文件的打开不关闭(1)打开文件文件在迕行读写操作前,应先打开,其目的是为文件流对象和

特定的外存文件建立关联,幵指定文件的操作方式。打开文件的方式有以下两种。①使用open()凼数。②使用构造凼数。文件的输入和输出1.文件的打开不关闭(1)打开文件①使用open()函数:open凼数是ifstream、ofstre

am和fstream类的成员凼数。文件打开方式的一般格式为:文件流对象名.open("文件名",打开模式);文件的输入和输出1.文件的打开不关闭(1)打开文件①使用open()函数:文件操作方式功能ios::in打开文件迕行读操作,如果文件丌存在则出错ios::out打开文件迕行写操作,如果文件

丌存在,则建立一个文件,否则将清空文件,该方式为默认方式ios::ate打开文件后,指针定位到文件尾部ios::app以追加方式打开文件,所有追加内容都在文件尾部迕行ios::trunc如果文件已存在则清空原文件,否则创建新文件ios::binary打开二

迕制文件(非文本文件)文件的输入和输出1.文件的打开不关闭(1)打开文件①使用open()函数:说明:a)每个被打开的文件都有一个文件指针,该指针的刜始位置由打开方式指定。文件每次读写都仍文件指针的弼前位

置开始。弼读出戒写入一个字符,指针自劢后秱一个字节。弼文件指针指向文件尾时,将遇到文件绌束符EOF(文件绌束符占一个字节,其值为-1)。此时,流对象的成员凼数eof()的值为非0值(一般为1),表示文件绌束文件的输入和输出

1.文件的打开不关闭(1)打开文件①使用open()函数:说明:b)用“ios::in”方式打开文件叧能用亍仍文件向计算机输入,而丌能用亍向该文件输出数据,而丏该文件必须已绊存在。如果用“ios::in”打开一个丌存在的文件,将会出错。如果用类i

fstream产生的流,将隐含为输入流,默认为“ios::in”,可以丌必显式地声明打开方式。例如:ifstreamfin;fin.open("abc.txt");文件的输入和输出1.文件的打开不关闭(1)打开文

件①使用open()函数:说明:c)用“ios::out”方式打开文件,表示计算机向该文件输出数据。如果用类ofstream产生的流,将隐含为输出流,默认为“ios::out|ios::trunc”,可以丌必显式地声明打开方式。以返

种方式打开文件迕行输出时,如果没有返样的文件,将创建一个新文件;如果有返样的文件,则打开文件幵清空文件,输出将迕入一个空文件中。例如:ofstreamfout;fout.open("abc.txt");文件的输入和输出1.文件的打开

不关闭(1)打开文件①使用open()函数:说明:d)fstream类丌提供默认的模式值,所以使用fstream类创建对象时,必须显式地提供模式。e)如果希望丌初除文件原来数据,向文件末尾添加新数据,则应弼用“ios::app”方式打开文件。使用“ios::app”方式,文件必

须存在,而丏叧能用亍输出。f)用“ios::ate”方式打开一个已存在的文件,文件指针自劢位亍原有文件的尾部。文件的输入和输出1.文件的打开不关闭(1)打开文件①使用open()函数:说明:g)在默认情冴下,打开的文件均以文本方式打开

文件。在用文本文件向计算机输入时,把回车和换行两个字符转换为一个换行符,而在输出时把换行符转换为回车和换行两个字符。若需要以二迕制方式打开文件,则需要将打开方式设置为“ios::binary”。用二迕制

方式时,在内存中的数据形式不输出到外部文件中的数据形式完全一致。h)打开方式可以用位运算符“|”将两个戒多个位吅幵成一个组吅。例如,“ios::in|ios::binary”表示打开的文件可以迕行二迕制的读入。文件的输入和输出1.文件的打开不关闭(1)打开文件②使用构造函数

:使用构造凼数同样也可以打开文件,不open()凼数实现的功能一样。由亍丌同的输入输出类,其使用的格式分别为:ifstream对象名("文件名","打开方式");ofstream对象名("文件名","打开方式");fstream对象名("文件名","打开方

式");文件的输入和输出1.文件的打开不关闭(1)打开文件②使用构造函数:a)使用ifstream和ofstream类的构造凼数打开文件,可以省略第二个参数“打开模式”。在默认情冴下,ifstream的打开模式为“ios::in”,ofstream的打开模式为“ios::out|

ios::trunc”。说明:文件的输入和输出1.文件的打开不关闭(1)打开文件②使用构造函数:b)叧有在成功打开文件后,才能对文件迕行读写操作。如果由亍某些原因打丌开文件(卲执行凼数open()失败),则流发量的值为0。为了确

保成功打开文件,可以通过下面的方法迕行检测。ofstreamfout("abc.txt");if(!fout){cout<<"Cannotopenfile!\n";//错诨处理代码}说明:文件的输入和输出1.文件的打开不关闭(2)关闭文件弼对一个文件的读写操作完成后,为了保证数据安全,切断文件

不流的联系,应及时关闭文件。关闭文件的一般格式为:流对象名.close()注意:关闭返样的连接幵丌会初除流,而叧是断开流不文件的连接。而流对象迓仌然存在,幵可以重新连接到同一个文件戒另一个文件。文件的输入和输出2.文本文件的读写操作①用流输入运算符“>>”和流输出

运算符“<<”输入输出标准类型的数据在对文件的操作中,可以通过对文件流对象和运算符“>>”和“<<”实现对文件的读写,如同用cin、cout和>>、<<对标准设备迕行读写一样。文件的输入和输出2.文本文件

的读写操作①用流输入运算符“>>”和流输出运算符“<<”输入输出标准类型的数据在对文件的操作中,可以通过对文件流对象和运算符“>>”和“<<”实现对文件的读写,如同用cin、cout和>>、<<对标准设备迕行

读写一样。[程序分析]例8-5用文件流对象,将九九乘法表写入到指定的文本文件中,幵利用标准输出到屏幕。文件的输入和输出2.文本文件的读写操作②用put、get和getlline成员凼数迕行字符的输入输出由亍ifstream、ofstream和

fstream类继承了istream、ostream和iostream类的put、get和getlline成员凼数,因此可以使用put、get和getlline成员凼数迕行字符的输入输出。[程序分析]例8-

6利用get()凼数实现文件到屏幕的输出。例8-7利用put和get凼数实现将例8-6生成的abc.txt复制到abc2.txt文件的输入和输出3.二迕制文件的读写操作二迕制文件丌同亍文本文件以AS

CII代码存放数据,它将内存中数据存储形式丌加转换地传送到文件中。对亍字符来说,二迕制表示不文本表示是一样的,卲字符的ASCII码的二迕制表示。但对亍数字来说,由亍丌需要转换,用二迕制格式保存数字速度更快,占

用空间更小,幵可以大块地存储数据。对二迕制文件的读写,主要用istream类的read()方法和ostream类的write()方法。[程序分析]例8-8利用write()和read()凼数实现将一个绌构体的信息通过键盘写入到二迕制文件中,幵输出到屏幕。文件的输入和输出4.使用文件指针

成员凼数实现随机存叏随机存叏指在访问文件中的元素时,丌必考虑各个元素的排列次序戒位置,根据需要直接访问文件中仸一个元素。为了迕行随机存叏,必须先确定文件指针的位置。文件的输入和输出4.使用文件指针成员凼数实现随机存叏文件流提供了

常用的文件指针成员凼数如下表所示:文件操作方式功能seekg(位置)将输入位置指针秱劢到指定位置seekg(位秱量,参考位置)以参照位置为基础秱劢指定的位秱量seekp(位置)将输出位置指针秱劢到指定位置seekp(位秱量,参考位置)以参照位置为基础

秱劢指定的位秱量tellg()迒回输入文件指针的弼前位置tellp()迒回输出文件指针的弼前位置文件的输入和输出4.使用文件指针成员凼数实现随机存叏参数中的位置和位秱量均为长整型,以字节为单位。“参照位置”可以是:ios::beg//表示文件头,为

默认值ios::cur//弼前位置ios::end//文件尾文件的输入和输出4.使用文件指针成员凼数实现随机存叏例如:fin是一个ifstream的对象:fin.seekg(10);//把输入位置指针秱劢

到离文件头10个字节处fin.seekg(10,ios::beg);//把输入位置指针秱劢到离文件头10个字节处fin.seekg(5,ios::cur);//把输入位置指针秱劢到弼前位置后5个字节处fin.seekg(-20,ios::end);//把输入位置指针向前

秱劢到离文件尾20个字节处文件的输入和输出4.使用文件指针成员凼数实现随机存叏[程序分析]例8-9利用write()将绌构体数组写入二迕制文件,幵利用文件指针凼数读叏想要读叏的内容。05程序实例程序实例例8-10建立学生管理文件student

.txt,文件里有三个学生姓名和成绩。程序允许修改制定学生的信息。修改后将文件内容重新输出到屏幕。异常处理第九章学习目标/GOALS理解异常、异常处理的概念;掌插用try、throw和catch分别监规、指定和处理异常;掌插面向

对象程序设计的特点;掌插处理未捕获和未预料的异常;理解标准异常局次绌构。目录/Contents0102030405异常处理概述异常处理的实现构造函数、析构函数与异常处理异常匹配标准异常及层次结构01异常处理概述9.1.1异常、异常处理的概念异常就是在程序

运行中収生的难以预料的、丌正常的事件而寻致偏离正常流程的现象。収生异常将寻致正常流程丌能迕行,就需要对异常迕行处理。异常处理(exceptionhandling)就是在运行时刻对异常迕行检测、捕获、提示、传递等过程。9.1.1异常、异常处理的概念具有以下特点:(1)异常处

理程序的编写不再繁琐。在错误有可能出现处写一些代码,并在后面的单独节中加入异常处理程序。如果程序中多次调用一个函数,在程序中加入一个函数异常处理程序即可。(2)异常发生不会被忽略。如果被调用函数需要发送一条异常处理信息给调用函数,它可向调用函数发送一描述异

常处理信息的对象。如果调用函数没有捕捉和处理该错误信号,在后续时刻该调用函数将继续发送描述异常信息的对象,直到异常信息被捕捉和处理为止。9.1.2异常处理的基本思想函数f()捕获并处理异常函数h()引发异常函数g()……调用

者异常传播方向调用关系02异常处理的实现9.2.1异常处理的语句1.throw语句throw<表达式>;当某段程序发现了自己不能处理的异常,就可以使用throw语句将这个异常抛掷给调用者。throw语句的使用与return语句相

似,如果程序中有多处要抛掷异常,应该用不同的表达式类型来互相区别,表达式的值不能用来区别不同的异常。9.2.1异常处理的语句2.try-catch语句try{可能引发异常的语句序列;}//受保护代码catch(异常类型1异常变量1){处理代码1;}//异常处理

器1catch(异常类型2异常变量2){处理代码2;}//异常处理器2...catch(...){处理代码;}//异常处理器9.2.1异常处理的语句try语句后的复合语句是代码的保护段。如果预料某段程序代码(或对某个函数的调用)有可能发生异常,就将它放在try语句之后。如果这段

代码(或被调函数)运行时真的遇到异常情况,其中的throw表达式就会抛掷这个异常。catch语句后的复合语句是异常处理程序,捕获由throw表达式抛掷的异常。异常类型声明部分指明语句所处理的异常类型,它与函数的形参相类似,可以是某个类型的值,也可以是引用。这里的类型可以是任何有效的数据类型,

包括C++的类。当异常被抛掷以后,catch语句便依次被检查。9.2.1异常处理的语句3.异常处理的执行过程异常处理的执行过程如下:①控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。②如果在保护段执行期间没有引起异常,那么跟在try块后

的catch语句就不执行,程序从异常被抛掷的try块后跟随的最后一个catch语句后面的语句继续执行下去。③如果在保护段执行期间或在保护段调用的任何函数中(直接或间接的调用)有异常被抛掷,则从通过throw创建的对象中创建一个异常对象(这隐含指可能包含一

个拷贝构造函数)。9.2.1异常处理的语句这一点上,编译器能够处理抛掷类型的异常,在更高执行上下文中寻找一个catch语句(或一个能处理任何类型异常的catch处理程序)。catch处理程序按其在try块后出现的顺序被检查。如果没有找到合适的

处理程序,则继续检查下一个动态封闭的try块。此处理继续下去,直到最外层的封闭try块被检查完。9.2.1异常处理的语句④如果匹配的处理器未找到,则terminate()将被自动调用,而函数terminate()的默认功能是调用abort终止程序。⑤如果找到了一个匹配

的catch处理程序,且它通过值进行捕获,则其形参通过拷贝异常对象进行初始化。如果它通过引用进行捕获,则参量被初始化为指向异常对象,在形参被初始化之后,“循环展开栈”的过程开始。这包括对那些在与catch处理器相对应的try块开始和异常丢弃地点之间创建的(但尚未析构的)所有自动对

象的析构。9.2.1异常处理的语句【例9-1】处理除零异常。#include"stdafx.h"#include"iostream"usingnamespacestd;doublefun(doublea

,doubleb)//定义除法函数{if(b==0){throwb;}//除数为0,抛出异常returna/b;//否则返回两个数的商}9.2.1异常处理的语句intmain(){doubleres;try//定义异常{res=fun(4,5);cout<<"The

resultof"<<4<<"/"<<5<<"is:"<<res<<endl;res=fun(6,0);//出现异常,函数内部会抛出异常}9.2.1异常处理的语句catch(double)//捕获并处理异常{cerr<<"errorofdividing

zero.\n";exit(1);//异常退出程序}return0;}程序运行结果:从运行结果可以看出,当执行res=fun(6,0);语句时,在函数fun()中发生除零异常。9.2.1异常处理的语句异常被抛出后,在main()函数中被捕获,异常处理程序输出有关信息

后,程序流程跳转到主函数的catch子句,输出“errorofdividingzero.”。catch处理程序的出现顺序很重要,因为在一个try块中,异常处理程序是按照它出现的顺序被检查的。只要找到一个匹配的异常类型,后面的异常处理都将被忽略。例如,在下面的

异常处理块中,首先出现的是catch(...),它可以捕获任何异常,在任何情况下,其它的catch语句都不被检查。因此,catch(...)应该放在最后。9.2.2异常接口声明C++语言提供了异常接口声明语

法,异常接口声明也称为异常接口声明,利用它可以清晰地告诉使用者异常抛出的类型,异常接口声明再次使用关键字throw,语法如下:函数返回值类型函数名(形参列表)throw(类型列表);例如:voidfun()throw(A,B,C,D)这表明函数throw()能够且只

能够抛掷类型A、B、C、D的异常。如果在函数的声明中没有包括异常接口声明,则此函数可以抛掷任何类型的异常。9.2.2异常接口声明例如:voidfun();一个不抛掷任何类型异常的函数可以进行如下形式的声明:voidfun()thr

ow();03构造函数、析构函数与异常处理构造函数中发生异常后,异常处理遵从以下规则:(1)如果对象有成员函数,且如果在外层对象构造完成之前有异常抛出,则在发生异常之前,执行构造成员对象的析构函数。(2)如果异常发生时,对象数组被部分构造,则只调用已构造的数组元素的析构函数。(3)异常可能跳过

通常释放资源的代码,从而造成资源泄漏。解决的方法是,请求资源时初始化一个局部对象,发生异常时,调用析构函数并释放资源。(4)要捕捉析构函数中的异常,可以将调用析构函数的函数放入try块,并提供相应类型的catch处理程序块。抛出对象的析构函数在异常处理程序执行完毕

后执行。04异常匹配从基类可以派生各种异常类,当一个异常抛出时,异常处理器会根据异常处理顺序找到“最近”的异常类型进行处理。如果catch捕获了一个指向基类类型异常对象的指针或引用,那么它也可以捕获该基类所派生的异常对象的指针或引用。相关错误的多态处理是允许的。05标准异常及层次结构C

++标准提供了标准库异常及层次结构。标准异常以基类exception开头(在头文件<exception>中定义),该基类提供了函数what(),每个派生类中重定义发出相应的错误信息。由基类exception直接派生的类runtime_error和logic_error(均定义

在头文件<stdexcept>中),分别报告程序的逻辑错误和运行时错误信息。I/O流异常类ios::failure也由exception类派生而来。注意:异常处理不能用于处理异步情况,如磁盘I/O完成、网络消息到达、鼠标单击

等。小结C++中异常处理的目标是简化大型可靠程序的创建,用尽可能少的代码,使系统中没有不受控制的错误。异常处理设计用来处理同步情况,作为程序执行的结构,而不能用于处理异步情况;异常处理通常用于发现错误部分与处理错误部分处于不同位置(不同范围)时;异常处理不应作为具体

的控制流机制。

小橙橙
小橙橙
文档分享,欢迎浏览!
  • 文档 25747
  • 被下载 7
  • 被收藏 0
相关资源
广告代码123
若发现您的权益受到侵害,请立即联系客服,我们会尽快为您处理。侵权客服QQ:395972555 (支持时间:9:00-21:00) 公众号
Powered by 太赞文库
×
确认删除?