C面向对象的程序设计

上传人:cl****1 文档编号:568247958 上传时间:2024-07-23 格式:PPT 页数:930 大小:2.06MB
返回 下载 相关 举报
C面向对象的程序设计_第1页
第1页 / 共930页
C面向对象的程序设计_第2页
第2页 / 共930页
C面向对象的程序设计_第3页
第3页 / 共930页
C面向对象的程序设计_第4页
第4页 / 共930页
C面向对象的程序设计_第5页
第5页 / 共930页
点击查看更多>>
资源描述

《C面向对象的程序设计》由会员分享,可在线阅读,更多相关《C面向对象的程序设计(930页珍藏版)》请在金锄头文库上搜索。

1、C+程序设计目 录第第1章章 最简单的最简单的C+程序程序 第第2章章 类与对象类与对象 第第3章章 构造函数构造函数 第第4章章 引用引用 第第5章章 静态成员与友员静态成员与友员 第第6章章 继承继承 第第7章章 重载重载第第8章章 I/O流流 第第9章章 模板模板 第第10章章 Visual C+编程实例编程实例C+程序设计第第1章章 最简单的最简单的C+程序程序1.1 C和和C+ 1.2 一个简单的程序一个简单的程序1.3 C+程序的实现与运行环境程序的实现与运行环境C+程序设计1.1 C和和C+ 1.1.1C+的发展史C语言是20世纪80年代以来迅速推广使用的一种程序设计语言。它既具

2、有高级语言的特点,又具有低级语言的一些特点,能够很方便地实现汇编级的操作,目标程序效率较高。然而,C语言也存在一些缺陷,例如类型检查机制相对较弱,缺少支持代码重用的语言结构等,使得用C语言开发大程序比较困难。C+程序设计为了克服C语言存在的缺点,并保持C语言简洁、高效的特点,贝尔实验室的BjarneStroustrup博士及其同事于1983年开始对C语言进行改进和扩充,将“类”的概念引入了C语言,构成了最早的C+语言。发明C+的重要目标就是在保留C原有精华的基础上提供全面的面向对象的编程支持,使得程序的结构更加清晰,更容易维护和扩充,同时又不丧失其高效性。后来Stroustrup博士及其同事们

3、又为C+引进了运算符重载、引用、虚函数等许多特性,并使之更加精炼。C+程序设计C+支持面向对象的程序设计方法,很适合于大、中型软件的开发,从开发时间、费用到软件的可重用性、可扩充性、可维护性和可靠性等方面,都具有很大的优越性。C+程序设计1.1.2面向对象的程序设计1面向对象的程序设计的基本概念语言、PASCAL、FORTRAN等都是一种面向过程的程序设计。面向过程的程序设计的基本思想是将问题分成独立的功能模块,用过程或函数来实现,达到最终解决问题的目的。它采用函数来描述对数据结构的操作,但又将函数与其所操作的数据分离。作为对现实世界的抽象,函数和它所操作的数据是密切联系、相互依赖的。如果数据

4、结构发生改变,则必须改写相应的函数。某个函数的改动经常引起许多其它函数的改动,维护较为困难,特别是难以适应大型复杂软件系统开发的需要。C+程序设计面向对象程序设计从所处理的数据入手,并以数据为中心。它把现实世界的问题抽象为“类”的概念。类是对现实世界中的客观事物的抽象,是对具有共同属性和行为的一类事物的抽象描述,其中,共同属性被描述为类中的数据成员,共同行为被描述为类中的成员函数。例如:汽车是对各种汽车(如小轿车、大卡车、公共汽车等)的一个抽象,汽车的颜色、型号、发动机等是属性,而开动和转弯等是它的成员函数即是它的方法。C+程序设计面向对象的程序设计方法最基本的思想就是把客观世界看成一个个相对

5、独立而又相互联系的实体,称为对象。例如,一个桌子、一个气球都是一个对象。类是对象集合的抽象,规定了这些对象的公共属性和方法,对象是类的一个实例。例如,钢笔是一个类,具体到一支钢笔就是一个对象。C+程序设计2面向对象程序设计的基本特征面向对象程序设计具有封装、抽象、继承和多态性4种基本特征。(1)封装封装是把函数和数据封藏起来,把它看成一个有机整体。封装是面向对象的重要特征,首先它实现了数据隐藏,保护了对象的数据不被外界随意改变;其次它使对象成了相对独立的功能模块。2)抽象抽象就是忽略一个主题中与当前目标无关的那些方面,以便充分地注意与当前目标有关的方面。对象是具有特定属性的一个抽象。C+程序设

6、计3)继承自然界中的大部分事物之间都有很多共性,但也有不同。比如,四边形是一个类,而矩形与四边形有相同的性质,也有自已的属性。我们可以将事物之间的共性保留下来也就是继承,如矩形继承四边形这个类的公共属性,将不同的特性再定义。面向对象程序设计方法允许一个继承其它类(称为基类)的属性和方法,该类称为派生类。继承是类的层次结构之间共享数据和方法的机制,允许和鼓励类的重用。C+程序设计4)多态性多态性是指不同类的对象对同一消息作出不同的响应。例如,同样是加法,把两个时间加在一起和把两个整数加在一起的内涵是完全不同的。再比如,十字路口的交通灯,同样是灯,当红灯亮时,人们知道该停止;当绿灯亮时,人们知道该

7、通行。多态性包括参数多态性和包含多态性,它具有灵活、抽象、行为共享和代码共享的优势。C+程序设计1.2 一个简单的程序一个简单的程序 1.2.1一个简单的C+程序例编程,求从键盘上输入的两个浮点数的和。#includevoidmain()doublex,y;coutxy;doublez=x+y;coutx+y=zendl;执行该程序,屏幕上出现如下提示信息:Entertwodoublenumber:3.45671.3运行结果为:x+y=74.7C+程序设计1.2.2C+程序结构特点1.C+程序的组成C+程序由若干个文件组成,每个文件又由若干个函数组成。C+程序的若干个函数是相对独立的程序段。组

8、成一个程序的若干个函数中,有且仅有一个是主函数,其函数名为main,它可以有参数,也可以无参数。主函数只能存在于组成该程序的若干个文件中的一个。程序是用计算机语言对程序要完成任务(即功能)的描述。程序必须存储在文本文件中,称为源程序文件。对于C+,源程序文件约定的扩展名是CPP。文件名最好有一定的提示作用,能使人联想到程序的内容或功能。C+程序设计利用VisualC+进行Windows编程时,源程序代码主要可以分为类:(1)头文件(*.h)。头文件用于定义函数原型、类的声明等。(2)CPP文件(*.CPP)。CPP文件用于定义函数或类成员函数的实现。(3)资源文件(*.RC)。资源文件中定义的

9、是应用程序中所遇见的菜单、对话框、位图等资源,它是一个文本文件,可以被VC+集成编译环境可视化显示。另外,VC+还会产生一些工程管理文件,由于应用程序通常由许多源程序组成,因此,为了进行有效管理,VC+将它们以一种形式组织在一起,这种组织形式称为工程(Project),其扩展名为(*.dsp)C+程序设计2.预处理命令#include预处理命令是包含命令,其功能是把一个文本文件的内容包含到该命令处。1.2.1节的程序的第1行以#打头,表明该行是一个预处理命令;是命令的参数,它指出了要包含的那个文件的文件名是iostream.h。3.头文件以.h作为扩展名,包含有对象和函数说明的文件称为头文件。

10、在1.2.1节的程序中,用C+流完成输入、输出的操作,其中,cout是输出流对象,cin是输入流对象;v1v2.vn;C+程序设计其中,“的功能是从输入流中提取数据赋值给一个变量,称为提取操作符;v1,v2,.,vn都是变量。该语句的意思是,程序暂时中止执行,等待用户从键盘上输入数据。用户输入了所有的数据后,按回车键表示输入结束,程序将用户键入的数据存入各变量中,并继续执行下面的语句。C+的输出操作是由系统提供的标准输出流对象cout来完成的。它的格式为coutx=x;y=yendl;其中,“”称为插入运算符,x,y都是表达式,endl是换行符。这个语句的意思是,将表达式的值输出到屏幕上当前光

11、标位置处。在输出时可适当使用字符串作为提示信息。C+程序设计5.注释符在C+中,注解的形式有两种:一种以两个斜杠“/”起头,直到行末;一种是用斜线星号组合“/*”和“*/”括起的任意文字。后一种多用于注解篇幅多于一行的情况。编译系统不理会注释文字,因此注释文字可以是任意的。注释可使程序更容易理解,在编写程序时随时添加注释是一种良好的习惯。C+程序设计1.3 C+程序的实现与运行环境程序的实现与运行环境VisualC+(简称VC+)是美国Microsoft公司开发的MicrosoftVisualStudio的一部分,是一个使用广泛的C/C集成化开发环境。它成功地将面向对象和事件驱动编程概念联系起

12、来,并得到了很好的配合,使得编写Windows应用程序的过程变得简单、方便且代码量小。VC6.0集程序的代码编辑、编译、连接、调试于一体,给编程人员提供了一个完整、方便的开发界面和许多有效的辅助开发工具。C+程序设计VC+6.0的编辑环境包含了许多独立的组件,它们是:文本编辑器、资源编辑器、C/C+编译器、连接器、调试器、AppWizard、ClassWizard、源程序浏览器以及联机帮助。所有这些构件的功能都隐藏在VC+6.0的菜单和工具条中。通过该集成环境,程序员可以观察和控制整个开发进程。VC+6.0的主窗口可以分为如图1-1所示的几个部分。典型的Windows应用程序结构有控制台应用程

13、序,基于框架窗口的应用程序,基于对话框的应用程序和基于文档视图结构的应用程序4种。C+程序设计VC+既可用于管理基于Windows的应用项目,也可用于管理基于DOS的应用项目。基于DOS的应用系统也称为控制台应用系统,本书前9章的应用程序均为控制台应用程序。控制台应用程序结构简单,可以不使用MFC类库。本书的第10章将介绍基于Windows的编程,基于框架窗口以及基于对话框和单文档的应用程序。C+程序设计图1-1VC6.0的主窗口C+程序设计1.3.1VisualC+6.0常用功能介绍:1文件菜单文件菜单中共有14个菜单项,分成6组,组与组之间通过凹下去的横线分割开。第一组是基本文件操作;第二

14、组是工作区操作,工作区是VC+6.0中最大的一个处理单位,每个工作区包括多个工程,每个工程又包含若干个文件;第三组用于文件保存;第四组用于文件打印。2编辑菜单编辑菜单包含用于编辑和搜索的命令,这些命令与其它Windows应用程序中的同名命令具有相似的功能。C+程序设计3查看菜单查看菜单包含用于检查源代码和调试信息的命令项,它可以用来设置全屏方式显示窗口,或者打开工作区窗口、输出窗口和各种调试窗口。4插入菜单插入菜单可以创建新的类、资源、窗体并将它们插入到文档中;可以将文件作为文本插入到文件中,也可以添加新的ATL对象到项目中。C+程序设计5工程菜单工程菜单中的命令用于管理项目和工作区。可以选择

15、指定的项目作为工作区中的活动项目,也可以把文件、文件夹、数据链接以及可再用部件添加到项目中,还可以编辑和修改项目间的依赖关系。C+程序设计6编译菜单编译菜单中包括以下命令:(1)编译(Compile):此命令用于编译显示在源代码的编辑窗口中的源文件并检查源文件中是否有语法错误。在编译过程中若有警告或错误,则将在输出窗口中显示错误信息。可以向前或向后浏览输出窗口中的错误信息,然后按F4键在源代码编辑窗口中显示相应的代码行。(快捷键:【Ctrl+F7】)C+程序设计(2)构建(Build):用于创建项目的可执行目标文件(.exe或.dll),但不运行它。(3)重建全部(Buildall):重新编译

16、所有文件(包括资源文件),重新连接生成可执行的目标文件。(4)成批编译(BatchBuile):成批编译、连接不同项目或同一项目的不同设置。(5)清除(Clean):把编译、连接过程中所生成的中间文件和最终可执行的目标文件删除掉。C+程序设计(6)开始调试(StartDebug):在调试状态下运行程序,程序运行到一个断点处暂时停止。(7)执行(Execute):运行可执行目标文件,如果此文件比源代码旧,则首先编译项目,再运行新产生的可执行目标文件(快捷键【Ctrl+F5】)C+程序设计1.3.2VC+6.0控制台应用程序1应用项目的建立一个应用项目(Project)由若干个编译单元组成,而每个

17、编译单元由一个程序文件(扩展名是CPP)及与之相关的头文件(扩展名是)组成。在组成项目的所有单元中,必须有一个(也只能有一个)单元包含函数main()的定义,这个单元称为主单元,相应的程序文件称为主程序文件。一个简单的控制台应用系统可以只有一个单元,即主单元。通过编译,每个单元生成一个浮动程序文件(也称为目标程序文件,扩展名是OBJ)。通过链接这些浮动程序文件,整个系统生成一个惟一的可执行文件,扩展名是EXE。C+程序设计由若干个关系密切的项目构成一个工作区,工作区在建立时自动生成扩展名为DSW的工作区文件以及其它文件。建立一个控制台应用项目(名为li)的过程分为三个步。1)建立项目及工作区(

18、1) 启动Visual C+后,选择菜单命令“文件”“新建”,屏幕上出现新建对话框,其中包括文件、工程、工作区和其它文档4个卡片。一般当前卡片是工程,如果不是,则应点击“工程”标签,使之成为当前卡片,如图1-2所示。C+程序设计图1-2C+程序设计(2)选择Win32ConsoleApplication(32位控制台应用程序)。在窗口的右上方的“工程”处,输入要建立的项目的名称,在“位置”处输入工程所在的路径,然后按“确定”按钮。屏幕上出现图1-3所示的Win32ConsoleApplication-Stepof1界面。C+程序设计图1-3C+程序设计(3)图1-3是应用程序生成向导,提问要生

19、成的项目类型。选择“AnEmptyProject(空项目)”,然后按“完成”按钮。此时,屏幕上会出现图1-4所示的新建工程信息,检查无误后按“确定”按钮。2)建立主程序文件lyh.cpp再次选择“文件”菜单的“新建”选项,弹出图1-5所示的窗口。选择“文件”标签,从窗口中选择C+SourceFile(C+源程序),在窗口右侧“文件”处填写文件的名字lyh,在“目录”处输入该文件存放的路径,然后按“确定”按钮。弹出图1-6所示的窗口。C+程序设计图1-4C+程序设计图1-5C+程序设计图1-6C+程序设计3)输入程序如图1-6所示,在VC+6.0窗口右侧的文件编辑窗口中出现了一个空文件,在光标处

20、输入源程序的内容,如图1-7所示。C+程序设计图1-7C+程序设计2.程序的运行选择菜单命令“编译”中的“编译程序名.cpp”(或按F7键),编译源程序。稍后在Output窗口中会显示编译结果。如果编译出错,则重新返回编辑窗口,查找并改正错误后,继续编译直到没有编译错误。编译会产生一个目标文件(后缀为.obj)。目标文件还需通过连接生成可执行文件。选择菜单选项“编译”中的“编译文件名.exe连接目标文件。连接的结果显示在Output窗口中,它是一个可执行文件。C+程序设计1.3.3VC+6.0MFC应用程序Microsoft提 供 了 一 个 功 能 强 大 的 基 础 类 库MFC(Micr

21、osoftFoundationClass),其中包含许多用来开发C+应用程序的类。它包含基类、窗口、对话框和控制类以及绘图打印类等。VisualC+的MFC应用框架将编辑器、编译器、连接器、调试器、AppWizard、AppStudio和ClassWizard等工具集成在同一环境中,极大地方便了程序员开发Windows应用程序。本节使用AppWizard生成一个单文档。AppWizard是一个代码发生器,用于创建MFC应用程序。它按照用户通过对话框指定的特性、类名及源代码文件名来创建一个可启动的Windows应用程序框架。C+程序设计下面介绍使用AppWizard开发一个单文档程序的过程。(1

22、)在VC+6.0主窗口中选择菜单“文件”“新建”或按【Ctrl+N】键,弹出一个对话框,如图1-8所示。在对话框中选择“工程”标签,并用鼠标选中左边列表框中的MFCAppWizard【exe】。在有“位置”标记的编辑框中输入一个目录的名字,用于存放即将创建的程序的源代码,也可以按编辑框右面的“”按钮选择一个目录。在有“工程”标记的编辑框中输入程序的名字。(2)在图1-8中按“确定”按钮,弹出一个对话框,如图1-9所示。C+程序设计图1-8C+程序设计图1-9C+程序设计在图1-9中选择“S单个文档”单选框;图中显示有“中文中国(APPWZCHS.DLL)”的组合框是用来选择程序资源所支持的语言

23、的,默认为中文。然后按“下一个”按钮,出现图1-10所示的窗口。(3)对图1-10中的对话框不作任何改变,用鼠标单击“下一个”铵钮进入下一步,出现图1-11所示的窗口。(4)连续按“下一个”按钮,直到该按钮为灰色不能再按为止,便出现图1-12所示的窗口。C+程序设计图1-10C+程序设计图1-11C+程序设计图1-12C+程序设计(5)在图1-12中单击“完成”按钮,弹出图1-13所示的窗口,单击“确定”按钮,出现图1-14所示的窗口。至此,AppWizard已经产生了一个单文档程序的框架代码。(6)选择菜单“编译”“菜单项”,在弹出的对话框中选“是(Y)”按钮,稍后会看到VC+6.0下的输出

24、窗口中不停地显示出一些信息,最后产生图1-15所示的窗口,这就是由VC+6.0自动生成的单文档用户界面。C+程序设计图1-13C+程序设计图1-14C+程序设计图1-15C+程序设计第第2章章 类与对象类与对象2.1 类与对象的概念类与对象的概念2.2 类的定义类的定义 2.3 函数成员的实现函数成员的实现2.4 保护成员保护成员2.5 编程实例编程实例C+程序设计2.1 类与对象的概念类与对象的概念 客观世界中任何一个事物都可以看成一个对象,对象之间通过一定的渠道相互联系。例如,学校是一个对象,一个班级是一个对象,一个学生是一个对象,它们彼此有一定的联系。在现实生活中,人们往往在一个对象中进

25、行活动,即对象是进行活动的基本单位。比如一个班级要进行上课、开会、娱乐等活动。C+程序设计从计算机角度来看,一个对象应包括两个要素:一是数据(即活动主体),相当于班级中的学生;二是需要进行的操作,相当于学生进行的活动。对象就是一个包含数据以及与这些数据有关的操作的集合。每一个实体都是对象,有一些对象具有相同的结构和特性。例如,一个学校中的不同班级。具有相同的类型和特性,但专科学校和本科学校的班级却属于不同的类型。在C+中,对象的类型就称为“类”(class),即类代表了某一批对象的共性和特征。C+程序设计可以说,类是对某一类对象的抽象,而对象是某一种类的实例。在C+中,是先声明一个类类型,然后

26、用它去定义若干不同类型的对象,即对象就是一个类类型的变量,而类类型相当于产生对象的模板。C+是C的超集,最重要的就是增加了“类”这样一种数据类型,其性质和其它数据类型(如整型、实型、枚举类型、结构体类型)相同。C+程序设计对象和类都是面向对象的基本元素,而类是构成C+实现面向对象程序设计的核心和基础。在C语言与其它传统的过程化的语言中,通常没有类与对象的概念,这些语言所写的程序采用函数或过程描述对数据结构类型的操作,但又将函数及其操作的数据相分离,所写的程序完全依赖于数据的结构和形式。而C+语言通过类将数据结构和与之相关的函数操作封装在一起,形成一个整体,具有良好的外部接口,可以防止对数据结构

27、内部未经授权的访问,提高了程序模块之间的独立性。C+程序设计在C+中怎样声明一个“类”类型呢?类的声明语法与C语言中的struct(结构)声明类似。在C语言中结构可把相关联的数据元素组成一个单独的统一体,例如,一个学生(Student)结构:structStudentintnum;/学号floatsum;/总分;C+程序设计Student结构的每个实例(对象)可包含同样的两个数据元素。定义如下:voidabc()Studenta;Studentb;a.num=101;b.num=102;在C和C+中说明结构对象的语句分别为。C+程序设计StructStudenta;和Studenta;/关键字

28、struct不必要C的结构不含成员函数。C+的类既能包含数据成员,又能包含函数成员(或称成员函数),例如,下面的Student类包含两个数据成员,一个成员函数:classStudentpublic:floatfun(floatx)/成员函数C+程序设计sum+=x;returnsum;private:intnum;/数据成员floatsum;关键字class表示类,Student是类名,一般首字母用大写字母表示,以区别于对象。C+程序设计值得说明的是,函数体fun()中对数据成员sum的引用是在其说明之前进行的,即出现了“使用在前,说明在后”的问题。但类中的这种特殊情况在C+中是允许的,因为C

29、+的编译器先扫描类的数据成员的说明,之后再处理各种形式的成员函数的代码体。关键字public、private和protected(将在后面进行详细讲解)表示存储控制类型。在类中说明的,无论是数据成员,还是成员函数,它们从访问权限上均可分为这三种存储控制类型。C+程序设计紧跟在private之后的数据成员和成员函数是“私有的”,外部不能调用,而只有用public方式声明的成员才是“公有的”,即公开的,外界可以调用;protected成员同私有成员相似,不能被外部调用,但它可以被派生类的成员函数(将在后面进行详细讲解)调用。注意,在没有遇见下一次访问权限的声明之前,不必显式地声明是public还是

30、private访问权限,且它们出现的次数及顺序可以是任意的。例如:C+程序设计voidabc()Studenta; /定义类对象Studentb;a.sum=181.5;/错误,不能访问sum,因为它是私有成员b.sum=172.5;/错误a.fun(100); /正确,使sum赋值为100,因为fun是公共的,可间接地访问sumC+程序设计总之,类与结构的区别是:(1)在C+中,结构是用struct声明的,默认情况下其成员函数是公有(public)的,在需要显式声明时可以改变;(2) 在C+中,类和结构的惟一区别是:类(class)定义中默认情况下其成员是private访问权限,而结构(st

31、ruct)定义中默认情况下其成员是public访问权限。(3)在C中,结构中不允许有成员函数,而在C+中可以有成员函数。C+程序设计下面介绍一下面向对象设计中的几个名词。类中的成员函数称为“方法(method)”,一个“方法”对应一种操作。显然,只有声明为public的“方法”(成员函数),才能被外界对象所激活。外界是通过“消息”来激活有关方法的。所谓“消息”,其实就是一个命令,由程序语句来实现。例如,要输出对象stud1中的学生学号、姓名、性别等信息,可以在程序中写为stud1.display();/它就是对象stud1发出的一个“消息”,通知它执行display()“方法”对象之间只能通过

32、“消息”(C+中采用函数调用的形式)相互通信、相互依赖。C+程序设计一个对象只能通过调用另一个对象所提供的成员函数来访问其中的数据或提出其它的要求,而不必去了解其细节。当类的成员声明为保护时,外部不能访问;为公有时,则在任何地方都可以访问。总之,类和对象密切相关,相互不能脱离。面向对象的C+程序设计语言主要是通过仔细地定义类、构造对象来进行程序设计的,而主程序一般相对较简单。通过类和对象的学习,要求掌握声明和定义类和成员函数的方法,掌握访问成员函数的方法,理解保护数据如何屏蔽外部访问的原理,以便对类的封装有更好的认识。C+程序设计2.2 类类 的的 定定 义义 2.2.1类的概念类是一种复杂的

33、数据类型,它是将不同类型的数据和与之相关的操作封装在一起的集合体。在C+中类分别用一组数据成员和成员函数来实现,由此而形成的类是一个新的类型,用它可以说明一个个的实例,从而形成了模拟实际问题空间的对象。因此,类具有更高的抽象性,类中的数据具有隐藏性,类还具有封装性。C+程序设计2.2.2类的定义格式类的定义格式一般分为说明部分和实现部分。说明部分是用来说明该类中的成员,包含数据成员的说明和成员函数的说明。成员函数(即“方法”)是用来对数据成员进行操作的。实现部分用来对成员函数进行定义。概括地讲,说明部分将告诉使用者“干什么”,而实现部分告诉使用者“怎么干”。可见,使用者关心的往往是说明部分,而

34、实现部分是一些不必关心的信息。C+程序设计1.定义类的一般形式定义类的一般形式如下:class类名public:/公有数据成员和成员函数protected:/保护的数据和函数private:/私有数据成员和成员函数;/各个成员函数的实现C+程序设计说明:私有的数据成员和成员函数只允许本类的成员函数访问或调用;保护的数据成员和成员函数,允许本类和本类派生类的成员函数访问或调用;公有的数据成员和成员函数允许本类和其它类的函数访问或调用。其中,尽管C+没有要求,但按面向对象的观点,一般情况下,类设计鼓励程序员将数据设计在私有部分,而只将该类的接口操作放在公有部分。C+程序设计2.成员函数的实现的一般

35、形式成员函数的实现的一般形式如下:返回类型类名:成员函数名(形参表列)函数体;其中,“:”是C+中新引入的运算符,称为“作用域运算符”,“类名:”用来标识某个成员函数是属于哪个类的。如果省略“类名:”,定义的函数就成了一般的自由函数,由于不同的类可有相同的成员函数名,因此在定义成员函数时,必须指出类名。C+程序设计C语言规定每一个变量都有其有效的作用域,只能在变量的作用域内使用该变量。例如:floata=13.5;main()inta=5;coutaendl;C+程序设计程序中有两个a变量:一个是全局变量a,实型,另一个是main函数中的局部变量a。根据规定,在main函数中局部变量将屏蔽全局

36、变量,因此该程序输出将是局部变量a的值5。如果想输出全局实型变量的值,则要用C+提供的作用域运算符:。即上述程序加上语句cout:aendl;即可。注意,“:变量名”表示全局作用域中的某个变量,不能用“:”访问函数中的局部变量。C+程序设计下例考虑日期这一概念。它具有一组数据表示年、月、日,相关的一组方法是设置、显示日期等。用C+程序可以设计一个Date类来表示日期这个概念。例2.1日期类的说明classDatepublic:voidset(inty,intm,intd); /设置日期值C+程序设计intgetleap();/判返回值是否为闰年voidprint();/输出日期值private

37、:intday,month,year;;class是说明一个类的关键字,Date是该类的名字,类说明体中的day、month和year表示该类的数据成员,set()、getleap()、print()等函数表示该类的成员函数。与此相对应,在C+中不属于任何类的函数被称作非成员函数或自由函数。C+程序设计类的信息隐蔽表现在用public和private的声明上,关键字public和private是类成员的访问权限声明,紧跟在private之后的数据成员和成员函数是外部不可见的,而只有用public方式声明的成员才是可见的,且成员出现的次数及顺序可以是任意的。例如:classDatepublic:

38、voidset(int,int,int);intgetleap();C+程序设计public:voidprint();private:intday,month;private:intyear;这与例2.1定义的类中的成员的访问权限是一致的。例2.1仅给出了日期类的说明,日期类的实现见下例。C+程序设计例2.2日期类的实现。#includevoidDate:set(inty,intm,intd)day=d;month=m;year=y;intDate:getleap()C+程序设计intk;k=(year%4=0&year%100!=0)|(year%400=0);return(k);voidD

39、ate:print()coutmonth:day:yearendl;C+程序设计例2.3日期类的定义及实现。#includeclassDatepublic:voidset(intd,intm,inty)/设置日期值day=d;month=m;year=y;intgetleap()/判返回值是否为闰年C+程序设计intk;k=(year%4=0&year%100!=0)|(year%400=0);return(k);voidprint()/输出日期值coutmonth:day:yearendl;private:intday,month,year;C+程序设计voidmain()Datea;a.s

40、et(10,3,2002);a.print();说明:如果成员函数定义在类体内,它将与一般函数的定义一样,如果成员函数定义在类体外,则在函数头的前面要加上函数所属类的标识,这时要使用域标识符。C+程序设计注意:(1)在类体中不允许对所定义的数据成员进行初始化。例如,下面的定义是错误的:classDatepublic:private:intday(10),month(3),year(2002);/对数据成员不能进行初始化;C+程序设计(2)类中的数据成员可以是任意的类型,包括整型、浮点型、字符型、数组、指针和引用等,也可以是另一个类的对象(自身类的对象是不可以的,而自身类的指针或引用却可以)。此

41、外,当一个类的对象可以作为这个类的成员时,如果另一个类的定义在后,则需要提前声明,反之,则无需声明。例如:classN;/类N在类M之后定义,提前声明类NclassM;public:C+程序设计private:classNn;/n是N类的对象;classN;/此处无需提前声明类M,因类M本身在前面已定义public:voidf(Mm);/m是M类的对象;C+程序设计(3)通常,在类体内先说明公有成员,它们是用户关心的,后说明私有成员,它们是用户不感兴趣的。而且,在说明数据成员时,一般按数据成员的类型由小到大进行说明,这样可提高空间利用率。(4)习惯上,人们将类定义的说明部分或整个定义及实现部分

42、放在一个头文件中,如前面例子将类Date放在一个名为Date.h的头文件中,以方便后面引用。(5)成员函数可以定义在类体内,也可以定义在类体外,其区别将在后面详述。C+程序设计2.3 函数成员的实现函数成员的实现 2.3.1函数成员的定义在类的定义中规定将在类体中说明的函数作为类的成员,称为成员函数(也叫函数成员)。成员函数的实现不是类的一部分,通常在其它地方实现。如例2.3中,类定义中的getleap()成员函数在主函数中并未用到,但其定义仍放在类中。类定义是提供给更多不同用途的程序共享的,并不受单个程序应用的影响而“优化”,这是类定义的常识。C+程序设计函数set(int,int,int)

43、的全名是Date:set(int,int,int)。类名Date的作用是,指出set(int,int,int)是Date的一个成员函数,而不是其它类的成员函数,也不是普通函数。没有类名的函数也叫非成员函数,成员函数也叫“方法”,它多出现在面向对象方法论叙述中。数据成员month的全名为Data:month,注意“:”也可以不跟类名,这表示全局数据或全局函数(即非成员函数)。例如,在成员函数set()中调用非成员函数set(),程序如下:C+程序设计intday;intmonth;intyear;voidset(intd,intm,inty)/设置日期值:day=d;:month=m;:year

44、=y;classDateC+程序设计public:voidset(intd,intm,inty)/设置日期值:set(d,m,y);private:intday;intmonth;intyear;;C+程序设计2.3.2内联函数和外联函数类的成员函数分为内联函数和外联函数。内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内,而说明在类体内,定义在类体外的成员函数叫外联函数。外联函数的函数体在类的实现部分。C+程序设计引入内联函数的目的是为了解决程序中函数调用的效率问题。内联函数在调用时不是像一般函数那样要转去执行被调用函数的函数体,执行完成后再转回调用函数中执行其后语句,而是在

45、程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换。这样显然增加了目标程序代码量,但是却避免了转去转回的问题,是以目标代码的增加为代价来换取时间的节省,以提高运行效率。C+程序设计1.内联函数的定义方法使用内联函数的方法很简单,只要在函数定义的开头加上关键字inline即可。内联函数的定义方法与一般函数一样,例如:inlineintadd_int(intx,inty,intz)returnx+y+z;其中,inline是关键字,函数add_int()是一个内联函数。C+程序设计例2.4编程求110中各个数的平方。#includeinlineintpower_int(

46、intx)return(x)*(x);voidmain()for(inti=1;i=10;i+)C+程序设计intp=power_int(i);couti*i=pendl;说明:在该程序中,函数power_int()是一个内联函数,其特点是该函数在编译时被替代,而不是像一般函数那样是在运行时被调用。C+程序设计将内联函数与C中学过的带参数的宏定义进行一下比较可知,它们的代码效率是一样的,但是内联函数要优于宏定义。因为内联函数遵循函数的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,调试比较方便。C+程序设计2.使用内联函数的注意事项使用内联函数

47、时应注意以下几点:(1)在内联函数内不允许使用循环语句和switch(开关)语句;(2)内联函数的定义必须出现在内联函数第一次被调用之前;(3)后面提到的类结构中所有在类说明内部定义的函数都是内联(内嵌)函数。C+程序设计3.外联函数对于大的成员函数而言,直接把代码放在类定义中使用起来十分不便。为了避免这种情况,C+允许在类定义之外定义成员函数,这就是外联函数。外联函数变成内联函数的方法很简单,只要在函数开头加上关键字inline就可以了。C+程序设计例2.5下面程序说明定义两种内联函数的方法。classApublic:A(intx,inty)X=x;Y=y;inta()returnX;int

48、b()returnY;intc();intd();private;intX,Y;C+程序设计;inlineintA:c()returna()+b();inlineintA:d()returnc();#includevoidmain()C+程序设计Am(3,5);inti=m.d();coutd()return:iendl;运行结果如下:d()return:8说明:类A中,直接定义了3个内联函数,又使用inline定义了2个内联函数。内联函数一定要在调用之前进行定义,并且内联函数无法递归调用。C+程序设计4.使用外联函数的注意事项使用外联函数时应注意以下几点:(1)将类定义和其成员函数的定义分开

49、,是目前开发程序的通常作法。我们把类定义(头文件)看成是类的外部接口,把类的成员函数定义看成是类的内部实现。将类拿来编制应用程序时,只需类的外部接口(头文件)。这和我们使用标准函数库的道理一样,即只需包含某函数声明的头文件。因为类定义中,包含了类中全部成员函数的声明。C+程序设计(2)在类定义的外部定义成员函数,比在类内部定义时成员函数名前多加上一个“类名:”,否则,编译器就会认为该函数是一个普通函数,以后在连接时,系统会查出缺少与成员函数相对应的定义而报错。(3)在类定义内部定义成员函数时,类名是省略的(如例2.5)。好比某人在家的小名是“某某”,而出门叫“姓某某”一样。(4)类名要加在成员

50、函数名之前而不是加在函数的返回类型前。(5)类名不能加在成员函数定义的最开头。如:inlineA:intc()/或A:intc()/是错误的。C+程序设计2.3.3重载成员函数所谓函数重载,是指同一个函数名可以对应多个函数实现。例如,可以给函数名add()定义多个函数实现,如一个函数实现是求两个int型数之和,另一个实现是求两个浮点数之和,再一个实现是求两个复数之和。每种实现都对应一个函数体,这些函数的名字相同,但是函数的参数个数或类型不同。C+程序设计成员函数可以用与传统函数一样的方法进行重载,但由于类名是成员函数名的一部分,因此一个类的成员函数与另一个类的成员函数即使同名,也不能认为是重载

51、。例2.6下面的代码中定义了Student类的两个重载函数,Scope类的一个成员函数和一个普通函数,并在主函数中分别调用了它们。C+程序设计classstudentpublic:floatgrade()/floatgrade(floatabc)/;classScopepublic:floatgrade()/;C+程序设计chargrade(floatabc)/voidmain()Students;Scopet;s.grade(3.2);/Student:grade(float)floatv=s.grade(); /Student:grade()charc=grade(v);/:grade(f

52、loat)floatm=t.grade();/Scope:grade()C+程序设计2.3.4调用成员函数一个对象要表现其行为,就要调用它的成员函数,其调用形式类似于访问一个结构对象的分量,要先指明对象,再指明分量。即调用成员函数时必须指定对象和成员名,否则无意义,先看例2.7。例2.7下面的程序列出了数据成员和成员函数错误和正确的调用形式。/Date.h:类定义classDateC+程序设计public:voidset(int,int,int);/成员函数声明intgetleap();voidprint();private:intday;intmonth;intyear;;C+程序设计/Da

53、te.cpp:成员函数定义#include#includeData.hvoidDate:set(intd,intm,inty)day=d;month=m;year=y;C+程序设计intDate:getleap()return(year%4=0&year%100!=0)|(year%400=0);voidDate:print()coutmonth/day/yearendl;C+程序设计下面的代码是错误的调用成员函数形式。#include#includeData.hDates;/全局对象名为svoidfunc()month=10;/错误,month是成员还是对象Date:month=10;/错误

54、,month是Date类的哪个对象Date:set(2,3,2002);/错误,set对哪个对象操作;C+程序设计下面的代码是正确的调用成员函数形式:voidfunc()/普通函数Dateday;/创建对象day.set(2,3,2002);/调用其成员函数day.print();C+程序设计1.用指针调用成员函数对象可以由指针来引导。例如,下面的程序用指针引出对象的成员函数,该程序是一个多文件程序结构,工程1.prj包含两个源文件:/1.prjli2_8.cppDate.cppC+程序设计例2.8用指针调用成员函数。/li2_8.cpp#include#includeDate.hvoidfu

55、nca(Date*p)p-print(); /p是s对象的指针if(p-getleap()coutleapyearn;elsecoutnotleapyearn;C+程序设计voidmain()Dates;s.set(5,3,2002);funca(&s);/对象的地址传给指针运行结果为:3/5/2002notleapyearC+程序设计2.在成员函数中访问成员成员函数必须用对象来调用。但在成员函数内部,访问数据成员或成员函数无须如此。例2.9在成员函数内部访问数据成员。#include#includeDate.hvoidDate:set(intd,intm,inty)C+程序设计month=m

56、;/不能在day前加对象名day=d;year=y;intDate:getleap()return(year%4=0&year%100!=0)|(year%400=0);voidDate:print()coutmonth/day/year,所以:month=m;等价于this-month=m;或s.month=m。C+程序设计因此,无论数据成员被哪个对象调用,都可以通过成员函数获得的参数(显式的或隐式的)来判断数据成员属于哪个对象,这就是成员函数中访问成员无须对象名作前缀的原因。set()成员函数还可表示成代码。voidDate:set(intd,intm,inty)this-month=m;

57、this-day=d;this-year=y;但不可以表示成代码:voidDate:set(intd,intm,inty)C+程序设计s.month=m;/错误,s没有声明。s.day=d;s.year=y;因为成员函数是所有对象共享的代码,不是某一个对象所独占的,所以不能在成员函数内使用某个特定的对象。在编译期间,s对象由于没有在成员函数内部或文件作用域中声明而导致失败。C+程序设计3.关于this指针this指针是一个隐含于类的成员函数中的特殊指针,该指针是一个指向正在被某个成员函数操作的对象的指针。当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数。每次

58、成员函数存取数据成员时,则隐含使用this指针(通常不去显式地使用this指针)。同样也可以使用*this来标识调用该成员函数的对象。C+程序设计2.4 保保 护护 成成 员员类的成员声明为被保护的(protected)时,从类的外部(即指在普通函数或其它类的成员函数中)不能对它访问。也可以把成员声明为公有的,公有成员在任何地方都可以被访问。在类中设置保护屏障,不让外部访问,主要是由面向对象程序的目标决定的。面向对象程序的目标包括:C+程序设计(1)相对于外部函数(即指在普通函数或其它类的成员函数中)而言,保护类的内部数据不会被任意侵犯。好比电视机内部复杂的电路装配在机壳内部,属于保护成员,而

59、面板上的按钮属于公共成员函数。(2)使类对它本身内部实现的维护负责。因为只有类自己才能访问该类的保护数据。(3)限制类与外部世界的接口。把一个类分为两部分:一部分是公共的,另一部分是保护的。保护成员对于使用者来说是不可见的,也是不需了解的。了解使用一个具有有限接口的公共成员的类是很容易的。C+程序设计(4)减少类与其它代码的关联程度。类的功能是独立的,它不依赖于应用程序的运行环境,可以放到不同的应用程序中去使用。这样,便能够非常容易地用一个类去替换另一个类。(5)类的保护机制使得人们编制的应用程序更加可靠和易于维护。人们在编程时误用的保护成员的代码在编译时都无法通过。例2.10一个学生类Stu

60、dent具有3个功能:增加课程:voidAddcousre(inthours,floatgrade);返回当前平均成绩:floatgrade();返回本学期学时数:intHours()。C+程序设计Student的其余成员可声明为保护的,以便使其它函数的操作与学生事物相隔离。类代码以头文件形式保存实现如下:/Student.hclassStudentpublic:floatGrade()/取当前平均成绩returngpa;C+程序设计intHours()/取学时数returntermshours;voidAddcourse(inthours,floatgrade)/增加课时及成绩gpa=ter

61、mshoursgpa+grade*hours;/总分termshours+=hours; /调整学期学时数gpa/=termshours;/调整平均成绩C+程序设计protected:inttermshours;/学期学时数floatgpa;/平均成绩;又如,下面程序代码试图修改学生的成绩(程序中使用到上述类时,只需要将该类的头文件包含进来):#include#includestudent.hvoidmain()C+程序设计Students;s.gpa=3.5;/错误,因为非法访问了保护数据成员gpa,编译无法通过couts.gpaendl;/错误,原因同上再如下面程序代码试图维护学生的成绩:

62、#include#includestudent.hvoidmain()Students;C+程序设计couts.grade();couts.hours();floatgpa=s.grade();inthours=s.hours();gpa+=3;/修改局部变量gpa并不能使s对象中数据得以修改hours+=4.0;couts.gpaendl;/错误couts.hours()endl;C+程序设计以上代码想查看一下某学生原来的学分和成绩,然后添加一门课程的成绩,但是新建局部变量并不代表s对象中的学分和成绩,属使用错误。但若将上述代码做如下修改,可很好地运行。#include#include“st

63、udent.hvoidmain()Students;couts.grade();couts.hours()C+程序设计s.addcourse(3,4.0);couts.Grade()endl;couts.Hours()endl;C+程序设计说明:(1)应该养成类编制的书写习惯。类定义中总是以访问控制说明符(public、protected和private)开始,让人一目了然。(2)可以把类的成员声明作为私有的(private),使外部不能访问它们而起到保护作用。在一个类定义中,如果不写访问控制说明符,那么它就默认为是private的。(3)protected和private的区别,在类的继承中

64、才表现出来(详细内容见后面介绍)。C+程序设计2.5 编编 程程 实实 例例例2.11用字符串重新实现日期类。/date.h,thisfileisthedefinitionofclassDate.classDateprivate:charday2;charmonth2;charyear4;C+程序设计public:voidset(intd,intm,inty);voidget(int&d,int&m,int&y);voidnext()voidprint();/date.cpp,thisfileistheimplementationofclassDate.#include#includedate

65、.hvoidDate:set(intd,intminty)C+程序设计strcpy(day,inttochar(d);strcpy(month,inttochar(m);strcpy(year,inttochar(y);voidDate:get(int&d,int&m,int&y)d=chartoint(day);m=chartoint(month);y=chartoint(year);voidDate:next()C+程序设计intd,m,y;d=chartoint(day);m=chartoint(month);y=chartoint(year);d+;switch(m)case1:cas

66、e3:case5:case7:C+程序设计case8:case10:case12:if(d31)d=1;m+;if(m12)m=1;y+;C+程序设计break;case2:if(Y/4*4)=Y&(Y/100)*100!=Y)/(Y/4*4)=Y)if(d29)d=1;M+;elseif(d28)C+程序设计d=1;m+;break;case4:case6:case9:case11:if(d30)C+程序设计d=1;m+;break;day=inttochar(d);month=inttochar(m);year=inttochar(y);voidDate:print()coutday:mo

67、nth:yearendl;C+程序设计以上类的成员函数参数的定义并不影响这个类的外部特性,即修改类并不需要修改与之相关的程序。但如果将该类的定义修改为:classDateprivate:charday2;charmonth2;charyear4;C+程序设计public:voidset(char*d,char*m,char*y);voidget(char*&d,char*&m,char*&y);voidnext()voidprint();;并实现了该类的各成员函数,那么它修改了类外部接口的说明,这将引起与之相关的所有程序的变动。可见,类充分体现了封装性和信息隐蔽的原则。C+程序设计一般地,将类

68、的定义放在.h文件中,而将类中各成员函数的实现放在.cpp文件中。当某个程序文件中要用到该类时,只需用include嵌入该类的.h头文件即可。在设计头文件时最好对类进行注释,以帮助用户使用这个类。以后当类的实现发生了变化而接口不变时,只需修改相应类的.cpp文件,而不影响别的文件中的程序。C+程序设计注意:(1)不能在类的数据成员说明时初始化。例如,下面的说明是错误的:classDateprivate:intday=1;/错误(2)数据成员可以是enum位域及别的内部或用户定义的类型,甚至可以是一个类类型,但不能声明为auto、register或extern。C+程序设计(3)类不能定义自身实

69、例作为它的数据成员,即不能嵌套,但类可以包含指向自己实例的指针或引用。(4)类中的成员函数除属于一个类外,其它方面与自由函数均相同,例如,它可以重载,可以有缺省的参数,也可以是内置函数等。小结本章介绍了类与对象的概念,及类的定义,函数成员的实现,内联函数和外联函数的使用以及保护成员的使用等内容。定义一个类后,可以把该类名作为一种数据类型,定义其“变量”(对象)。通常程序利用点操作符(.)访问类的公共成员。C+程序设计一个类具有数据成员,还具有成员函数,通过成员函数可以对数据成员进行操作,并实现其它的功能。程序可以在类的内部或外部定义它的成员函数,在类的外部定义成员函数时,必须指出所属的类名,并

70、用全局作用域运算符(:)把类名和函数名连接起来。类的成员,包括数据和函数,都可以被说明为公有、保护或私有。公有成员可以在程序中任意被访问,而保护或私有成员只能被这个类型成员函数访问。C+程序设计类的成员函数分为内联函数和外联函数。内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内,而说明在类体内,定义在类体外的成员函数叫外联函数。外联函数的函数体在类的实现部分。把成员说明为保护类型后,使得类的使用者在使用它时,只关心接口,无须关心它的内部实现,既方便了使用,又保护了内部结构,这就是类的封装原理。含有类的程序结构,充分体现了类的封装和重用,使它更易为人们所接受。C+程序设计第第3

71、章章 构造函数构造函数3.1 对象的定义对象的定义 3.2 构造函数的使用构造函数的使用3.3 析构函数析构函数3.4 带参数的构造函数带参数的构造函数3.5 构造对象的顺序构造对象的顺序3.6 构造函数重载构造函数重载3.7 编程实例编程实例C+程序设计3.1 对对 象象 的的 定定 义义 3.1.1const类型声明C+中变量的命名规则与C语言相同,也是由变量类型、存贮类型、作用域和生存期等概念来描述。C+要求变量在使用前必须声明,且可在使用前的任何地方声明。在C+中,变量可声明的类型有和C一样的类型(void、int、short、char、long、float、double),此外,还新

72、增加了一些类型,const类型就是其中常用的类型之一,在此将详细介绍其使用情况。C+程序设计通常,一个变量被声明为const类型后,就只能被读,而且该变量必须在声明时定义。此后,在任何地方的修改都将会导致编译错误。由于const变量只能在初始化时候赋值,因此,下面的语句都是错误的:constinti;double*constpd;我们常常用const型的常量来代替用#define定义的常量,因为const型的常量要灵活的多,它可以创建有类型的常量,这是用#define定义的常量所无法比拟的。C+程序设计另外,在C+中,const可用于下面的代码:constintarraySize=6000;f

73、loatarrayarraySize;而ANSIC是不能接受这段代码的(ANSI即美国国家标准化协会)。在ANSIC中,const型的文件变量是全局变量,即除非声明为static存储类型,否则它将在声明之外的文件中可见。当含有const声明的头文件被一个以上的源文件所包含时,将会导致严重后果。C+的解决方案是将所有const对象的缺省存储类型定义为static.C+程序设计当用const来说明其它类型时将产生一些有趣的现象,例如:constchar*pc=“ABCD”;其中,pc是一个指向constchar*的指针。pc当然也可以用来指向其它对象,但它指向了某个对象,那么其指向的内容就不能改变

74、。例如:pc1=c;/错误pc=xyz;/正确然而,当我们这样来说明:char*constpc=ABCD;C+程序设计时,pc是一个const型的指针,它指向字符串“ABCD”。pc不能再指向其它对象,但pc所指向的内容却是可以变化的。例如:pc1=c;/正确pc=xyz;/错误下面是另一种组合形式:constfloatf;constfloat*constpf=&f;C+程序设计其中,pf是一个const类型的指针,它的指向内容也是const类型的,即它是一个指向const类型的const指针。pf既不能随便乱指,也不能改变它所指向的内容。例如:pf=3.14/错误pf=&x;/错误注意:在C

75、+中,类型的说明需使用从里向外读这一原则。应用这一原则,对组合类型的定义就比较容易理解了。const定义经常被应用于函数的参数说明中,建议读者在设计程序时,一定要将那些不应该在函数体中改变的参数设计成为const类型。C+程序设计在C+中,与常量有关的是用enum定义的枚举类型。枚举实际上定义了一组常量。例如:enumBoolerFALSE,TRUE该语句定义了由两个整型常量组成的枚举,并给它们依次赋了从0开始并逐一递增的缺省值,因此,上述声明等价于:constintFALSE=0;constintFALSE=1;与C不同的是,在C+中定义的枚举就像类型名一样可单独使用。例如:Boolerfl

76、ag;C+程序设计注意:枚举类型还可以在声明中显式地赋值。例如:enumtext_modesLASTMODE=-1,BW40=0,C40,BW80,C80,MONO=7,C4350=64;其中,C40、BW80、C80的值分别是1、2、3(从BW40的值逐一加起来的)。总结:如果const出现在“*”左边,指针指向的数据为常量;如果const出现在“*”右边,指针本身为常量;如果const在两边都出现,二者都是常量。C+程序设计3.1.2动态地分配和撤销内存空间在软件开发中,常常需要动态地分配和撤销内存空间,在C语言中是利用库函数malloc和free实现的,但是使用malloc函数时必须要指

77、定需要开辟的内存空间的大小,其调用形式是malloc(size)。C+中提供了较简便而功能较强的运算符new和delete来取代malloc和free函数(为了和C兼容,C+仍保留该两函数)。例如:newint;/开辟一个存放整数的空间,返回一个指向整形数据的指针C+程序设计newint(100);/开辟一个存放整数的空间,并指定其初值为100newchar10;/开辟一个存放字符数组的空间,该数组有10个元素,返回一个指向字符数据/的指针newint54;/开辟一个存放二维整形数组的空间,该数组大小为5*4float*p=newfloat(3.14159);/开辟一个存放实数的空间,并指定该

78、实数的初值为3.14159,将返C+程序设计/回的指向实型数据的指针赋给指针变量pnew运算符的一般格式为new类型初值delete运算符的一般格式为delete指针变量例如,要撤销上面用new开辟的实数空间(上面第五句),可用语句deletep。上面用newchar10开辟的空间,若把返回的指针赋给了指针变量pt,则可用语句deletept/在指针变量前加一对,表示对数组空间的操作C+程序设计例3.1开辟空间以存放一个结构体变量。#includestructStudentcharname10;intnum;charsex;main()C+程序设计Student*p;/声明一个结构体类型Stu

79、dent并定义指针变量p指向它p=newStudent;/用new开辟一段空间(其大小由系统自动算出)以存放一/个Student类型的变量strcpy(p-name,WangFun);/对各成员赋值p-num=10123;p-sex=M;deletep;/用delete撤销空间C+程序设计说明:(1)堆(heap)是内存空间。它是区别于栈区、全局数据区和代码区的另一个内存区域。堆允许程序在运行时(而非编译时),申请某个大小的内存空间。动态申请是指程序在运行时从堆的内存空间中动态申请一块内存空间。(2)new和delete是运算符,不需要包含在任何的.h文件中。(3)使用new比使用malloc

80、()更加简单和可靠,因为在每次进行内存申请时,它能自动计算要分配类型的大小,这样就简化了使用,避免了错误。C+程序设计(4)new操作符被执行后,如果有足够的内存空间,则new按要求分配一块内存,并返回指向该内存起始地址的指针;如果内存空间不够,则返回NULL。(5)用new动态申请空间时,以下的情况可能蕴含着错误。int*ip=newint0;此时的返回值是一个非零值,但当真正使用该指针时会出现错误。如果改用malloc()函数,即int*ip=(int*)malloc(0),则返回一个NULL,这是C+的不足之一。C+程序设计(6)malloc()函数和new操作实现的方法不同,可能采用不

81、同的内存管理技术,因此,不要混合使用,以免引起混乱。动态分配管理要求,有new申请的堆存储空间必须用delete操作释放。C+中用的new与其它动态申请空间的管理机制不同。因此,用delete释放由其它方法(如用malloc()函数)动态申请的空间将会导致严重的错误,并且,这种错误C+编译器是无法检查出来的,只有到动态运行时才会产生,它有很大的隐蔽性,特提请注意。C+程序设计(7)使用delete释放动态空间,比free()函数简单得多,例如:int*p;p=newint;deletep;p=newint(9); /用new可初始化deletep;p=newint9; /申请一个动态数组。de

82、letep;C+程序设计(8)注意用delete释放由new申请的动态指针数组的情况:char*p10;p0=newchar256;p1=newchar180;delete10p;其中,语句delete10p;可简写为deletep;但不能写成deletep,因为它表示只释放p本身所占据的空间,而没有释放p中每个元素所指向的空间。C+程序设计3.1.3对象的定义1.对象的定义对象(object)是类的实例(instance),是属于某个已知的类的,好比你、我、他都属于“人”这个类的实例,都被称为对象,都有“人类”的某些特征,如身高、体重、文化程度、性别、年龄、民族等。因此,定义对象前一定要先定

83、义好该对象的类。1)对象定义的格式对象定义的格式为;C+程序设计其中,是待定的对象所属的类的名字;中可以有一到多个对象,可以是一般的对象、指向对象的指针或引用名、对象数组名等,多个对象之间以“,”分隔。如Datad1,d2,*pdate,data31;2)对象的作用域和生存期我们知道,变量有作用域和生存期以及全局变量、静态变量和内部变量之分。与变量类似,对象也有全局对象、静态对象和内部对象之分。C+程序设计从面向对象和数据封装的角度出发,应尽量避免使用全局变量和全局对象,因为这不符合数据封装的原则。静态变量和静态对象有较长的生存期,该生存期在程序开始运行时开始,到程序结束时结束。这意味着它们将

84、较长时间地占用内存,因此也应尽量少用。我们较多使用的是内部对象。把对象定义在函数内部(或定义在程序块内部),当函数被调用时,对象的生存期才开始;当函数返回(即函数运行结束)时,对象生存期结束,这时对象所占用的内存被自动释放。C+程序设计深入一点来讲,内部对象是建立在栈(Stack)中的。若干年以前,PC机主要使用DOS操作系统时,对栈的大小有严格的限制,当时,程序员不敢将太多的东西放在栈中,以避免栈的溢出。采用32位Windows以后,已基本上不再担心栈的大小问题,一般情况可以放心使用。3)子对象当一个类的成员是另一个类的对象时,可称之为子对象。子对象实际上就是成员对象。它的作用域和生存期同成

85、员变量。C+程序设计假设A是一个已定义的类。在定义类B时,类B的数据成员中有类A的对象,则该对象即类B对象的子对象。如果类A的构造函数有参数,那么,在类B的构造函数中,要包含子对象的初始化。4)堆对象利用指针和运算符new可以把对象建立在堆(heap)中,可以在程序中随时创建(动态创建),当该对象不再使用时,用运算符delete可以删除它,同时释放内存。堆对象的生存期是从new开始,到delete结束。C+程序设计如果在构造函数中使用了new,在析构函数中应使用delete释放内存。通常,属于不同类的不同对象在不同的时刻、不同的地方分别被建立。如:全局对象在主函数开始执行前被创建,而局部对象在

86、程序执行过程中遇到它们的对象定义时才被创建。与定义变量类似,在定义对象时C+也要为其分配一定的存储空间。C+程序设计例3.2下面的代码定义了两个类,创建了类的全局对象、局部对象、静态对象和堆对象。classDesk/定义Desk类public:intweight;intheight;intwidth;intlength;C+程序设计classTool/定义Tool类public:intweight;intheight;intwidth;intlength;Daskaa;/全局对象Toolbb;C+程序设计voidfn()staticToolcc;/静态局部对象Deskaa;/局部对象voidm

87、ain()Tooldd;/局部对象Desk*pp=newDesk;/堆对象C+程序设计Desknn20;/局部对象数组deletepp;/释放堆对象C+程序设计2.对象成员的表示方法(1)一个对象的成员就是该对象的类所定义的成员。对象成员包括数据成员和成员函数。一般对象的成员表示为:./用来表示数据成员或.()/用来表示成员函数其中,“.”是一个运算符,表示对象的成员。C+程序设计例如,设Date是一个已定义的类,那么Dated1;定义了该类的一个对象d1。如果该类有成员变量year、month和 day, 那 么 , d1的 成 员 可 表 示 为 d1.year、d1.month和d1.d

88、ay。又如,d1.setdata(intd,intm,inty)表示Data类的d1对象的成员函数setdate()。(2)平共处指向对象的指针的成员表示为:-/用来表示数据成员或-()/用来表示成员函数C+程序设计其中,“-”是一个表示成员的运算符,它与“.”的区别是:前者用来表示指向对象的指针的成员,而后者用来表示一般对象的成员。以上表示和下面的形式等价:(*对象指针名.或(*对象指针名.()C+程序设计例如,pdate的成员表示可为:pdate-year,pdate-month,pdate-day或(*pdate).year,(*pdate).month,(*pdate).day用指向对

89、象的指针pdate表示它的setdate()成员函数如下:pdate-setdate(intd,intm,inty)或(*pdate).setdate(intd,intm,inty)此外,引用对象的成员表示与一般对象的成员表示相同。C+程序设计例3.3对象成员的引用。#include#includeDate.hvoidmain()Datedate1,date2;date1.setdata(5,4,2000);date2.setdate(4,3,2002);intleap=date1.getleap();coutleapendl;C+程序设计date1.print();date2.print()

90、;执行该程序后,输出结果如下:15/4/20024/3/2002C+程序设计分析:该程序在主函数中定义了对象date1和date2,并通过成员函数setdate()给对象date1和date2赋值。程序中,通过成员函数getleap()判断对象date1的年份(2000)是否是闰年,本例输出1表示2000年是闰年。最后,通过调用成员函数print()输出显示对象date1和date2的数据成员的值,即年、月、日。C+程序设计3.对象的初始化前面介绍了通过定义成员函数的方法给对象的数据成员赋值,下面介绍对对象进行初始化。C+建立和初始化对象的过程专门由该类的构造函数来完成。该构造函数很特殊,只要

91、对象建立,它马上被调用,并为对象分配空间和初始化。例如,一旦打造好一张桌子,它就应有长、宽、高和重量,因此,在桌子对象建立时,构造函数的任务就是给该桌子对象赋予一组值。如果一个类没有专门定义构造函数,那么C+就仅仅创建对象而不对对象进行初始化。C+程序设计C+另有一种析构函数,它也是类的成员函数,当对象撤消时,就会马上被调用,其作用是进行善后处理。例如,一张桌子要扔掉,需拿出其中所放的东西,而不能随桌子一起扔掉,类似这些事情由析构函数完成。变量初始化的方法如下所示:inta=1;int*pa=&a;数组初始化的方法如下所示:intb=1,2,3,4;结构初始化的方法如下所示:C+程序设计str

92、uctStudentintsemrsHours;/总学时数floatgpa;/平均成绩;voidfn()students=100,3.5;/创建结构变量时,初始化C+程序设计但是,对类对象来说,其变量、数组、结构不能这样初始化,这是由类的特殊性所决定的。例如,下面的代码企图在对象创建时,对其进行初始化:classStudentpudlic:/.公共成员.protected:intsemeHours;/此处的数据成员是受保护的floatgpa;C+程序设计floatgpa;voidfn()students=100,3.5;/错误,不能访问如果上面的函数允许那样初始化的话,就意味着在函数中,允许:

93、voidfn()s.semesHour=0;s.gpa=0;C+程序设计类的封装性就体现在一部分数据是不能让外界访问的,所以直接在非成员函数中访问类对象的保护或私有数据是不允许的。因此,类对象的初始化任务,自然就落在了类的成员函数身上,因为它们可以访问保护和私有数据成员。于是将初始化构想成下面的形式:classStudentpublic:voidinit()semesHours=100;gpa=3.5;C+程序设计/.其它公共成员protected:intsemesHours;intgpa;voidfn()students;s.init();/类的初始化/函数的其它部分C+程序设计类的保护数据

94、在外界是不能访问的,因此对这些数据的维护是类的份内工作。在上面的程序中将初始化工作交由init()成员函数无可非议,但系统中多了处理初始化的过程,因为这意味着在编写应用程序过程中每当建立对象时,都要人为增加书写代码,这样实现的类机制并不理想。我们要求建立对象的同时,自动调用构造函数,省去人为调用的麻烦。也就是说,类对象的定义studentss;明确表达了为对象分配空间和初始化的意思,这些工作由构造函数来完成。C+程序设计3.2 构造函数的使用构造函数的使用C+规定与类同名的成员函数是构造函数(constructor),构造函数由用户创建,且在该类的对象创建时自动被调用。例3.4下面的代码用于实

95、现初始化一个类对象,输出学生的学号、姓名和性别。#include#includeC+程序设计voidmain()classStud/声明一个类private:/私有部分intnum;charname10;charsex;public: /公有部分stud() /构造函数定义,函数名与类名相同C+程序设计num=10010;/给数据赋初值strcpy(name,Wang_Min);sex=F;voiddisplay()/定义成员函数,输出对象的数据coutnum:numendl;coutname:nameendl;coutsex:sexendl;C+程序设计;studstud1;/定义对象stu

96、d1时自动执行构造函数stud1.display();/从对象外面调用display()函数注意:构造函数不需要用户调用,而是在定义一个对象时由系统自动执行,而且只能执行一次。构造函数一般声明为public,无返回值,也不需加void类型声明。C+程序设计说明:从程序可以看出整个程序很简单,它包括三部分:声明一个类;定义一个对象;向对象发出消息,执行对象中的成员函数display()。在定义stud1对象时自动执行了构造函数stud(),因此对象中的数据成员均被赋了值。执行display()函数时输出以下信息:num:10010name:Wang_Minsex:F另外,在程序中可以看到,只有对

97、象中的函数才能引用本对象中的数据。如果在对象外面直接用coutstud1.num;C+程序设计企图输出学生的学号是不行的。由此可体会到类的特点。如果要建立两个对象并分别为数据赋予初值,就不能如上定义构造函数stud了,因为它会使两个学生的初值相同。例如,姓名都是Wang_Min的两个对象的初值会相同,而实际上他们应该被赋予不同的初值。可将构造函数修改如下:stud(intn,charnam,chars)num=n;strcpy(name,nam);sex=s;C+程序设计此时,数据的值不由构造函数stud确定,而是在调用此函数时由实参传来。但应注意构造函数不同于一般的成员函数,不能这样调用:s

98、tud1.stud(10010,Wang_Min,F);/企图用调用一般成员函数的方法来调用构造函数前已说明构造函数是在建立对象时调用的,因此实参应该在建立对象时给出。如:studstud1(10010,Wang_Min,F),stud2(10011,Zang_Fun,M);该语句定义了两个对象stud1和stud2,它们的数据初值是不同的。如果分别输出两个学生的数据,可以用以下的语句:C+程序设计stud1.display();stud2.display();此时的输出如下:num:10010name:Wang_Minsex:Fnum:10011name:Zang_Funsex:M构造函数也

99、可以放在类的外部定义,读者可以试着改写。C+程序设计说明:(1)放在类的外部定义的构造函数,其函数名前要加上“类名:”,这和其它成员函数的定义方法一样。因为在类定义的外部,可能有各种函数定义,为了区分成员与非成员函数,以及此类成员函数与彼类成员函数,加上“类名:”是必要的。(2)构造函数另一特殊之处是它没有返回类型,函数体中也不允许返回值,但可以有无值返回语句“return;”,因为构造函数专门用于创建对象并将其初始化,所以它不能随意被调用。下面代码是在类定义的外部定义一个构造函数,但却错误地加上了返回类型。C+程序设计classDeskpublic:Desk(); /构造函数声明protec

100、ted:intweight;intheight;intwidth;intlength;C+程序设计voidDesk:Desk()/错误,不能有返回类型weight=10;height=5;width=5;length=15;C+程序设计(3)如果类A对象是类B的数据成员,则在类B的对象创建时会自动调用类A的构造函数。构造函数的特点总结如下:(1)构造函数是成员函数,函数体可写在类体内,也可写在类体外。(2)构造函数是一个特殊的函数,该函数的名字与类名相同;该函数不指定类型说明,它有隐含的返回值,该值由系统内部使用;该函数可以不带参数,也可以有一个或多个参数(详见3.4)。C+程序设计(3)构造

101、函数可以重载,即可以定义多个参数不同的函数。(4)程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数。C+程序设计3.3 析析 构构 函函 数数3.3.1析构函数和构造函数的比较构造函数的主要功能是在创建对象时,对对象作初始化操作。而析构函数(destructor)与构造函数相反,是在对象被删除(即脱离作用域)前由系统自动执行它做清理工作,比如,在建立对象时用new分配的内存空间,应在析构函数中用delete释放。C+程序设计构造函数的函数名与类名相同,可以有参数,也可以重载,但没有函数类型,也不得在return后面放返回值。而析构函数也是一种特殊的类成员函数,其函数名是在类名前面加

102、上一逻辑非运算符“”符号。同样,析构函数无函数类型,无参数,无返回值(包括void类型),一个类中只能有一个,不能随意调用,也不能重载。作为一个类,可能有许多对象,每当对象生命结束时,都要调用析构函数,且每个对象调用一次。这跟构造函数形成了鲜明的对比,因此在析构函数名前加上一“”运算符,表示“逆构造函数”。C+程序设计下面的代码定义了一个析构函数:classabcpublic:abc()name=newchar20;/分配堆空间abc()C+程序设计deletename; /释放堆空间protected:char*name;在abc类的构造函数中分配了一段堆内存给作为指针的name数据成员。一

103、旦对象创建,该对象就在对象空间之外拥有了一段堆内存资源。对应地,当对象撤消时,首先必须归还这一内存资源,这个工作由析构函数来做。C+程序设计例3.5下面的程序中有一个“帮教派对”类Tpair,其中包含学生类对象和老师类对象。#includeclassStudent/类的声明放在main()前。说明其作用是全局的,可使main()简练public:Student()coutconstructingstudent.n;C+程序设计semesHours=100;gpa=3.5;Student()coutdestructingstudent.n;protected:intsemesHours;floa

104、tgpa;C+程序设计classTeacherpublic:Teacher()coutconstructingteacher.n;Teacher()coutdestructingteacher.n;C+程序设计classTpairpublic:TPair()coutconstructingtutorpair.n;numMeetings=0;TPair()coutdestructingtutorpair.n;C+程序设计protected:Studentstudent;Teacherteacher;intnumMeetings;voidmain()TPairtp;coutbackinmain.n

105、;C+程序设计运行结果为:constructingstudent.constructingteacher.constructingtutorpair.Backinmain.destructingtutorpair.destructingteacher.destructingstudent.当主函数运行到结束的时,析构函数依次被调用,其顺序正好与调用构造函数相反。C+程序设计析构函数特点总结如下:(1)析构函数是成员函数,函数体可写在类体内,也可写在类体外。(2)析构函数也是一个特殊的函数,它的名字同类名,并在前面加一“”字符,用来与构造函数加以区别。析构函数不指定数据类型,并且也没有参数和返回

106、值。(3)一个类中只能定义一个析构函数,析构函数没有重载。(4)析构函数不能被显式调用,因此许多简单的类中没有显式的析构函数。在下面两种情况下,析构函数会被自动调用:C+程序设计l如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用;l如果一个对象是使用new运算符被创建的,则在使用delete运算符释放它时,delete将会自动调用析构函数。C+程序设计3.3.2缺省构造函数和缺省析构函数C+中规定,每个类必须有一个构造函数,没有构造函数就不能创建任何对象。但当程序中未定类的构造函数时,C+编译器会自动生成一个不带参数缺省的构造函数,其格式如下::()按构造函数的

107、规定,缺省构造函数名同类名。缺省构造函数的这种格式也可以由程序员根据需要在类体中自己定义。C+程序设计如果在程序中定义一个对象时没有指明初始化参数,则编译器便按缺省的构造函数来初始化该对象。例如:在点类Point的程序中有如下说明语句:Pointp1,p2;则编译系统,便会用缺省构造函数对p1和p2进行初始化。用缺省构造函数对对象进行初始化时,会将对象的所有数据成员都初始化为0或NULL。C+程序设计同样,当一个类中没有定义析构函数时,C+编译器也会自动生成一个不带参数缺省的析构函数,其格式如下::()C+程序设计3.3.3拷贝初始化构造函数拷贝初始化构造函数是一种特殊的成员函数,其功能是用一

108、个已知的对象来初始化一个被创建的同类的对象。拷贝初始化构造函数实际上也是一种构造函数,它在初始化时被调用来将一个已知的对象的数据成员的值拷贝给正在创建的另一个同类的对象。拷贝初始化构造函数的特点如下:(1)该函数名同类名,因为它也是一种构造函数,并且该函数不被指定返回类型;(2)该函数只有一个参数,并且是对某个对象的引用(详见第四章);C+程序设计(3)每个类都必须有一个拷贝初始化构造函数,其格式如下::(const&)其中,与该类名相同,const是一个类型修饰符,被它修饰的对象是一个不能被更改的常量。如果类中没有说明拷贝初始化构造函数,则编译系统自动生成一个具有上述形式的缺省拷贝初始化构造

109、函数,作为该类的公有成员。C+程序设计例3.6定义点类Point。/*point.h*/classPointpublic:Point(intx,inty)X=x;Y=y;Point(Point&p);Point()coutDestructorCalled.n;intXcoordreturnX;intYcoordreturnY;C+程序设计private:intX,Y;Point:Point(Point&p)X=p.X;Y=p.Y;coutCopy_initializationConstructorCalled.n;将上面定义的Point类存放在名为point1.h的头文件中。在上述Point类

110、体中增加了一个拷贝初始化构造函数,并在类体外给出了定义。C+程序设计例3.7分析下面程序的输出结果。#include#includepoint.hvoidmain()PointP1(5,7);PointP2(P1);coutP2=P2.Xcoord(),P2.Ycoord()endl;C+程序设计执行该程序后,输出结果如下:Copy_initializationConstructorCalled.P2=5,7DestructorCalled.DestructorCalled.说明:从输出结果中可以看出,该程序中调用过一次拷贝初始化构造函数,用来给对象P2赋值。程序中还在退出程序前系统在释放对象

111、P1和P2时自动调用了两次析构函数。关于拷贝初始化构造函数的其它用法,可从下例中看出。C+程序设计例3.8分析下面程序的输出结果。#include#includepoint1.hPointf(PointQ);voidmain()PointM(20,35),P(0,0);PointN(M);P=f(N);coutP=P.Xcoord(),P.Ycoord()endl;C+程序设计Pointf(PointQ)coutokn;intx,y;x=Q.Xcoord()+10;y=Q.Ycoord()+20;PointR(x,y);returnR;C+程序设计执行该程序后,输出如下结果:Copy_init

112、ializationConstructorCalled.Copy_initializationConstructorCalled.okP=30,55DestructorCalled.DestructorCalled.DestructorCalled.C+程序设计说明:从上述程序的输出结果中可以看出,共使用了三次拷贝初始化构造函数。第一次是在执行PointN(M);语句时,对对象N进行初始化时;第二次是在调用f()函数时,实参N给形参对象Q进行初始化,即实参向形参传递值时;第三次是在执行f()函数时的返回语句returnR时,系统用返回值初始化一个匿名对象时使用了拷贝初始化构造函数。关于析构函数

113、的自动调用问题,这里做如下说明:在退出f()函数时,该函数中定义的对象将被释放,系统将自动调用其析构函数。这里共调用了两次:一次是用来释放对象Q,另一次用来释放对象R。C+程序设计在返回主函数main()后,用赋值运算符将其匿名对象的值赋给对象P,然后释放匿名对象,这时又调用一次析构函数。最后退出整个程序时,又调用3次析构函数,分别是用来释放主函数中定义的对象M、P和N的。因此,总共调用6次析构函数通过对例3.7和例3.8的分析可以看出,拷贝初始化构造函数的功能就是用一个已知的对象来初始化另一个对象。在下述情况下,需要调用拷贝初始化构造函数来用一个对象初始化另一个对象:C+程序设计(1)明确表

114、示由一个对象初始化另一个对象时。如在例3.7中的,ain()函数中,语句PointP2(P1);便属于这种情况,这时需要调用拷贝初始化构造函数。(2)当对象作为函数实参传递给函数形参时。如在例3.8的main()函数中,语句P=f(N);便属于这种情况。在调用f()函数时,对象N是实参,要用它来初始化被调用函数的形参Q时需要调用拷贝初始化构造函数。(3)当对象作为函数返回值时。如在例3.8的f()函数中,语句returnR;便属于这种情况。执行返回语句returnR;时,系统将用对象R来初始化一个匿名对象,这时需要调用拷贝初始化构造函数。C+程序设计3.4 带参数的构造函数带参数的构造函数前面

115、介绍的构造函数不带参数,不能完全满足初始化的要求。应该让构造函数可以带参数,否则,往往只能先将对象构造成千篇一律的对象值,甚至一个随机值对象,然后调用一个初始化成员函数将数据存到该对象中去。因此,让构造函数可以带一个或多个参数,能使初始化一步到位。C+程序设计例3.9带参数的构造函数(修改前面定义过的日期类,并将其放在Date.h文件中)。classDate1public:Date1(intm,intd,inty);Date1();voidprint();private:intyear,month,day;C+程序设计Date1:Date1(intm,intd,inty)year=y;mont

116、h=m;day=d;coutconstructorcalled.n;Date1:Date1()coutdestructorcalled.n;voidDate1:print()C+程序设计coutmonth.day.yearendl;#include#includeDate.hvoidmain()Date1today(4,9,2002),tomorrow(4,10,2002);couttodayis;today.print();couttomorrowis;C+程序设计tomorrow.print();执行该程序后,输出结果如下:constructorcalled.constructorcall

117、ed.todayis4,9,2002tomorrowis4,102002destructorcalled.destructorcalled.C+程序设计说明:在该程序中,定义了两个对象,并对它们进行了初始化,初始化是由构造函数实现的,而构造函数又是自动调用的。从输出结果中看出程序共调用了两次构造函数,因为在构造函数体中输出了字符串“constructorcalled.”。析构函数在程序中也是自动调用的,且在主函数结束时,调用了两次析构函数,因为输出了两次字符串“destructorcalled.“。C+程序设计3.5 构造对象的顺序构造对象的顺序 在一个大程序中,各种作用域的对象很多,有些对象

118、包含在别的对象里面,有些对象早在主函数开始运行之前就已经建立。创建对象的惟一途径是调用构造函数。构造函数是一段程序,;因此,构造对象的先后顺序不同,直接影响程序执行的先后顺序,导致不同的运行结果。C+给构造对象的顺序作了专门的规定:C+程序设计(1)局部对象和静态对象以声明的顺序构造;(2)局部对象和静态对象是指块作用域和文件作用域的对象,它们声明的顺序与它们在程序中出现的顺序是一样的。例3.10下面程序使用了goto语句,企图绕过变量定义.#includevoidmain()intm=5;if(m=5)gotoabc;intn=0;C+程序设计abc:coutm=m,n=nendl;/n已有

119、定义运行结果为:m=5,n=0程序中并不是根据运行顺序来决定变量定义的顺序,而是所有的变量和对象都在函数开始执行时统一定义。统一定义的顺序正是这些变量和对象在函数中出现的顺序。C+程序设计1.静态对象只被构造一次静态对象和静态变量一样,文件作用域的静态对象在主函数开始前全部构造完毕,块作用域的静态对象则在首次进入到定义该静态对象的函数时进行构造。例3.11下面程序在一个函数中定义了一个静态对象。#include#includeclassSmallpublic:C+程序设计Small(intsma)coutSmallconstructingwithavalueofsmaendl;voidfn(i

120、ntn)staticSmallsm(n);coutInfunctionfnwithn=nendl;voidmain()C+程序设计fn(10);fn(20);运行结果为:Smallconstructingwithavalueof10Infunctionfnwithn=10Infunctionfnwithn=20C+程序设计2.所有全局对象都在主函数main()之前被构造和全局变量一样,所有全局对象在主函数开始前。已全部被构造,如果它们中的一个有致命错误,那么可能永远也得不到控制权。此时,该程序在开始执行之前就已死机。有两种方法可以解决这个问题:一是先将全局对象作为局部对象来调试;二是在所有怀疑

121、有错的构造函数的开头增加输出语句,这样,在程序开始调试时,就可以看到来自这些对象的输出信息。C+程序设计3.全局对象构造时无特殊顺序全局对象不象局部对象的构造顺序那么简单。对于简单的单文件程序来说,全局对象可以按照它们出现的顺序依次来进行构造。但是,实际中真正有用的程序都是由多个文件组成的,这些文件被分别编译和连接。因为编译器不能控制文件的连接顺序,所以它不能决定不同文件中全局对象之间的构造顺序。C+程序设计例3.12下面的程序代码是一个多文件程序结构,创建了两个全局对象。/*stud.h*/classStudpublic:Studintd:id(d)protected:constintid;

122、classTutarC+程序设计public:Tutar(Stud&s)id=s.id;protected:intid;/file1.cppStudra(1234);/建立一个Stud全局对象/file2.cppTutorje(ra);/建立一个Tutor全局对象,以使辅导教师能帮助学生C+程序设计4.成员以其在类中声明的顺序构造例3.13下面的代码在构造函数头的冒号后初始化两个成员,但是却并不如意。#includeclassApublic:A(intj):age(j),num(age+1)coutage:age,num:numendl;C+程序设计protected:intnum;intag

123、e;voidmain()Asa(15);运行结果为:age:15,num:2C+程序设计3.6 构造函数重载构造函数重载 构造函数重载是C+应用最多的函数重载。C+允许为一个类定义多个构造函数,除了保证类数据成员的有效性以外,多构造函数为用不同形式初始化对象带来了极大的方便。例3.14时间类的构造函数重载。C+程序设计要求:定义一个用时、分、秒计时的时间类Time。在创建Time类对象时,可以用不带参数的构造函数将时、分、秒初始化为0值;可以用任意正整数为时、分、秒赋初值;可以用大于0的任意秒值为Time对象赋初值,还可以用“hh:mm:ss”形式的字符串为时、分、秒赋初值。#include#

124、includeclassTimeC+程序设计inthh,mm,ss;public:Time();Time(inthour,intminute,intsecond);Time(intsecond);Time(char*str)voidprint()couthh,mm,ssn;C+程序设计Time:Time()hh=0;mm=0;ss=0;Time:Time(inthour,intminute,intsecond)if(hour0|minute0|second0)hh=0;mm=0;ss=0;cout非法的初值!时间不能为负59)minute=minute+second/60;minute=sec

125、ond%60;if(minute59)hour=hour+minute/60;minute=minute%60;if(hour23)hour=hour%24;hh=hour;mm=minute;ss=second;C+程序设计Time:Time(intsecond)hh=0;mm=0;if(second0)ss=0;cout 非 法 的 初 值 !时 间 不 能 为 负59)mm=second/60;ss=second%60;if(mm59)hh=mm/60;mm=mm%60;C+程序设计if(hh23)hh=hh%24;Time:Time(char*string)char*str=newch

126、ar3;str0=string0;str1=string1;str2=0;hh=atoi(str);str0=string3;str1=string4;C+程序设计str2=0;mm=atoi(str);str0=string6;str1=string7;str2=0;ss=atoi(str);deletestr;voidmain()Timet1,t2(100,100,100),t3(3690),t4(18:56:34);C+程序设计coutt1:;t1.print();coutt2:;t2.print();coutt3:;t3.print();coutt4:;t4.print();程序运行结

127、果为:t1:0:0:0t2:5:41:30t3:1:1:30t4:18:56:34C+程序设计3.7 编编 程程 实实 例例对于包含有指针或引用的类,一定要认真地定义其构造函数、拷贝构造函数以及析构函数,否则将引起严重的错误,请看下例。例3.15串类。/string.hppclassStringC+程序设计private:char*content;intlength;public:String()length=0;String(char*);String(int);String(constString&);C+程序设计/string.cpp#includestring.hpp#includeS

128、tring:string(char*str)length=strlen(str)content=str;String:String(intlen)length=len;conten=newcharlen;C+程序设计String:String(constString&s)length=s.length;content=s.content;缺省的拷贝构造函数与这里定义的构造函数结果是一样的。测试程序如下:/main.cpp#includestring.hppStrings1(test);main()C+程序设计char*str;str=newchar20;strcpy(str,examples)

129、;Strings2(str);String*ps;ps=newString(s1);deletestr;deleteps;C+程序设计当这个程序执行deletestr和deleteps语句后,对象s1和s2都不能正常工作。让我们来分析s1的情况,当用s1初始化p所指的动态对象时,s1和s2两个将共享同一个字符串“test”,而当ps所指的对象删除后,s1对象将失去所操作的空间。s2的情况也类似,请读者自己分析。因此,我们将string.cpp修改为:/string.cpp#includestring.hpp#includeC+程序设计String:String(char*str)length=

130、strlen(str);content=newcharlength;strcpy(content,str);String:String(intlen)length=len;content=newcharlen;C+程序设计String:String(constString&.s)length=s.length;content=newcharlength;strcpy(content,s.content);这样String类就可以正常工作了。C+程序设计小结构造函数和析构函数是类的特殊成员函数,它们的实现使C+的类机制得以充分的展示,因此,本章内容是全书的重点之一。本章一开始介绍了各种对象的定义

131、及使用,并介绍了与之相关的const类型声明和动态分配/撤销内存运算符(new和delete)的使用。通过本章学习,要求能理解类和对象的区别。C+程序设计构造函数是一种用于创建对象的特殊的成员函数,其主要功能是在对象被创建时对对象作初始化操作。而析构函数与构造函数相反,是在对象被删除(即脱离作用域)前由系统自动执行它做清理工作。带参数的构造函数:不带参数的构造函数不能完全满足初始化的要求,因此要让构造函数可以带一个或多个参数,否则,往往只能先将对象构造成千篇一律的对象值,甚至一个随机值对象。构造函数是一段程序,因此,构造对象的先后顺序不同,直接影响程序执行的先后顺序,导致不同的运行结果。为此,

132、C+给构造对象的顺序作了一些专门的规定。C+程序设计第第4章章 引用引用4.1 引用的概念引用的概念4.2 引用的操作引用的操作4.3 引用作为函数的参数引用作为函数的参数4.4 编程实例编程实例C+程序设计4.1 引用的概念引用的概念引用(reference)是另一标识符的别名,可以说是C+的一种新的变量类型,是对C的重要扩充。当建立引用时,程序用另一个变量或对象(目标)的名字初始化它(即它代表了标识符的左值),此时,引用作为目标的别名而使用,对引用的改动实际就是对目标的改动。C+程序设计为建立引用,在编程时应先写上目标的类型,再写引用运算符“&,然后是引用的名字。引用能使用任何合法的变量名

133、,它可以在该变量出现的任何地方出现,这称为独立引用。通常,用X&表示对X类型的某变量的引用。引用的声明有些像指针,但必须在声明时就初始化,指出它引用的内存对象,否则会产生编译错误。引用不是值,不占内存空间,声明引用时,目标的存储状态不会改变。因此,既然“定义”的概念有具体分配空间的含义,那么引用只有声明,没有定义。C+程序设计例4.1下面的程序建立和使用了引用。#include#includevoidmain()inta=10;int&b=a;/声明b是对整数a的引用,a=a*a;/a的值变化了,b的值也一起变化C+程序设计coutasetw(6)bendl;b=b/5;/b的值变化了,a的值

134、也一起变化coutbsetw(6)aendl;运行结果为:1001002020声明b是对整数a的引用,并且使其初始化为变量a的一个别名。一旦b同a的内存对象发生了联系,就不能改变,而且,对b的访问就是对a的访问,对a的访问也就是对b的访问。C+程序设计图4-1C+程序设计说明:(1)引用运算符与地址符使用的符号相同,尽管它们显然是彼此相关的,但它们却不一样,引用运算符只在声明时使用,它放在类型名后面。例如:int&ri=i;。任何其它的“&”的使用都是地址操作符,例如:int*ip=&i;cout&ip;C+程序设计(2)与指针类似,下面三种声明引用的方法都是合法的:int&ri;int&ri

135、;int&ri;(3)下面的语句包含一个引用的声明和一个变量的定义:int&ri,b;/会误解为声明了两个引用为了提高可读性,不应在同一行上同时声明引用、指针和变量。C+程序设计4.2 引引 用用 的的 操操 作作对引用的操作就是对其包含的常量指针所指向的内容进行操作,也就是对被引用变量的值进行操作。如果程序要寻找引用的地址,则它只能找到所引用的目标的地址。例4.2下面是取引用地址的程序。#includeC+程序设计voidmain()inti;floatj=3.1415926;int&ri=i;i=5;float&rj=j;float&rrj=j;float*pj=&rj;/pj指向j,取r

136、j的地址就是取j的地址C+程序设计float*p;float*&rp=p;/rp引用指针pfloatm=6.0;rp=&m;/使p指向m,对rp的访问就是对p的访问couti:iendl;coutri:riendl;cout&i:&iendl;cout&ri:&riendl;coutj:jendl;coutrj:rjendl;C+程序设计coutrrj:rrjendl;coutpj:pjendl;coutrp:rpendl;coutm:mendl;运行结果为:i:5ri:5&i:8f48:fff4&ri:8f48:fff4C+程序设计j:3.141593rj:3.141593rrj:3.141

137、593pj:8f48:fff0rp:8f48:fff0m:6说明:(1)C+没有提供访问引用本身地址的方法,因为它与指针或其它变量的地址不同,它没有任何意义。引用在建立时就初始化,而且总是作为目标的别名使用,即使在应用地址操作符时也是如此。C+程序设计(2)引用一旦初始化,它就维系在一定的目标上,再也分不开。任何对该引用的赋值,都是对引用所维系的目标的赋值,而不是将引用维系到另一个目标上。在例4.1中,引用b被重新赋值为变量a。从运行结果可以看出,b仍然维系在原a上,因为b与a的地址是一样的。(3)引用与指针有很大的区别,指针是个变量,可以把它再赋值成指向别处的地址,然而,建立引用时必须进行初

138、始化,并且决不会再关联其它不同的变量。C+程序设计(4)需强调以下几点:l若一个变量声明为T&,即引用时,它必须用T类型的变量或对象,或能够转换成T类型的对象进行初始化。如果引用类型T的初始值不是一个左值,那么将建立一个T类型的目标并用初始值初始化,而那个目标的地址变成引用的值。C+程序设计例如,代码double&rr=1;是合法的。在这种情况下,应首先作必要的类型转换,然后将结果置于临时变量,最后把临时变量的地址作为初始化的值。因此上面的语句解释为:doubletemp;temp=double(1)/类型转换,并放在临时变量temp中double&rr=temp;C+程序设计l上面的语句中将

139、临时变量显式地表示了出来。事实上,临时变量并不在存放局部变量的栈区。由于指针也是变量,因此可以有指针变量的引用:int*a;int*&p=a;/表示int*的引用p初始化为aintb=8;p=&b;/p是a的别名,是一个指针l对void进行引用是不允许的。例如:void&a=3;/错误C+程序设计void只是在语法上相当于一个类型,但是本质上不是类型,因为没有任何一个变量或对象的类型为void。l不能建立引用的数组:inta10;int&ra10=a;/错误因为数组是某个数据类型元素的集合,数组名表示该元素集合空间的起始地址,它自己不是一个名副其实的数据类型。C+程序设计l引用本身不是一种数据

140、类型,定义引用在概念上不产生内存空间,因此,没有引用的引用,也没有引用的指针。例如:l引用不能用类型来初始化,即int&ra=int;/错误因为引用是变量或对象的引用,而不是类型的引用。l有空指针,无空引用。不应有下面的引用声明,否则会有运行错误:int&ri=NULL;/无意义C+程序设计4.3 引用作为函数的参数引用作为函数的参数4.3.1用引用传递函数参数传递引用给函数与传递指针的效果一样,传递的是原来的变量或对象,而不是在函数作用域内建立变量或对象的副本。在C语言中我们看到,对swap(int,int)传值方式函数的调用不影响调用函数中的实参,结果并未达到交换数据的预想目的。C+程序设

141、计使用指针传递方式的swap(int*,int*)函数的调用,能够达到预定的目的,但是函数的语法相对传值方式来说比较累赘。首先,在swap()函数内需要重复的引用(dereference)(*px),这容易产生错误且难于阅读;其次,调用函数需要传递变量地址,使swap()内部的工作对用户太过显然,而且swap(&x,&y)的形式会造成一种交换两个变量地址的错觉。C+程序设计C+的目标之一就是让使用函数的用户无须考虑函数是如何工作的。传递指针给使用函数的用户增加了编程和理解的负担,这些负担应属于被调用函数。例4.3利用指针变量作形参,实现两个变量的值互换。voidswap(int*p1,int*

142、p2)inttemp;temp=*p1;*p1=*p2;*p2=temp;C+程序设计voidmain()intx=20;inty=30;coutbeforeswap,x:x,y:yendl;swap(x,y);coutafterswap,x:x,y:yendl;C+程序设计运行结果为:beforeswap,x:20,y:30afterswap,x:30,y:20上述方法实际也是一种“值传递”方式,即向一个指针变量传送一个地址,然后通过指针访问有关变量。这样做虽然结果正确,但在概念上“兜了一个圈子”,不那么直截了当。C+程序设计例4.4下面是一个用“引用形参”改写swap()函数的定义及调用的

143、程序。#inciudevoidswap(int&rx,int&ry);voidmain()intx=20;inty=30;coutbeforeswap,x:x,y:yendl;swap(x,y);C+程序设计coutafterswap,x:x,y:yendl;voidswap(int&rx,int&ry)inttemp;temp=rx;rx=ry;ry=temp;C+程序设计运行结果为:beforeswap,x:20,y:30afterswap,x:30,y:20在主函数中,调用swap()函数的参数是x和y,表面上看是简单地传递变量而不是它们的地址,而实际上传递的就是它们的地址。引用传递的内

144、存布局与指针相仿,只是操作完全不同。每当使用引用时,C+就会去求该引用所含地址中的变量值。C+程序设计引用具有指针的功效,但是调用引用传递的函数时,可读性却比指针好。引用具有传值方式函数调用语法的简单性与可读性,但是功效却比传值方式强。说明:尽管引用可以让程序员了解如何传递参数,但是在有些情况下引用会隐藏错误。例如,下面的代码在没有得到函数原形之前可能会误认为实参a和b是通过值来传递的,从而不能通过函数调用来修改它,而事实上却能够修改:C+程序设计inta=10;intb=20;swap(a,b);因为程序中引用了函数所能使用的参数传递的类型,所以无法从所看到的函数调用判断其是值传递还是引用传

145、递。正因为此,下面代码中的两个重载函数将引起编译报错。voidfn(ints)函数体;C+程序设计voidfn(int&t)函数体;voidmain()inta=5;fn(a);/错误,匹配哪一个函数C+程序设计4.3.2返回多个值通常,函数只能返回一个值。让程序从函数返回两个值的办法之一是用引用给函数传递两个参数,然后由函数往目标中填入正确的值。因为用引用传递允许函数改变原来的目标,这一方法实际上让函数返回两个信息。这一策略绕过了函数的返回值,使得可以把返回值保留给函数,作报告运行成败或错误原因用。引用和指针都可以用来实现这一过程。下面的程序实际上返回了三个值,两个是引用,一个是函数返回值。

146、C+程序设计例4.5利用引用返回多个值。#includeboolFactor(int,int&,int&);voidmain()intnum,squ,cub;boolerror;coutnum;C+程序设计error=Factor(num,squ,cub);if(error)coutErrorencountered!n;elsecoutNum:numendl;coutSqu:squendl;coutCub:cub20|n0)returntrue;rSqu=n*n;rCub=n*n*n;returnfalse;C+程序设计运行结果为:Enteranum(020):3Num:3Squ:9Cub:2

147、7Fact()函数用于检查用值传递的第一参数,如果不在020的范围内,它就简单地返回错误值(假设程序正常返回为0)。程序真正需要的值squ和cub是通过改变传递给函数的引用返回的,而没有使用函数返回机制。C+程序设计下面介绍带缺省参数的函数。一般情况下,实参个数与形参个数相同,而C+允许实参个数与形参个数不同。办法是在形参表列中对一个或几个形参指定缺省值(即默认值)。例如,某一函数的首部可用如下形式:voidfun(inta,intb,intc=100)在调用此函数时如写成fun(2,4,6),则形参a,b,c的值分别为2,4,6。但如果写成fun(2,4),则形参a,b,c的值分别为2,4,

148、100。C+程序设计注意:赋予缺省值的参数必须放在形参表列中的最右端。例如:voidf1(floata,intb,intc=0,chard=a)/正确voidf1(floata,intc=0,chard=a,intb) /错误利用此特性,可以使函数的使用更加灵活。但不要同时使用重载函数和带缺省参数的函数,因为当调用函数少一个参数时,系统将无法判定是利用重载函数还是利用带缺省参数的函数,从而发生错误。C+程序设计4.4 编编 程程 实实 例例函数返回值时,要生成一个值的副本,而用引用返回值时,不生成值的副本。例4.6下面的程序是有关引用返回的4种形式。#includefloattemp;floa

149、tfn1(floatr)C+程序设计temp=r*r*3.14;returntemp;float&fn2(floatr)temp=r*r*3.14;returntemp;voidmain()C+程序设计floata=fn1(5.0);/情况1float&b=fn1(5.0);/情况2:warningfloatc=fn2(5.0);/情况3float&d=fn2(5.0);/情况4coutaendl;coutbendl;coutcendl;coutdendl;C+程序设计运行结果为:78.578.578.578.5对主函数的4种引用返回的形式,程序的运行结果是一样的,但是它们在内存中的活动情况是

150、各不相同的。其中,变量temp是全局数据,驻留在全局数据区data,函数main()、函数fn1()和函数fn2()驻留在栈区stack。C+程序设计情况1:这种情况是一般的函数返回值方式。返回全局变量temp值时,C+创建临时变量并将temp的值78.5复制给临时变量。返回到主函数后,赋值语句a=fn1(5.0)把临时变量的值78.5复制给a。情况2:这种情况下,函数fn1()是以值方式返回的,返回时,复制temp的值给临时变量。C+程序设计返回到主函数后,引用b以临时变量来初始化,使得b成为临时变量的别名。由于临时变量的生命期短,因此b面临无效的危险。根据C+标准,临时变量或对象的生命期在

151、一个完整的语句表达式结束后便宣告结束,也即在float&b=fnl(5.0);之后,临时变量不再存在,因此,引用b以后的值是无法确定的值。BorlandC+对C+标准进行了扩展,规定如果临时变量或对象作为引用的初始,则其生命期与该引用一致。这样的程序依赖于编译器的具体实现,因此移植性很差。C+程序设计若要以返回值初始化一个引用,应该先创建一个变量,将函数返回赋给这个变量,然后再以该变量来初始化引用,就像下面这样:intx=fnl(5.0);int&b=x;情况3:在这种情况下,函数fn2()的返回值不产生副本,因此,直接将变量temp返回给主函数。主函数的赋值语句中的左值c直接从变量temp中

152、得到复制,这样避免了临时变量的产生。当变量temp是一个用户自定义的类型时,这种方式会提高程序执行效率和空间利用。C+程序设计情况4:在这种情况下,函数fn2()返回一个引用,因此不产生任何返回值的副本。在主函数中,一个引用声明d用该返回值来初始化,使得d成为temp的别名。由于temp是全局变量,因此在d的有效期内temp始终保持有效。这种方式是安全的。但是,如果返回不在作用域范围内的变量或对象的引用,那就有问题了。这与返回一个局部作用域指针的性质一样严重。BC作为编译错误,VC作为警告来提请编程者注意。C+程序设计小结引用是C+的独有特性,也是C+学习的难点。本章主要介绍了引用的概念及各种

153、语法现象。引用型变量作为函数的形参时,仅仅传递实参的地址而没有传递实参新的拷贝(若将子类对象传递给父类的形参函数时,可以保留子类的特定信息),而传值仅仅传递“实参的拷贝”。引用传递与值传递在使用方法上惟一的区别是函数的形式参数声明不同是。引用是对操作符重载必不可少的补充(这将在后面介绍)。C+程序设计在C+中,函数参数的传递有按值传递和引用传递两种方式。如果接收函数改变了传送给它们的参数的值,而且这些改变在调用函数中被识别,则认为这种传递方式是按地址传送;如果调用函数的参数在接收函数中保持不变则是按值传递。所有的数组都自动被按地址传递,而不能按值传递。数组的名是一个指针,指针永远等于数据的地址

154、。C+程序设计任何非数组变量都可以采用引用传递,只要在接收参数表中插入一个&符号就可以表示在对一个变量进行引用传递。引用传递是高效的安全的,可以在接收引用参数前加const,以防止函数无意间改变了引用参数。在函数原型中可以声明缺省参数表,简化编程。函数不仅可以声明一个缺省参数,可以按需要声明多个缺省参数,还可以混合常规参数。但缺省参数必须放在参数表中所有常规参数的后面。C+程序设计任何非数组变量都可以采用引用传递,只要在接收参数表中插入一个&符号就可以表示在对一个变量进行引用传递。引用传递是高效的安全的,可以在接收引用参数前加const,以防止函数无意间改变了引用参数。在函数原型中可以声明缺省

155、参数表,简化编程。函数不仅可以声明一个缺省参数,可以按需要声明多个缺省参数,还可以混合常规参数。但缺省参数必须放在参数表中所有常规参数的后面。C+程序设计第第5 5章章 静静态成员与友员成员与友员5.1 静态成员的需要性静态成员的需要性5.2 静态数据成员静态数据成员5.3 静态函数成员静态函数成员5.4 静态成员的使用静态成员的使用5.5 友元的使用友元的使用5.6 编程实例编程实例C+程序设计5.1 静态成员的需要性静态成员的需要性类是类型而不是数据对象,每个类的对象都是该类数据成员的拷贝。然而,往往需要让类的所有对象在类的范围内共享某个数据,这些数据是类中所有对象共有的。声明为stati

156、c类的成员便能在类的范围内共享,称为静态成员。例如,建立一个学生链表,在Student类中,声明链表的首指针和学生人数。C+程序设计classStudent/.protected:Student*pFirst;/链表首指针intcount;/学生人数;这个类声明意味着每个学生对象都有一个链表首指针和学生数,要想得到现有的学生数,不能到类中去取,因为类不是一个占有内存的实体。而到对象中去取,则要求一旦学生人数变化,就要对每个对象进行修改。C+程序设计如果将学生数的定义count放在全局变量中,则它们在类的外面,不符合面向对象要求的封装性和重用性。例如,下面的代码用全局变量来表示学生类链表首指针和

157、学生人数。classStudent/.;intcount;/记录学生人数,是全局变量Student*pFirst;/学生类链表首指针,是全局变量C+程序设计voidfn()Studentstud1;/创建第一个学生对象count+;/学生人数加1pFirst=&stud1;/对pFirst没有约束/fn()退出时,stud1作用域终止,stud1被析构C+程序设计由于count和pFirst均为全局变量,在庞大的程序中,没有真正指明哪个函数对它们负责,这种无规则会引起软件设计的混乱,一旦程序变大,维护量就急剧上升。同时,在重用Student类的时候,总是还要额外地考虑全局变量的处置,这不得不使

158、类的封装性受到伤害。若能将学生人数和链表首指针封装在类里面,则count和pFrirst可以受到保护,还可以作为一个类而重用,这种属于类的一部分,但既不适于用普通成员表示,也不适于用全局变量表示的数据,用静态成员来表示。C+程序设计5.2 静态数据成员静态数据成员 在一个类中,若将一个数据成员说明为static,这个成员就成为静态数据成员。静态数据成员是类的所有对象共享的成员因此,静态数据成员被看做是类的成员而不是某个对象的成员。静态数据成员有以下特点:(1)使用静态数据成员可以节省内存。因为无论建立多少个类的对象,都只有一个静态数据成员的拷贝,即对多个对象来说,静态数据成员只有一个,供所有对

159、象共用。C+程序设计(2)静态数据成员是静态存储的,必须对它进行初始化。静态数据成员的初始化与一般数据成员的初始化不同,其格式如下:数据类型类名:静态数据成员名=值;静态数据成员采用如下方式声明、分配内存空间和初始化:classclassNamepublic:protected:C+程序设计staticintabc;/声明静态数据成员,未分配空间;intclassName:abc=0;/静态数据成员在类声明外分配空间和初始化注意:l初始化是在类体外进行的,在类中不为它分配内存空间;l 初始化时不加该成员的访问权限控制符private、protected、public;l初始化时必须使用作用域运

160、算符(:)来表明它所属的类。C+程序设计公共静态数据成员可被类的外部访问(即访问控制权限设为public),保护或私有静态数据成员只可被类的内部访问,上面的代码描述了一个保护静态数据成员。访问静态数据成员时,采用如下格式:类名:静态数据成员名C+程序设计静态数据成员用得比较多的场合一般为:l用来保存流动变化的对象个数(如:记录学生类链表中学生对象的个数);l作为一个标志,指示一个特定的动作是否发生(如:可能创建几个对象,每个对象要对某个磁盘文件进行写操作,但显然在同一时间里只允许一个对象写文件,在这种情况下,用户希望说明一个静态数据成员指出文件何时正在使用,何时处于空闲状态);l一个指向一个链

161、表第一成员或最后一个成员的指针(如:链表首指针pFirst)。C+程序设计例5.1对象计数模式。#includeclassPointdoublex,y;staticintcount;public:Point(doublea=0,doubleb=0)x=a;y=b;count+;/构造函数Point(Point&p);/拷贝初始化构造函数C+程序设计Point();doubleGetX()returnx;doubleGetY()returny;voidShowCount()/输出静态数据成员coutPointObjectid=countendl;voidPrint()coutThepointis

162、:(x,y)endl;C+程序设计;Point:Point(Point&p)x=p.x;y=p.y;count+;intPoint:count=0;/静态数据成员的初始化,使用类名限定voidmain()C+程序设计PointA(3.14,4.2);coutPointA(A.GetX(),A.GetY();A.ShowCount();PointB(A);coutPointB(B.GetX(),B.GetY();B.ShowCount();运行结果为:PointA(3.14,4.2)PointObjectid=1PointB(3.14,4.2)PointObjectid=2C+程序设计说明:程序

163、中类Point的数据成员count被声明为静态,用来给Point类的对象计数,每定义一个新对象,count的值就加1。静态数据成员count的定义和初始化在类外进行,其访问控制权是私有的,在成员函数中可以直接访问,而在main()函数中不能直接访问。C+程序设计例5.2建立一个学生类,该类对象是一个个的学生,他们构成一个单向链表/静态数据成员的应用#include#includeclassStudentpublic:Student(char*pName);Student();protected:C+程序设计staticStudent*pFirst;Student*pNext;charname4

164、0;Student*Student:pFirst=0;intStudent:count=0;Student:Student(char*pName)strcpy(name,pName,sizeof(name);namesizeof(name)-1=0;C+程序设计pNext=pFirst;/每新建一个节点(对象),就将其挂在链首pFirst=this;Student:Student()coutnamepNext);if(ps-pNext=this)ps-pNext=pNext;return;Student*fn()Student*ps=newStudent(Jenny);/通过new申请的空间,

165、必须由delete释放C+程序设计Studentsb(Jone);/对象sb的作用域在函数fn内Returnps;voidmain()Studentaa(Jamsa);Student*sb=fn();Studentsc(Tracey);deletesb;C+程序设计运行结果为:JoneJennyTraceyJamsa程序建立和删除学生类链表的过程如图5.1所示。delete sb;语 句 先 将 “Jenny”对 象 析 构 , 再 依 次 将“Tracey”和“Jamsa”析构。C+程序设计实现链表结构的学生类,需要一个链首指针pFirst,且每个对象都需要一个指向下一个对象的指针pNext

166、,;因此,pNext数据成员不是静态的,而pFirst数据成员是静态的。运行上面程序时,主函数先创建一个名叫Jamsa的学生对象,并使链表含有一个节点,随后调用函数fn()。在函数fn()中,从堆中创建一个名叫Jenny的学生对象,此时,链中含有两个节点,Jenny是首节点。接着,fn()又在栈中创建Jone的学生对象,这时在链中含有3个节点,Jone成为链首节点。C+程序设计函数fn()返回时,名叫Jone的sb对象的作用域结束,析构它时,将其从链表中删除。删除的是链表首节点,此时链表中含有两个节点。函数fn()返回一个堆对象的指针给主函数中的sb。接着主函数又创建一个新的对象sc,将其链入

167、链表中,成为首节点。C+程序设计最后一条语句释放堆对象空间。释放时,自动析构堆对象,该对象便从链中删除。删除的该结点不在链首,删除过程中,先找到该结点,然后将前后两个结点连起来。此后,链中剩下两个结点。当退出主函数时,先后析构Tracey和Jamsa的两个对象。析构后,链表清空如初。链表操作是在构造函数和析构函数中进行的。构造中增加节点的处理比从析构中删除一个节点相对要容易一些。删除一个学生节点时,先要在链表中找到指向当前节点的节点,然后把前一个节点和后一个节点链接起来。在实现节点链接时,找到当前节点位置时,保存指向当前节点的节点指针是至关重要的。C+程序设计图5-1C+程序设计5.3 静态函

168、数成员静态函数成员用static关键字声明的函数成员是静态函数成员。静态函数成员和静态数据成员一样,都属于类的静态成员,而不是属于某个对象,由同一个类的所有对象共同维护,为这些对象所共有。C+程序设计静态函数成员定义是类的内部实现,属于类定义的一部分。它的定义位置与一般成员函数一样。与静态数据成员一样,静态函数成员与类相联系,而不是与类的对象相联系,因此访问静态函数成员时,不需要对象。如果用对象去引用静态函数成员,只是用其类型。C+程序设计由于一个类的静态函数成员只有一个拷贝,因此它访问对象的数据和函数时将受到限制。静态函数成员可以直接访问该类的静态数据成员和成员函数,而访问非静态成员时,必须

169、通过对象进行,即必须通过参数传递方式得到对象名,然后通过对象名来访问。如果访问控制权限允许的话(即为public),可以在程序内部不依赖于任何对象按下述格式调用静态函数成员:类名:静态函数成员名()C+程序设计例5.3静态函数成员调用。#includeclassAintx;staticinty;public:A(inti)x=i;y+=x;staticvoidfun(Aa);C+程序设计intA:y=0;voida:fun(Aa)coutx=a.xendl;couty=yendl;voidmain()AP(6),Q(8);A:fun(P);A:fun(Q);C+程序设计程序运行结果如下:x=6

170、y=14x=8y=14说明:该程序的A类中,申明并定义了静态函数成员fun(),在该函数的函数体中访问类的非静态成员是通过对象进行的,例如coutx=a.x;访问类的静态成员是直接进行的,例如couty=y。另外,在main()函数中,下述两条语句是对静态函数成员的调用:A:fun(P);A:fun(Q);C+程序设计这里P和Q是A类的两个对象,作为函数的参数。对静态函数成员的使用再作几点说明:(1)静态函数成员可以在类体内定义,也可以在类体外定义。在类体外定义时,不要用static前缀;(2)使用静态函数成员可以在建立任何对象之前处理静态函数成员,这是普通函数成员不能实现的功能;C+程序设计

171、(3)在一般函数成员中都隐含有一个this指针,用来指向对象自身,而在静态函数成员中是没有this指针的,因为它不与特定的对象相联系;(4)一般静态函数成员不能访问类中的非静态成员。如果确实需要,静态函数成员只能通过对象名(或指向对象的指针)访问该对象的非静态成员。C+程序设计例5.4两种调用静态函数成员的方法。#includeclassStudentpublic:staticintnumber()returnnoofStudents;C+程序设计protected:charname40;staticintnoofStudent;intStudent:noofStudents=1;voidma

172、in()Students;counts.number()endl;/用对象引用静态函数成员countStudent:number()endl;/用类名引用静态函数成员C+程序设计运行结果为:11如果一个静态函数成员不与任何对象相联系,就不能对非静态数据成员进行默认访问。例如,下面的代码中静态函数成员不应访问非静态数据成员name。C+程序设计#includeclassStudentpublic:staticchar*sName()coutnoofStudentsendl;returnname;C+程序设计protected:charname40;staticintnoofStudents;in

173、tStudent:noofStudents=0;voidfn()Students;Couts.sName()endl;/sName从对象s上得到的是Student类型C+程序设计静态函数成员sName()只认类型,不认对象。即使用对象s引导的s.sName(),也只识别对象s所属类型。静态成员的这种性质使得访问任何非静态成员的操作都是非法的,因此,在上例的静态函数成员定义中,成员name对sName()来说不知所云。然而,并不是说静态函数成员不能对非静态函数成员进行访问。C+程序设计例5.5静态函数成员对非静态函数成员访问。#include#includeclassStudentpublic:

174、Student(char*pName);Student();staticStudent*findname(char*pName);C+程序设计protected:staticStudent*pFirst;Student*pNext;charname40;Student*Student:pFirst=0;/静态成员空间分配及初始化Student:Student(char*pName)strcpy(name,pName,sizeof(name);namesizeof(name)-1=0;C+程序设计pNext=pFirst;/每新建一个节点(对象),就将其挂在链首pFirst=this;Stude

175、nt:Student()if(pFirst=this)/如果要删除链首节点,只要链首指针指向下一个节点即可pFirst=pNext;return;C+程序设计for(Student*ps=pFirst;ps;ps=ps-pNext);if(ps-pNext=this)ps-pNext=pNext;return;Student*Student:findname(char*pName)C+程序设计for(Student*ps=pFirst;ps;ps=ps-pNext)if(strcmp(ps-name;pName)=0)returnps;voidmain()Students1(Randy);St

176、udents2(Jenny);Student*ps=Student:findname(Jenny);if(ps)C+程序设计coutOk.endl;elsecoutnofind.endl;运行结果为:Ok.说明:findname()是静态函数成员,它查找链中所有对象,看有没有“Jenny这个学生,因此它要访问对象中的name.。但它是从静态数据成员pFirst这个链表指针着手,通过一个临时指针不断指向所要用的对象,借此来访问对象成员的。C+程序设计5.4 静态成员的使用静态成员的使用静态数据成员由静态函数成员访问,下面的程序在类中定义了一个静态数据成员和一个静态函数成员;在类的构造函数和析构函

177、数中对静态数据成员进行操作;在应用程序中,调用了静态函数成员。例5.6静态成员的使用。#include#includeclassStudentC+程序设计public:Student(char*pName=noname)coutcreateonestudentn;strcpy(name,pName);noofStudents+;coutnoofStudentsendl;Student()C+程序设计coutdestructonestudentn;noofStudents-;coutnoofStudentsendl;staticintnumber()returnnoofStudents;prot

178、ected:staticintnoofStudents;charname40;intStudent:noofStudents=0;C+程序设计voidfn()Students1;Students2;coutStudent:number()endl;voidmain()fn();coutStudent:number()endl;C+程序设计运行结果为:createonestudent1createonestudent22destructonestudent1destructonestudent00C+程序设计数据成员noofStudents,既不是对象s1也不是对象s2的一部分。Student类

179、随着对象的产生,每个对象都有一个name成员值,但无论对象有多少,甚至没有,静态数据成员noofStudents都只有一个,所有Student对象都共享它,并且能够访问它。在Student对象空间中,是没有静态数据成员noofStudents的,它的空间。不会随着对象的产生而分配,或随着对象的消失而回收。因此,它的空间分配并不在Student的构造函数里完成,并且空间回收也不在类的析构函数里完成。C+程序设计静态数据成员确实是在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。这样,它的空间分配有3个可能的地方:一是作为类的外部接口的头文

180、件,那里有类声明;二是类定义的内部实现,那里有类的函数成员定义;三是应用程序的main()函数前的全局数据声明和定义处。C+程序设计静态数据成员要实际地分配空间,故不能在类声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸与规格,并不进行实际的内存分配,在类的声明中写成“staticintnoofStudents=0;”是错误的。静态数据成员也不能在头文件中类的声明的外部定义,因为那会造成在多个使用该类的源文件中对其重复定义。静态数据成员也不能在main()函数之前的全局数据声明处定义,因为那样会使每个重用该类的应用程序在包含了声明该类的头文件后,都不得不在应用程序中再定义一下该类的静

181、态成员。C+程序设计静态数据成员是类的一部分,静态数据成员的定义是类定义的一部分,因此将其放在类的内部实现部分中定义非常合适。定义静态数据成员时,要用类名引导。重用该类时,简单地包含其头文件即可。例5.7下面的程序将例5.6改成了多文件程序实现结构。文件Student.hclassStudentpublic:C+程序设计Student(char*pName=noname);Student();staticintnumber();protected:staticintnoofStudents;charname40;文件student.cpp#include#include#includeStud

182、ent.hC+程序设计intStudent:noofStudents=0;Student:Student(char*pName)coutcreateonestudentn;strcpy(name,pName);noofStudents+;coutnoofStudentsendl;Student:Student()coutdestructonestudentn;C+程序设计noofStudents-;coutnoofStudentsendl;intStudent:number()returnnoofStudents;文件example.cpp#includestudent.h#includevo

183、idfn()C+程序设计Students1;Students2;CoutStudent:number()endl;工程文件example.prj中包含:student.cppexample.cppC+程序设计运行结果为:createonestudent1createonestudent22destructonestudent1destructonestudent00C+程序设计在程序设计实践中,经常会遇到某个类只允许有一个实例对象的情况,如:一个WINDOWS应用程序中的Application类只能有一个Application对象,单文档应用程序的Document类也只允许有一个Documen

184、t对象等。使用静态成员可以做到保证这些类最多只创建一个对象实例。C+程序设计例5.8单实例对象模式。#includeclassApplicationstaticApplication*instance;Application();Public:Staticintcount;Application();staticApplication*init();C+程序设计;Application*Application:instance=NULL;intApplication:count=0;if(count=0)count+;instance=newApplication;coutSingleinst

185、ancehascreated,OK!endl;C+程序设计elsecout Cant create more than one instance! endl;returnNULL;Application:Application()count=count-1;coutThesingleinstancehasreleased!endl;C+程序设计voidmain()Application*ptr;ptr=Application:init();coutcount=Application:countendl;deleteptr;coutcount=Application:countendl;Appl

186、ication*ptr1;ptr1=Application:init();coutcount=Application:countendl;C+程序设计运行结果为:Singleinstancehascreated,OK!count=1Thesingleinstancehasreleased!count=0Singleinstancehascreated.OK!count=1C+程序设计若对main()中的deleteptr;语句进行注释,则程序输出为:Singleinstancehascreated,OK!count=1count=1Singleinstancehascreated.OK!cou

187、nt=1对比以上运行结果,可以看到,程序中只允许创建Application类的一个对象。注意,Application类的构造函数定义是私有的,只能通过公有函数成员init()间接访问。C+程序设计5.5 友元的使用友元的使用类的主要特征之一是数据隐藏,只有类的成员函数才能访问类的私有成员,而程序中的其它函数是无法访问私有成员的。但是,有时为了兼容早期独立定义的一些系统函数并提高两个类之间的互访效率,普通函数(相对于函数成员)需要直接访问一个类的保护或私有数据成员。C+程序设计面向对象程序设计的思想不允许对象的外部直接访问对象的私有数据成员或函数成员,普通函数需要直接访问类的保护或私有数据成员主

188、要是为了提高效率。为此,C+提供了一种有条件打破访问封装的机制“友元”,在不影响私有数据安全性的前提下,它使类的“朋友”能从类的外部访问类的私有成员。如果没有友元机制,则只能将类的数据成员声明为公共的,从而,任何函数都可以无约束地访问它。C+程序设计5.5.1需要友元的原因在某些情况下,不使用友元会使得程序的效率降低。例5.9编程实现矩阵和向量的乘法。定义两个类,矩阵类和向量类分别为Matrix和Vector,乘法操作的函数只能是普通函数,因为一个函数不可能既是这个类的成员又是那个类的成员。#include#include#include/向量类C+程序设计classVectorpublic:

189、Vector(int);/定义向量的大小Vector()deletev;/回收分配的空间Vector(Vector&);intSize()returnsz;/返回向量的长度voidDisplay();/显示向量int&Elem(int);/返回某个向量元素C+程序设计protected:int*v;/定义指向向量的指针intsz;/向量的长度;Vector:Vector(ints)if(s=0)cerrbadVectorsize.n;exit(1);C+程序设计sz=s;v=newints;/申请长度为s整型长度的内存空间/引用返回的目的是返回值可以作左值int&Vector:Elem(int

190、i)if(i0|sz=i)cerrVectorindexoutofrange.n;exit(1);C+程序设计returnvi;Vector:Vector(Vector&vec)v=newintsz=vec.sz;memcpy(void*)v,(void*)vec.v,sz*sizeof(int);/将vec.v指向的字符串拷贝到v中voidVector:Display()C+程序设计for(inti=0;isz;i+)coutvi;coutendl;/定义矩阵类classMatrixC+程序设计public:Matrix(int,int);Matrix(Matrix&);Matrix()de

191、letem;intSizeL()returnszl;/返回矩阵的行intSizeR()returnszr;/返回矩阵的列int&Elem(int,int);protected:int*m;intszl;intszr;C+程序设计;Matrix:Matrix(inti,intj)if(i=0|j=0)cerrbadMatrixsize.n;exit(1);szl=i;szr=j;m=newinti*j;C+程序设计Matrix:Matrix(Matrix&mat)szl=mat.szl;szr=mat.szr;m=newintszl*szr;memcpy(void*)m,(void*)mat.m

192、,szl*szr*sizeof(int);/引用返回的目的是返回值可以作左值C+程序设计int&Matrix:Elem(inti,intj)if(i0|szl=i|j0|szr=j)cerrMatrixindexoutofrange.n;exit(1);returnmi*szr+j;/矩阵乘向量的普通函数C+程序设计VectorMultiply(Matrix&m,Vector&v)if(m.SizeR()!=v.Size()cerrbadmultiplyMatrixwithVector.n;exit(1);Vectorr(m.SizeL();/创建一个存放结果的空向量for(inti=0;im

193、.SizeL();i+)C+程序设计r.Elem(i)=0;for(intj=0;jm.SizeR();j+)r.Elem(i)+=m.Elem(i,j)*v.Elem(j);returnr;voidmain()C+程序设计Matrixma(4,3);ma.Elem(0,0)=1;ma.Elem(0,1)=2;ma.Elem(0,2)=3;ma.Elem(1,0)=0;ma.Elem(1,1)=1;ma.Elem(1,2)=2;ma.Elem(2,0)=1;ma.Elem(2,1)=1;ma.Elem(2,2)=3;ma.Elem(3,0)=1;ma.Elem(3,1)=2;ma.Elem(3

194、,2)=1;Vectorve(3);ve.Elem(0)=2;ve.Elem(1)=1;ve.Elem(2)=0;Vectorva=Multiply(ma,ve);va.Display();输入的矩阵为,输入的向量为。C+程序设计运行结果为:4134Matrix中的m和Vector中的v是保护数据。由于Multiply()不是Matrix和Vector类的成员,不能直接操纵mij和vj,因此,只能通过m.Elem(i,j)和v.Elem(j)来访问矩阵和向量的元素。因为在Elem()函数定义中要对下标进行合法性检查,所以,Multiply()函数要频繁地调用函数和进行下标检查。在例5.7中进行

195、一次乘法运算:43矩阵乘3维以向量得到4维向量,其Elem()和Size()成员函数要被调用3+1+4(1+(1+2)3)=48次,显然效率不高。C+程序设计5.5.2友元函数友元函数不是本类的成员函数,而是独立于本类的外部函数,本类允许友元函数通过对象名访问它的私有成员和保护成员。在本类中声明一个普通函数,标上关键字friend,就成了该类的友元,它可以访问该类的一切成员。在例5.9中,将Multiply()函数声明为Matrix和Vector两个类的友元,就能使Multiply()函数既可访问Matrix的保护数据成员,又可访问Vector类的保护数据成员。友元函数在本类中定义,函数原型如

196、下:C+程序设计friend返回值类型友元函数名();友元说明语句可以放在类的公有部分,也可以放在类的私有部分。友元函数与成员函数一样既可以定义在类的内部,也可以定义在类的外部。例5.10将例5.9中的Multiply()改为友元。#include#include#include/向量类C+程序设计classMatrix;/前向声明类名声明classVectorpublic:Vector(int);Vector()deletev;Vector(Vector&);/intSize()returnsz;/有了友元,不需要该函数voidDisplay();int&Elem(int);C+程序设计fr

197、iendVectorMultiply(Matrix&m,Vector&v);protected:int*v;intsz;Vector:Vector(ints)if(s=0)cerrbadVectorsize.n;exit(1);C+程序设计sz=s;v=newints;int&Vector:Elem(inti)if(i0|sz=i)cerrVectorindexoutofrange.n;exit(1);returnvi;C+程序设计Vector:Vector(Vector&vec)v=newintsz=vec.sz;memcpy(void*)v,(void*)vec.v,sz*sizeof(i

198、nt);voidVector:Display()for(inti=0;isz;i+)coutvi;coutendl;C+程序设计classMatrixpublic:Matrix(int,int);Matrix(Matrix&);Matrix()deletem;/intSizeL()returnsz1;/intSizeR()returnszr;int&Elem(int,int);friendVectorMultiply(Matrix&m,Vector&v);C+程序设计protected:int*m;intszl;intszr;Matrix:Matrix(inti,intj)if(i=0|j=0

199、)cerrbadMatrixsize.n;exit(1);C+程序设计szl=i;szr=j;m=newinti*j;Matrix:Matrix(Matrix&mat)szl=mat.szl;szr=mat.szr;m=newintszl*szr;memcpy(void*)m,(void*)mat.m,szl*szr*sizeof(int);C+程序设计int&Matrix:Elem(inti,intj)if(i0|szl=i|j0|szr=j)cerrMatrixindexoutofrange.n;exit(1);returnmi*szr+j;C+程序设计VectorMultiply(Mat

200、rix&m,Vector&v)if(m.szr!=v.sz)cerrbadmultiplyMatrixwithVector.n;exit(1);Vectorr(m.szl);for(inti=0;im.szl;i+)r.vi=0;C+程序设计for(intj=0;jm.szr;j+)r.vi+=m.mi*3+j*v.vj;returnr;voidmain()C+程序设计Matrixma(4,3);ma.Elem(0,0)=1;ma.Elem(0,1)=2;ma.Elem(0,2)=3;ma.Elem(1,0)=0;ma.Elem(1,1)=1;ma.Elem(1,2)=2;ma.Elem(2,

201、0)=1;ma.Elem(2,1)=1;ma.Elem(2,2)=3;ma.Elem(3,0)=1;ma.Elem(3,1)=2;ma.Elem(3,2)=1;Vectorve(3);ve.Elem(0)=2;ve.Elem(1)=1;ve.Elem(2)=0;Vectorva=Multiply(ma,ve);va.Display();C+程序设计运行结果为:4134由于程序中的乘法运算直接访问矩阵类和向量类的保护数据,避免了频繁调用成员函数,因此效率较例5.9有很大提高。需要友元的另一个原因是为了方便重载操作符。友元函数不是成员函数,它是类的朋友,能够访问类的全部保护数据。在类的内部,只能声

202、明友元的函数原型,并加上friend关键字。友元声明的位置可在类的任何部位:既可在public区,也可在protected区,意义完全一样。友元函数定义则在类的外部,一般与类的成员函数定义放在一起,因为类重用时,友元是一起提供的。C+程序设计函数VectorMultiply(Matrix&m,Vector&v),既是矩阵类Matrix的友元又是向量类Vector的友元,当它在向量类Vector中声明时,要用到矩阵类Matrix,而矩阵类此时并未定义,解决这种交叉声明问题的方法是先进行类声明。如例5.10中的“classMatrix;”,让编译知道Matrix类的名字已经登记在册,后面可以引用这

203、个名字。类声明不能用于定义Matrix类的对象,因为这时还没有类的完整声明,没有分配类对象空间的依据(即不能在类声明“classMatrix;”后,声明Matrix类之前,出现定义类对象语句:Matrixmm;)。C+程序设计例5.11已知两点坐标,求两点间距离。#include#includeclassPointdoublex,y;public:Point(doublea,doubleb)x=a;y=b;Voidprint()cout(x,y)endl;C+程序设计frienddoubledistance(Point&p1,Point&p2);/说明distance()是Point类的友元函

204、数;doubledistance(Point&p1,Point&p2)doubledx=p1.x-p2.x;doubledy=p1.y-p2.y;returnsqrt(dx*dx+dy*dy);C+程序设计voidmain()PointA(3.0,4.0),B(6.0,8.0);A.print();B.print();doubled=distance(A,B);coutDistanceis:dendl;C+程序设计运行结果如下:(3.0,4.0)(6.0,8.0)Distanceis:5.0说明:由于程序中在Point类的定义中说明了distance()函数是友元函数,因此在distance(

205、)函数中可以访问Point类的私有数据成员x和y。C+程序设计使用友元函数时,应注意以下几点:(1)友元函数虽然可以访问本类对象的私有成员,但它毕竟不是本类的成员,因此,在类的外部定义友元时,不能在类名前加上“类名:”。(2)友元函数一般带有一个本类对象作入口参数。因为友元函数不是类的成员,所以它对本类私有成员的访问不是直接进行的,也不能用this指针来访问,而必须通过作为入口参数传递进来的对象名、对象指针或对象引用来访问该对象的私有成员或保护成员。(3)一个函数可以被多个类说明为友元。当一个函数需要访问多个类时,友元函数非常有用。C+程序设计5.5.3友元类不仅函数可以作为一个类的友元,而且

206、一个类也可以作为另一个类的友元,这种类称为友元类。当类A作为类B的友元类时,类A的所有成员函数都是类B的友元函数,都可以访问类B中的私有和保护成员。将类A说明为类B的友元类的语法格式为:classBfriendclassA;/说明类A是类B的友元类;C+程序设计说明类A是类B的友元类的语句可以放在类B的公有部分,也可以放在类B的私有部分。例5.12友元类的使用。#includeclassXprivate:intx;staticinty;friendclassY;public:C+程序设计voidset(inta)x=a;voidprint()coutx=x,y=yendl;classYpriv

207、ate:Xa;public:Y(intm,intn)a.x=m;C+程序设计b:y=n;voidprint();intX:y=1;voidY:print()coutx=a.x,;couty=X:yendl;voidmain()C+程序设计Xb;b.set(5);b.print();Yc(6,9);c.print();b.primt();运行结果如下:x=5,y=1x=6,y=9x=5,y=9C+程序设计说明:程序中,在X类中说明了类Y是它的友元类,因此在Y类中的成员函数中两次引用了X类的私有成员a.x。只有在友元中才可以这样做,因为一般在一个类的成员函数中,不能引用另一个类中的私有成员。另外,

208、在X类中定义了一个静态数据成员y,在Y类的成员函数中两次引用了X类中的这个静态数据成员,这也是只有友元类才可以做到的。从程序的执行结果可看出,Y类的对象c改变了X类的静态成员y之后,将保存其值,X类的对象b中y成员的值是改变后的值。由此可见,Y类对象与X类对象共用静态成员y。C+程序设计5.5.4友元成员除了一般的函数可以作为某个类的友元外,一个类的成员函数也可以作为另一个类的友元,这种成员函数称为友元成员函数。友元成员函数不仅可以访问自己所在类中的私有成员,还可以访问friend说明语句所在类(本类)中的私有成员和保护成员。例如,在下面的代码中,教师应该可以修改学生的成绩(访问学生类的保护数

209、据),将教师类的成员函数assignGrades()声明为学生类的友元。C+程序设计classStudent;/前向声明(类名声明)classTeacherpublic:voidassignGrades(Student&s);/给定成绩protected:intnoofStudents;Student*pList(100);C+程序设计;classStudentpublic:friendvoidTeacher:assignGrades(Student&s);proctected:Teacher*pT;IntsemesterHours;Floatgpa;C+程序设计;voidTeacher:as

210、signGrades(Student&s)s.gpa=4.0;/修改学生的平均成绩gpaC+程序设计例5.13友元成员的使用。#include#includeclassY;classXintx;char*strx;public:X(inta,char*str)C+程序设计x=a;strx=newcharstrlen(str)+1;strcpy(strx,str);voidshow(Y&ob);classYinty;char*stry;C+程序设计public:Y(intb,char*str)y=b;stry=newcharstrlen(str)+1;strcpy(stry,str);frien

211、dvoidX:show(Y&ob);voidX:show(Y&ob)C+程序设计coutthestringofXis:strxendl;coutthestringofYis:ob.stryendl;voidmain()Xa(10,stringx);Yb(10,stringy);a.show(b);C+程序设计运行结果如下:thestringofXis:stringxthestringofYis:stringy说明:(1)在该程序中,类X的成员函数show()在类Y中被说明为友元,因此,在该友元成员show()中可以访问类Y的私有成员stry。C+程序设计(2)一个类的成员函数作为另一个类的友元

212、函数时,必须先定义这个类。例如,在例5.13中,类X的成员函数为类Y的友元函数,因此,必须先定义类X,并且在说明友元函数时,要加上成员函数所在的类名,如:friendvoidX:show(Y&ob);(3)程序中第三行“classY;”为向前引用,因为函数show()的形参是类Y的对象引用,而类Y的定义在类X的定义之后。C+程序设计5.6 编编 程程 实实 例例 下面程序使用链表存储集合元素。#include#includestdio.hstructElementintval;Element*next;C+程序设计;classListprotected:Element*elems;public

213、:List();List();virtualBoolInsert(int);virtualBoolDelete(int);BoolContains(int);C+程序设计classSet:publicListintcard;public:BoolInsert(intval);Set();BoolDelete(intval);BoolEqual(Set*);voidPrint();voidintersect(Set*,Set*);voidEmptySet();C+程序设计;inlineList:List()elems=NULL;List:List()Element*tmp=elems;while

214、(tmp!=NULL)elems=elems-next;deletetmp;tmp=elems;C+程序设计BoolList:Insert(intval)Element*elem=newElement;if(elem!=NULL)elem-val=val;elem-next=elems;elems=elem;returnelem!=NULL;C+程序设计BoolList:Delete(intval)if(elems=NULL)returnFalse;Element*tmp=elems;if(elems-val=val)elems=elems-next;deletetmp;returnTrue;

215、C+程序设计elsefor(Element * elem = elems;elem-next ! = NULL;elem = elem-next)if(elem-next-val=val)tmp=elem-next;elem-next=tmp-next;deletetmp;returnTrue;returnFalse;C+程序设计BoolList:Contains(intval)for(Element*tmp=elems;tmp!=NULL;tmp=tmp-next)if(val=tmp-val)returnTure;returnFalse;inlineSet:Set()card=0;Bool

216、Set:Insert(insertcal)C+程序设计if(!Contains(val)&List:Insert(val)+card;returnTrue;returnTrue;BoolSet:Delete(intval)if(List:Delete(val)C+程序设计-card;returnTrue;returnFalse;BoolSet:Equal(Set*s)if(card!=s-card)returnDalse;for(Element*tmp=elems;tmp!=NULL;tmp=tmp-next)if(!(s-Contains(tmp-val)returnFalse;C+程序设计

217、returnTrue;voidSet:Print()coutnext!=NULL;tmp=tmp-next)coutval,;if(tmp!=NULL)coutval;coutnext;Deletetmp;voidSet:InterSect(Set*set,Set*res)res-EmptySet();C+程序设计for(Element*tmp=elems;tmp!=NULL;tmp=tmp-next)if(set-Contains(tmp-val)res-Insert(tmp-val);voidSet:Union(Set*set,Set*res)for(Element * tmp = ele

218、ms;tmp != NULL;tmp = tmp-next)res-Insert(tmp-val);for(tmp=set-elems;tmp!=NULL;tmp=tmp-next)res-Insert(tmp-val);C+程序设计voidmain()Setset(1);set.Insert(2);set.Insert(2);set.Print();Setset1(5);set1.Insert(6);set1.Print();set1.Union(&set,&set1);set1.Print();C+程序设计小结使用静态数据成员,实际上可以消灭全局变量。全局变量给面向对象程序带来的问题就是违

219、背封装原则。要使用静态数据成员必须在main()程序运行之前分配空间和初始化。使用静态数据函数,可以在实际创建任何对象之前初始化专有的静态数据成员。静态成员不与类的任何特定对象相关联。C+程序设计静态的static一词与静态存储类的static是两个概念,前者论及类,后者论及内存空间的位置以及作用域限定,因此要区分静态对象和静态成员。友元的作用主要是为了提高效率和方便编程。但随着硬件性能的提高,友元的这点作用已显得微不足道。另外,友元破坏了类的整体操作性,也破坏了类的封装性,因此,在使用它时要权衡利弊。C+程序设计 第第6章章 继承继承6.1 继承的概念继承的概念 6.2 派生类的构造和析构派

220、生类的构造和析构6.3 多态性多态性6.4 虚函数虚函数6.5 抽象类抽象类6.6 编程实例编程实例C+程序设计6.1 继继 承承 的的 概概 念念 继承是面向对象程序设计的一个重要特征,一方面,它提供了一种源代码级的软件重用手段,在程序中引入新的特性或功能时,可以充分利用系统中已定义的程序资源,避免了重复开发。另一方面,它也为抽象类的定义提供了一种基本模式。C+程序设计6.1.1继承的概念继承性在客观世界中是一种常见的现象。例如,一个人与他的兄弟、姐妹一样,在血型、肤色、身材、相貌等方面都具有其父母的某些生理特征,同时他又有与其兄弟、姐妹相区别的特征。从分类学的观点来看,要为一个新发现的物种

221、定义一个新的类,首先要确定与它最接近的既有的类是什么。在定义新类的时候,只要将新类定义为最接近既有类的子类,就可继承(无须重复描述)既有类的一切特性,对新类只要给出不同于既有类的那些特性的说明即可。C+程序设计图6-1C+程序设计从面向对象程序设计的观点来看,继承所表达的正是这样一种类与类之间的关系,这种关系允许在既有类的基础上创建新类。也就是说,定义新类时可以从一个或多个既有类中继承(即拷贝)所有的数据成员和函数成员,然后加上自己的新成员或重新定义由继承得到的成员。这就形成了一种类的层次关系,既有类称为基类或父类,以它为基础建立的新类称为派生类,导出类或子类。C+程序设计继承是C+语言中的一

222、种重要机制,该机制自动地为一个类提供来自另一个类的操作和数据结构,这使得程序员只需在新类中定义已有类中没有的成分来建立新类。理解继承是理解面向对象程序设计所有方面的关键。图6-1显示了生物的类层次。最顶部的类称为基类,是生物类,这个基类有动物和植物子类,即生物类就是动物和植物子类的父类。人类是动物类的子类,图中所示的每个子类均代表父类的特定版本。C+程序设计继承使得我们得以用一种简单的方式来描述事物。例如描述小学生时,可以说正在小学上学的人。小学生类是人类的子类,而人类的特点都清楚,因此只要举出小学生自己所具有的特点就行了。在面向对象程序设计中,可以声明一个新类作为另一个类的派生类(子类)并继

223、承其父类的属性和操作。子类也声明了新的属性和新的操作,剔除了那些不适合于其用途的继承下来的操作。继承是在解决软件重用的基础上扩展程序的有效手段。C+程序设计6.1.2派生类的定义格式派生类的定义形式与普通类的定义形式基本相同,不同的是必须在类声明的头部指明它的基类。派生类的定义格式为:class派生类名:继承方式基类名函数体;/派生类新增成员定义C+程序设计在上述格式中,派生类名是新定义的一个类名,它是从由“基类名”所标识的类派生而来的。这样定义的派生类继承了基类的除构造函数和析构函数之外的所有成员,因此派生类对象由两部分组成:一部分是从基类继承的成员,另一部分是派生类新增加的自己特有的成员。

224、派生类对象的数据结构由从基类继承到的那部分成员和派生类新增加的数据成员共同组成,其内存布局如图6-2所示。从图中可以看出,一个派生类对象比它的父类对象含有更多的信息,因此占有较大的存储空间。C+程序设计图6-2C+程序设计6.1.3继承方式“继承方式”用于规定派生类中由基类继承到的那部分成员在派生类中的访问控制权限。继承方式用下述3个关键字之一来指定:public(公有);protected(保护);private(私有)。例如,有学生类Student,要增加研究生类GraduateStudent。由于研究生除了他自己特有的性质外,具有学生的所有性质,因此我们用继承的方法重用学生类。C+程序设

225、计classStudent函数体;;classGraduateStudent:publicStudent函数体;;C+程序设计例6.1继承的应用#include#include/定义导师类classAdvisorintnoOfMeeting;C+程序设计/定义学生类,包括数据成员:总学时、平均分及相应的函数成员classStudentpublic:/初始化学生对象Student(char*pName=noname)strncpy(name,pName,sizeof(name);average=semesteerHours=0;C+程序设计/增加一门课程voidaddCourssee(intho

226、urs,floatgrade)average=(semesterHours*average+grade);/总分 semesterHours+=hours;/总修学时 average/=seemesterHours;/平均分/获取总修学时C+程序设计intgetHours()returnsemeesterHours;/获取平均分floatgetAverage()returnaverage;/显示结果voiddisplay()coutname=name,hours=semesterHours,average=averageendl;C+程序设计protected:charname40;intse

227、emesterHours;floataverage;classGraduateStudent:publicStudentpublic:getQualifier()returnqualifierGrade;protected:Advisoradvisor;IntqualifierGrade;C+程序设计;voidmain()Studentds(Loleeundergrade);GraduateStudentgs;ds.addCourse(3,2.5);ds.display();gs.addCourse(3,3.0);gs.display();C+程序设计运行结果为:name=Loleeunder

228、grade,hours=3,average=0.833333name=noname,hours=3,average=1说明:ds是Student类对象,gs是GraduateStudent类对象。作为Student的子类,对象gs可以实现ds能实现的任何功能,它包括数据成员name、semesterHours和average,以及addCourse()函数成员,此外,它还比ds多 导 师 Advisor和 资 格 考 试 分 qualifierGrade。GraduateStudent类作为Student类的派生类,其内存布局如图6-3所示。C+程序设计在该例中,子类以public方式继承。下

229、面分别对类的3种继承方式进行讨论。1)公有继承当继承方式为公有继承时,基类成员的访问属性在派生类中不变,即基类的public成员在派生类中的访问属性仍是public,protected成员在派生类中的访问属性仍是protected,而基类的private成员在派生类中是不可访问的。C+程序设计图6-3C+程序设计例6.2公有继承举例。#includeclassAprivate:intx;public:voidSetx(inti)x=i;voidShowx()coutxendl;C+程序设计classB:publicAprivate:inty;public:voidSety(inti)y=i;v

230、oidShowy()Showx();Coutyendl;C+程序设计;voidmain()Bb;b.Setx(10);b.Sety(20);b.Showy();执行该程序,输出结果如下:1020C+程序设计说明:(1)本例中类A有1个数据成员x和2个函数成员Setx()、Showx();类B有2个数据成员x、y和4个函数成员Setx()、Showx、Sety()、voidShowy();(2)派生类以公有继承方式继承了基类,并不说明派生类可以访问基类的private成员。例如,若将上述程序中派生类B的Showy()函数的实现改写为如下形式是不正确的。C+程序设计voidB:Showy()cou

231、tx,yendl;派生类成员函数访问基类私有成员x,是非法的。C+程序设计(3)在派生类中说明的名字支配基类中声明的同名的名字,即如果在派生类的成员函数中直接使用该名字,则表示使用派生类中声明的名字。例如:classXpublic:voidf();classY:publicXC+程序设计public:voidf();voidg();voidY:g()f(); /表示被调用的函数是Y:f(),而不是X:f()对派生类的对象的引用,也有同样的结论。C+程序设计例如:Yobj_y;Obj_y.f();/被调用的函数是Y:f()如果要使用基类中声明的名字,则应使用作用域运算符限定。例如:obj_y.X

232、:f();/被调用的函数是X:f()C+程序设计2)私有继承当类的继承方式为私有继承时,将基类的public(公有)和protected(保护)成员在派生类中的访问属性调整为private(私有),即基类的public和protected成员被继承后作为派生类的私有成员,派生类的其它成员可以直接访问它们。但是,在类外部派生类的对象无法访问基类的private成员,即不允许派生类的成员函数访问基类的私有成员。C+程序设计经过私有继承之后,所有基类的成员都成为派生类的成员,如果进一步派生的话,新的派生类的成员函数就不能访问已变成私有的基类的成员。因此,私有继承之后,基类的私有成员再也无法在以后的派

233、生类中发挥作用。由于这种原因,一般情况下较少使用私有继承。下面通过程序实例进一步说明有关私有继承的概念。C+程序设计例6.3在私有继承情况下,派生类访问基类成员。#includeclassAprivate:intx;public:voidsetx(inti)x=i;voidshowx()coutxendl;C+程序设计classB:privateAprivate:inty;public:voidsetxy(intm,intn)setx(m);/派生类成员函数访问基类公有成员函数,是合法的y=n;voidshowxy()C+程序设计showxy();coutyendl;voidmain()Aa;

234、a.setx(5);a.showx();Bb;/定义派生类对象bb.setxy(10,20);b.showx();/试图访问派生类的私有成员,非法!b.showxy();C+程序设计说明:(1)程序中的派生类B私有继承了基类A的所有成员,B的成员函数可以访问基类的公有成员函数,因此,在B的成员函数setxy()中访问A的公有成员函数setx()是合法的。(2)由B私有继承A得到的成员函数的访问控制属性是私有的,因此,在main()函数中b.showxy();调用非法。C+程序设计3)保护继承当类的继承方式为保护继承时,将基类的public成员和protected成员在派生类中的访问属性调整为p

235、rotected,即基类的public和protected成员被继承以后作为派生类的保护成员,而基类的private成员仍然是不可访问的。这样,派生类的其它成员函数就可以直接访问它们,但在类外部通过派生类的对象无法访问。C+程序设计对于私有继承和保护继承的直接派生类,基类的私有成员都是不可见的,基类保护成员和公有成员是可见的;对派生类对象而言,所有成员都不可见。私有和保护两种继承方式的效果似乎完全相同,但是,当派生类作为新的基类继续派生时,二者的区别就出现了。假设类A以私有继承方式派生类B,类B又派生类C,那么类C的成员函数和对象都不能访问间接从A类中继承来的成员。C+程序设计如果类A以保护继

236、承方式派生类B,那么类A中的公有和保护成员在类B中都是保护成员。而类B派生出类C后,类A中的公有和保护成员被类C继承,该公有和保护成员有可能是保护的或者是私有的(视类C对类B的继承方式而定)。因此,类C的成员函数有可能可以访问间接从类A中继承来的成员。由上述讨论可知类中保护成员的特征。对于类的保护成员,像私有成员一样从类的外部是不可访问的。C+程序设计例如:classAprivate:intx;/私有数据成员protected:inty;/保护数据成员;如果主函数为:voidmain()C+程序设计Aa;a.x=10;/非法a.y=20;/非法如果类A以公有或保护继承方式派生子类B,则对于该子

237、类B来讲,保护成员与公有成员具有相同的访问属性,也就是说,A类中的保护成员可以被派生类B的成员函数访问,但是不能在类外部通过派生类B的对象来访问。C+程序设计classAprotected:intx;public:voidfun();classB:protectedApublic:C+程序设计voidfunction();voidB:function()x=10;/在派生类的成员函数中访问基类的保护数据成员是合法的fun();/在派生类的成员函数中访问基类的公有数据成员是合法的voidmain()C+程序设计Bb;b.x=5;/从类的外部访问派生类的保护数据成员,非法b.fun();/从类的外

238、部访问派生类的保护函数成员,非法C+程序设计4)保留字protected的意义类继承的目的是引入新的特性和重用。为了利用基类提供的资源,通常希望派生类定义的新方法能够操纵基类的数据成员,也就是说,产生了从基类外部访问基类(私有)数据成员的需求。C+通过protected访问权限提供了一种有条件地打破封装的机制,即只向自己的后代开放数据成员的访问权限。由于封装的目的是修改的局部化,即只要类的接口不变,类可以对内部的数据成员类型及函数实现代码作任意的修改。C+程序设计封装打破后,类就不能再作这种随意的修改,否则,派生类成员函数继续以修改前的方式工作必然会发生错误。为此,基类将可以被派生类修改的数据

239、成员说明为保护的,是为了提醒基类的这些成员要么不修改,要么连同所有的派生类一起修改。C+程序设计从前面的讨论中可以总结出保留字protected的用法如下:(1)有条件打破封装,使派生类的成员函数能够访问基类的私有成员。(2)作为访问控制权限的protected应该出现在基类中而不一定出现在派生类中。(3)作为继承方式的protected的特殊之处是可将公有(方法)成员变成保护成员,使它们只能被用于直接派生类定义新的函数成员中,而不能从派生类外直接访问。根本的作用还是在于提醒派生类在修改这些受保护的成员时应注意对其后代可能产生的影响。表6.1中列出了不同继承方式下的基类成员在派生类中的访问权限

240、。C+程序设计表6.1不同继承方式下的基类成员在派生类中的访问权限继承方式基类中的访问权限派生类中的访问权限公有继承publicpublicprotectedprotectedprivate不可访问保护继承publicprotectedprotectedprotectedprivate不可访问私有继承publicprivateprotectedprivateprivate不可访问C+程序设计6.1.4公有继承下的赋值兼容规则所谓赋值兼容规则,是指不同类型的对象间允许相互赋值的规定。在面向对象程序设计语言中的公有派生的情况下,允许将派生类的对象赋值给基类的对象,但反过来却不行,即不允许将基类的对

241、象赋值给派生类的对象。这是因为一个派生类对象的存储空间总是大于它的基类对象的存储空间。若将基类对象赋值给派生类对象,该派生类对象中将会出现一些未赋值的不确定成员。C+程序设计允许将派生类的对象赋值给基类的对象,有以下3种具体作法:(1)直接将派生类对象赋值给基类对象,例如:BaseobjB;DerivedobjD;/假设Derived已定义为Base的派生类ObjB=objD;/合法ObjD=objB;/非法(2)定义派生类对象的基类引用,例如:Base&b=objD;C+程序设计(3)用指向基类对象的指针指向它的派生类对象,例如:Base*pb=&objD;使用时还应注意:当派生类对象赋值给

242、基类或基类指针后,只能通过基类名或基类指针访问派生类中从基类继承来的成员,不能访问派生类中的其它成员。C+程序设计6.2 派生类的构造和析构派生类的构造和析构6.2.1派生类构造函数派生类对象的数据结构由基类中继承的数据成员和派生类新定义的数据成员共同组成。在派生类对象中由基类继承的数据成员和成员函数所构成的封装体称为基类子对象,它应该由基类的构造函数进行初始化。C+程序设计由于构造函数不能被继承,派生类的构造函数除了对派生类新增数据成员进行初始化外,还需要承担为基类子对象初始化的任务,即为基类的构造函数提供参数。另外,对于含有其它类对象成员的派生类,还要负责为这些对象成员的初始化提供参数,以

243、完成派生类对象的整个初始化工作。C+程序设计派生类可以直接访问基类的保护数据成员,甚至在构造时初始化它们,但是一般不这么做,而是通过基类的接口(函数成员)去访问他们,初始化也是通过基类的构造函数进行的。这样做的好处是,一旦基类的实现有错误,只要不涉及接口,基类的修改就不会影响派生类的操作。类与类之间,以接口作沟通,即使基类与子类也不例外。这正是类能够发挥其生命力的原因所在。在构造一个子类时,由基类的构造函数完成其基类部分的构造,C+类的继承机制提供了这种支持。C+程序设计派生类构造函数的定义格式如下:派生类名:派生类名():对象成员1(),.,对象成员n(),对象成员n+1()函数体;/派生类

244、新增数据成员的初始化说明:在派生类构造函数的总参数表中,应给出初始化基类子对象、派生类对象成员及派生类新增数据成员所需要的全部参数。在参数表之后,列出需要使用参数进行初始化的基类名,对象成员名及各自的参数表,各项之间使用逗号分隔。C+程序设计派生类构造函数执行的一般顺序如下:(1)基类构造函数;(2)派生类对象成员的构造函数(如果有的话);(3)派生类构造函数体中的内容。C+程序设计例6.4派生类构造函数的使用。#includeclassAprivate:inta;public:A(intx)a=x;coutAsconstructorcalled.endl;Voidshow()coutaend

245、l;C+程序设计;classBprivate:intb;public:B(intx)b=x;coutBsconstructorcalled.endl;Intget()returnb;classC:publicAC+程序设计private:intc;Bobj_b;public:C(intx,inty,intz):A(x),obj_b(y)c=z;coutCsconstructorcalled.endl;voidshow()A:show();coutobj_b.get(),cendl;C+程序设计voidmain()Cc1(1,2,5),c2(3,4,7);C1.show();C2.show();

246、C+程序设计执行该程序,输出结果如下:Asconstructorcalled.Bsconstructorcalled.Csconstructorcalled.Asconstructorcalled.Bsconstructorcalled.Csconstructorcalled.12,534,7C+程序设计说明:(1)程序中定义了基类A和派生类C,C是A的派生类,继承方式为公有继承;(2)B是C中所包含的对象成员obj_b所属的类;(3)派生类C的构造函数格式如下:C(intx,inty,intz):A(x),obj_b(y)c=z;coutCsconstructorcalled.endl;C+

247、程序设计派生类C的构造函数总参数表中有3个参数:参数x用于初始化基类的数据成员;参数y用于初始化对象成员obj_b;参数z用于初始化类C新增数据成员c。该构造函数的执行顺序:先执行基类A的构造函数,再执行派生类中对象成员obj_b的类B的构造函数,最后执行派生类C的构造函数代码。程序中派生类C的构造函数也可以写成如下形式:C(intx,inty,intz):A(x),obj_b(y),c(z)coutCsconstructorcalled.endl;C+程序设计6.2.2派生类析构函数派生类析构函数的功能是在该类对象释放之前进行一些必要的清理工作。由于基类的析构函数也不能被继承,因此派生类需要

248、定义自己的析构函数。派生类析构函数的定义与一般类的析构函数的定义完全相同。只要在函数体中把派生类新增的非对象成员的清理工作做好,系统就会自己调用基类和对象成员的析构函数来对基类子对象和对象成员进行清理。C+程序设计释放派生类对象时,析构函数的执行顺序是:先执行派生类的析构函数,再执行对象成员类的析构函数(如果派生类有对象成员的话),最后执行基类的析构函数。执行析构函数的顺序与执行构造函数时正好相反,这从例6.4中程序的执行结果中可以看出。例6.5派生类析构函数实例。#includeclassXC+程序设计intx1,x2;public:X(inti,intj)x1=i;x2=j;Voidpri

249、nt()coutx1,x2endl;X()coutXsdestructorcalled.endl;classY:publicXinty;C+程序设计public:Y(inti,intj,intk):X(i,j)y=k;voidprint()X:print();coutyendl;Y()coutYsdestructorcalled.endl;voidmain()Yy1(5,6,7),y2(2,3,4);y1.print();y2.print();C+程序设计程序输出结果如下:5,672,34Xsdestructorcalled.Xsdestructorcalled.Ysdestructorcal

250、led.Ysdestructorcalled.C+程序设计6.2.3派生类构造函数和析构函数的使用中应注意的问题在实际应用中,对派生类构造函数和析构函数的使用应注意以下几个问题:(1)若基类使用缺省构造函数或不带参数的构造函数,则在派生类中定义构造函数时,可以省略对基类构造函数的调用。此时,如果派生类也不需要构造函数,则可不定义构造函数,例如:classAinta;C+程序设计public:A()a=0;A(intx)a=x;Voidprint()coutaendl;classB:publicAintb1,b2;public:B()b1=b2=0;C+程序设计B(intx)b1=x;b2=0;

251、B(intx,inty,intz):A(x),b1(y),b2(z);voidprint()A:print();coutb1,b2endl;说明:派生类B中定义了3个构造函数,前两个构造函数没有显式地调用基类A的构造函数,而是隐含调用了基类A的缺省构造函数,由于前两个构造函数不需要任何参数,因此可在派生类的构造函数中省去对它们的调用。第三个构造函数显式地调用了基类A中的第二个构造函数。C+程序设计(2)当基类构造函数带有一个或多个参数时,派生类必须定义构造函数,提供将参数传递给基类构造函数的途径。在一些情况下,派生类构造函数体可能为空,仅起到参数传递的作用。例如:classXintx1,x2;

252、public:X(inta,intb)x1=a;x2=b;voidprint()coutx1,x2endl;C+程序设计;classY:publicXpublic:Y(intx,inty):X(x,y);voidprint()X:print();(3)由于析构函数是不带参数的,在派生类中是否要定义析构函数与它所属的基类无关,因此基类的构造函数不会因为派生类没有定义析构函数而得不到执行,它们是各自独立的。C+程序设计6.3 多多 态态 性性在程序运行时,能依据参数类型确认调用哪个函数的能力,称为多态性,或称滞后联编。编译时就能确定哪个重载函数被调用的,称为先期联编。C+允许子类的成员函数重载基类

253、的成员函数。例如,在下面的代码中的基类和派生类中都定义了计算学费的成员函数。C+程序设计例6.6先期联欢编。classStudentpublic:floatcalcTuition()/计算学费;C+程序设计classGraduateStudent:publicStudentpublic:floatcalcTuition();voidmain()C+程序设计Students;GraduateStudentgs;s.calctuition();/调用Student:calcTuition()gs.calcTuition();/调用GraduateStudent:calcTuition()派生类中重

254、载了calcTuition()成员函数,学生的学费计算方法与研究生的学费计算方法不同。学生对象s调用小学费计算函数显然指的是Student:calcTuition()成员,而研究生对象gs调用其学费计算函数时,两个重载函数都在自己可使用范围内。C+程序设计C+编译规定:gs.calcTuition()指的是GraduateStudent:calcTuition()。若派生类没有重新定义其calcTuition()函数,则gs.calcTuition()才指的是基类Student:calcTuition()。有时会碰到对象所属的类不能确定的情况,如将例6.6作一改动。C+程序设计例6.7滞后联编

255、。classStudentpublic:floatcalcTuition()/计算学费函数体;;C+程序设计classGraduateStudent:publicStudentpublic:floatcalcTuition()函数体;;C+程序设计voidfn(Student&x)x.calcTuition();voidmain()Students;GraduateStudentgs;fn(s);/计算一下学生s的学费fn(gs);/计算一下研究生gs的学费C+程序设计因为学生和研究生都是学生,所以要求函数fn()的形参应该能接纳这种对象,这是继承机制的起码要求。在函数fn()中有以下问题:以

256、fn(s)调用时,形参x为Student对象,x.calcTuition()则指Student:calcTuition();以 fn(gs)调 用 时 , 形 参 x为 GraduateStudent对 象 , x.calcTuition()应该指的是GraduateStudent:calcTuition()。C+程序设计函数调用的实际参数随应用程序的运行进展而变更,要求函数fn()的行为也要随着变更。这样,函数fn()的运行执行代码就无法在编译时被确定。它有时调用Student:calcTuition(),有时又调用GraduateStudent:calcTuition()。以上总是可用多态

257、性技术来解决。若语言不支持多态,则不能称其为面向对象的语言;只支持类而不支持多态的语言,称为基于对象的语言。C+程序设计6.4 虚虚 函函 数数6.4.1虚函数的使用为了指明某个成员函数具有多态性,用关键字virtual来标志其为虚函数。例6.8虚函数的使用。#includeclassBaseC+程序设计public:virtualvoidfn()coutInBaseclassn;classSubClass:publicBasepublic:virtualvoidfn()coutInSubClassn;C+程序设计;voidtest(Base&b)b.fn();voidmain()Basebc

258、;SubClasssc;coutCallingtest(bc)n;test(bc);C+程序设计coutCallingtest(sc)n;test(sc);运行结果为:Callingtest(bc)InBaseClassCallingtest(sc)InSubClassC+程序设计说明:fn()是Base类的虚函数。在test()函数中,b是基类Base类的传递引用参数,Base类对象和SubClass类对象都可作为参数传递给b,因此,b.fn()的调用要等到运行时,才能确认是调用基类的fn(),还是调用子类的fn()。由于fn()标志为虚函数,因此编译看见b.fn()后,将其作为滞后联编来处

259、理,以保证在运行时确定调用哪个fn()虚函数。C+程序设计编译通常是在先期联编状态下工作的,只有看见虚函数,才能把它作为滞后联编来实现。因为多态性增加了一些数据存储和执行指令的代价,所以能不多态最好。fn()在基类中声明为virtual,该虚函数的性质自动地向下带给其子类,所以SubClass子类中virtual可以省略。C+程序设计例6.9利用虚函数计算圆和长方形的面积。#include#includeclassShapepublic:Shape(doublex,doubley):xCoord(x),yCoord(y);VirtualdoubleArea()return0.0;C+程序设计p

260、rotected:DoublexCoord,yCoord;classCircle:publicShapepublic:Circle(doublex,doubley,doubler):Shape(x,y),radiue(r);VirtualdoubleArea()return3.14*radius*radius;Protected:C+程序设计Doubleradius;classRectangle:publicShapepublic:Rectangle(doublex1,doubley1,doublex2,doubley2):Shpae(x1,y1),x2Coord(x2),y2Coord(y2

261、);virtualdoubleArea();protected:doublex2Coord,y2Coord;C+程序设计doubleRectangle:Area()returnfabs(xCoord-x2Coord)*(yCoord-y2Coord);voidfun(Shape&sp)coutsp.Area()endl;voidmain()Circlec(2.0,5.0,4.0);fun(c);C+程序设计Rectanglet(2.0,4.0,1.0,2.0);fun(t);运行结果为:50.242C+程序设计说明:fun()函数负责计算所有对象的面积。当需要计算对象的面积时,只要调用求面积的

262、Area()函数即可,不管参数是什么类型的对象,C+的滞后联编都会做好这一切。这样,程序就会显得简单,以后要增加一个求新的形状的面积,只需简单地增加一个类,应用程序不用作修改。多态性使得类的设计者要去考虑工作的细节,而且这个细节简单到只需在成员函数上加一个virtual关键字。C+程序设计6.4.2不恰当的虚函数如果在基类与子类中出现的虚函数仅仅是名字相同,而参数类型或返回类型不同,则即使写上了virtual关键字,也不能进行滞后联编。例如,下面的程序中,在派生类中重载了基类的成员函数,尽管标上了virtual,运行中也起不到多态的效果。例6.10不恰当的虚函数。#includeclassBa

263、seC+程序设计public:virtualvoidfn(intx)coutInBaseclass,intx=xendl;classSubClass:publicBasepublic:virtualvoidfn(floatx)C+程序设计coutInSubClass,floatx=xendl;voidtest(Base&b)inti=1;b.fn(i);floatf=2.0;b.fn(f);voidmain()C+程序设计Basebc;SubClasssc;coutCallingtest(bc)n;test(bc);coutCallingtest(sc)n;test(sc);C+程序设计运行结

264、果为:Callingtest(bc)InBaseclass,intx=1InBaseclass,intx=2Callingtest(sc)InBaseclass,intx=1InBaseclass,intx=2C+程序设计说明:基类中的voidfn(intx)和子类中的voidfn(floatx)是两个不同的函数。SubClass类继承了Base类中voidfn(intx)函数,虽然该函数标示为virtual,但它在基类与子类中都指的是同一个成员函数。SubClass自己添加了一个成员voidfn(floatx),也标上了virtual,但由于没有涉及同一个函数的多种行为,即没有涉及多态,因此

265、,对test(Base&b)函数中b.fn(i)成员函数调用的理解就十分简单。C+程序设计编译程序检测到对b.fn(i)的调用,分析出了fn(i)只有在Base类中定义的一个版本,因此,无论是基类对象还是子类对象,调用的都是Base:fn(int)成员,不存在多态,也就无须滞后联编了。接下来编译程序又检测到b.fn(f)的调用。分析出子类对象也是基类对象,因此在test(Base&b)中,b是作为Base类对象被匹配的,b.fn(f)只在Base类中寻求匹配。除非所匹配的函数是多态的,才考虑调用b的对象类型中的虚函数。C+程序设计上述程序的编译分析与运行结果出现问题,关键是因为不恰当的虚函数,

266、没有构成多态,而使编译看作为先期联编。有一种例外,如果基类中的虚函数返回一个基类指针或返回一个基类的引用,子类中的虚函数返回一个子类的指针或子类的引用,则C+将其视为同名虚函数而进行滞后联编。例如,下面的程序中,派生类与基类的成员函数返回类型不同,但仍然起到虚函数的作用。C+程序设计例6.11应用指针和引用的返回实现虚函数。#includeclassBasepublic:virtualBase*afn()coutThisisBaseclass.n;returnthis;C+程序设计classSubClass:publicBasepublic:SubClass*afn()coutThisisSu

267、bClass.n;returnthis;voidtest(Base&x)Base*b;B=x.afn();C+程序设计voidmain()Basebc;SubClasssc;test(bc);test(sc);运行结果为:ThisisBaseclass.ThisisSubClass.C+程序设计6.4.3虚函数的限制将一个类中的所有成员函数都尽可能地设置为虚函数总是有益的。设置虚函数时应注意以下问题:(1)只有类的成员函数才能说明为虚函数而普通函数则不能。这是因为虚函数仅适用于有继承关系的类对象。(2)静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。C+程序设计(3)内联函数不能是

268、虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时仍将其看作非内联的。(4)构造函数不能是虚函数,因为构造时,对象还是一片未定型的空间。只有在构造完成后,对象才能成为一个类的实例。(5)析构函数可以是虚函数,而且通常声明为虚函数。C+程序设计6.5 抽抽 象象 类类6.5.1纯虚函数纯虚函数是在基类中只有说明而没有实现定义的虚函数,它的任何派生类都必须定义自己的实现版本,而普通的虚函数在派生类中可以不重新定义。此时指向派生类对象的指针调用该函数时调用的是基类中定义的版本。C+程序设计纯虚函数的定义形式如下:virtual类型函数名(参数表)=0;基类中的虚函数

269、到底是否设计成纯虚函数取决于基类中的虚函数是否能给出合理的定义。如定义一个四边形类为正方形、矩形、梯形、平行四边形等平面图形类的公共基类,它的求周长函数可定义为边长循环相加。各具体类求周长函数可定义效率更高的实现方法如正方形、菱形用边长4,矩形用(长+宽)2等等。C+程序设计当一个函数说明为虚函数后,若派生类没有对它进行重定义,可以使用基类提供的缺省定义。而若将函数说明为纯虚函数,就没有这种缺省定义,这样,当不同派生类中重定义代码完全相同时,必须重复书写。因此,尽管四边形类中求周长的算法效率不高,也应该给予它的定义,而不要将它设计为纯虚函数。反之,只有当求“图形”面积这种对于基类不可能给出缺省

270、定义的函数,定义为纯虚函数才是合理的。C+程序设计6.5.2抽象类的概念和定义从概念上讲,抽象类是表示一组具有某些共性的具体类的公共特征的类。相对于具体类,它表示更高层次的抽象概念。从语法上讲,含有纯虚函数的类称为抽象类。抽象类中除了纯虚函数外还可以包含其它函数。抽象类只能用来作为派生其它类的基类,而不能创建抽象类的对象,即抽象类只能通过它的派生类来实例化。某个抽象类的派生又称为它的实现类。C+程序设计抽象类不能用来作为参数类型、函数返回类型和显式转换类型。可以声明指向抽象类的指针和引用,指向抽象类的指针可以指向它的派生类以支持运行时的多态性。已知一个抽象类为:classFigurepubli

271、c:virtualvoidarea()=0;C+程序设计根据抽象类的概念和语法,可以判断以下语句的正误:Figurea;/错,不能创建抽象类的对象Figure*ptr;/对,可以声明指向抽象类的指针Figurefunction1();/错,抽象类不能作为函数返回类型Voidfunction2(Figure);/错,抽象类不能作为函数参数类型Figure&function3(Figure&);/对,可以声明指向抽象类的引用C+程序设计例6.12用抽象类实现的菜单程序。#includeclassMenupublic:virtualvoidaction()=0;classItem1:publicMe

272、nupublic:virtualvoidaction()C+程序设计cout新建文件endl;classItem2:publicMenupublic:virtualvoidaction()cout打开文件endl;C+程序设计classItem3:publicMenupublic:virtualvoidaction()cout保存文件endl;classItem4:publicMenuC+程序设计public:virtualvoidaction()cout另存为endl;classItem5:publicMenupublic:virtualvoidaction()C+程序设计cout打印文件e

273、ndl;voidmain()intselect;Menu*fptr5;Fptr0=newItem1;Fptr1=newItem2;Fptr2=newItem3;Fptr3=newItem4;Fptr4=newItem5;C+程序设计docout1newfileendl;cout2openfileendl;cout3savefileendl;cout4savetoanotherfileendl;cout5printfileendl;cout0exitendlselect;if(select=1&selectaction();while(select!=0);C+程序设计由此可见,从语法上看,抽象

274、类是一种特殊的,具有动态的多态性。比起多态类来,抽象类更侧重于表达类的抽象层次。抽象类和它的实现类的关系虽然也是一种继承关系,但这种继承与前面讲的继承有一种质的区别。非抽象类的继承着眼点在于代码重用,称为类继承;抽象类的继承着眼点在于为一组具有某些共性的具体类提供统一的访问接口,称为接口继承。接口继承的目的是为复杂对象提供构造基础。C+程序设计抽象接口是一种特殊的抽象类,它只含有一组纯虚函数而无任何其它成员。抽象接口甚至可以在不同平台上用不同语言实现,是目标代码级重用技术的基础,也是分布式对象技术的实现基础。C+程序设计6.6 编编 程程 实实 例例堆栈、队列、链表等数据结构中的数据通常都是单

275、一类型的。利用类的多态性,可以构造异质的数据结构。所谓异质的数据结构,是指数据单元由不同类的对象组成的结构。例如,一个可以压入不同(长度)对象的堆栈。多态数据结构是面向对象的数据库、多媒体数据库的数据存储基础。C+程序设计本例的程序中定义了基类Phone及其3个派生类BP_user、Fax_user和Mobile_user,它们分别代表拥有不同通讯设备的人员,这几个类具有两个共同的接口insert()和print()。基类和派生类各自的实现代码能正确地创建和打印自己的对象。能够使用相同的对象指针是这几个类的对象的“异”中之“同”,正是这种共性,使他们能够进入同一个数据结构链表中。C+程序设计下

276、面是一个多态(异质)链表实现程序。#include#includeclassPhonefriendclassList;protected:charname20;charcityNo5;C+程序设计charphoneNo10;staticPhone*ptr;Phone*next;public:Phone(char*,char*,char*);virtualvoidinsert();virtualvoidprint();classBP_user:publicPhonecharserver10;C+程序设计charcall10;public:BP_user(char*,char*,char*,cha

277、r*,char*);voidprint();voidinsert();classFax_user:publicPhonecharfax10;C+程序设计public:Fax_user(char*,char*,char*,char*);voidprint();voidinsert();classMobile_user:publicPhonecharmobileNo12;C+程序设计public:Mobile_user(char*,char*,char*,char*);Voidprint();Voidinsert();classListphone*head;public:List()head=0;

278、voidinsert_node(Phone*node);voidremove(char*name);voidprint_list();C+程序设计;/成员函数的实现Phone:Phone(char*name,char*cityNo,char*phoneNo)strcpy(Phone:name,name);strcpy(Phone:cityNo,cityNo);strcpy(Phone:phoneNo,phoneNo);next=NULL;C+程序设计BP_user:BP_user(char*name,char*cityNo,char*phoneNo,char*server,char*call)

279、:Phone(name,cityNo,phoneNo)strcpy(BP_user:server,server);strcpy(BP_user:call,call);Fax_user:Fax_user(char * name,char * cityNo,char *phoneNo,char*fax):Phone(name,cityNo,phoneNo)C+程序设计strcpy(Fax_user:fax,fax);Mobile_user:Mobile_user(char*name,char*cityNo,char*phoneNo,char*mobileNo):Phone(name,cityNo,

280、phoneNo)strcpy(Mobile_user:mobileNo,mobileNo);voidPhone:insert()ptr=newPhone(name,cityNo,phoneNo);C+程序设计voidBP_user:insert()ptr=newBP_user(name,cityNo,phoneNo,server,call);voidFax_userinsert()ptr=newFax_user(name,cityNo,phoneNo,fax);voidMobile_user:insert()ptr=newMobile_user(name,cityNo,phoneNo,mobi

281、leNo);voidPhone:print()C+程序设计coutendlName:namePhone:phoneNocityNo:cityNoendl;voidBP_user:print()Phone:print();CoutBP:server-callendl;voidFax_user:print()Phone:print();C+程序设计CoutFax:faxendl;voidMobile_user:print()Phone:print();CoutMobilephonenumber:mobilename);Phone*current_node=head;Phone*last_node=

282、NULL;while(current_node!=0&strcmp(current_node-name,key)next;node-insert();node-ptr-next=current_node;C+程序设计if(last_node=0)head=node-ptr;elselast_node-next=node-ptr;voidList:remove(char*name)Phone*current_node=head;Phone*last_node=NULL;while(current_node!=0&strcmp(current_node-name,name)!=0)C+程序设计la

283、st_node=current_node;current_node=current_node-next;if(current_node!=0&last_node=0)head=current_node-next;deletecurrent_node;elseif(current_node!=0&last_node!=0)C+程序设计last_node-next=current_node-next;deletecurrent_node;voidList:print_list()Phone*current=head;while(current!=0)current-print();C+程序设计cu

284、rrent=current-next;Phone*Phone:ptr=0;Voidmain()Listpeople;Phonep1(ChenKun,0851,3871186);Phonep2(AZhiGuLi,0991,4847191);Mobile_userp3(ZhangZhiming,0851,685541,13037863048);C+程序设计BP_userp4(DuYajun,028,7722974,96960,2833955);Fax_userp5(LiangTiao,023,65105787,65106879);people.insert_node(&p1);people.ins

285、ert_node(&p2);people.insert_node(&p3);people.insert_node(&p4);people.insert_node(&p5);coutendl链中有五个记录endl;C+程序设计people.print_list();people.remove(AZhiGuLi);people.remove(ChenKun);coutendl删去两条记录print()调用,current为指向当前对象的指针。C+程序设计小结C+支持类的继承机制。继承是面向对象设计的关键概念之一。有了继承,才使面向对象程序设计才真正进入了实用阶段。派生类可以继承基类的所有公有和保护

286、的数据成员和成员函数。保护的访问权限对于派生类来说是公有的,而对于其它的对象来说是私有的。即使是派生类也不能访问基类中私有的数据成员和成员函数。C+程序设计构造函数可以在其函数体之前进行初始化。在进行初始化时构造函数要调用基类的构造函数并把参数传递给它。在派生类中,允许重载基类的成员函数。如果基类中的函数是虚函数,当使用指针或引用访问对象时,将基于实际运行时指针所指向的对象类型来调用派生类的函数。通过加上类名:作为前缀,可以显式的调用基类中的成员。纯虚函数是一个没有定义函数语句的基类虚函数,其值是0。派生类必须为每一个基类纯虚函数提供一个相应的函数定义。C+程序设计第第7章章 重载重载7.1

287、重载的概念重载的概念7.2 运算符重载运算符重载 C+程序设计7.1 重重 载载 的的 概概 念念 重载(overload)是C+语言静态多态性的表现形式,分为函数重载和运算符重载。函数重载由参数形式区分同名函数的不同版本。构造函数重载是函数重载的主要应用之一,它为对象创建提供了灵活方便的内部状态初始化手段。运算符重载使自定义抽象数据类型能像基本数据类型一样用系统已定义的运算符来书写表达式,这就使得对象的使用像内部变量的使用一样自然方便,因而带来了更加自然的系统可扩展性。C+程序设计非面向对象的过程化语言要求每个过程或函数必须具有惟一的调用名。在这种命名约束机制下,许多参数不同但功能相似的函数

288、必须通过不同的函数名调用。比如,在C语言的数学函数库中就有abs(),labs()和fabs()3个函数分别作为求整型、长整型和浮点型数据类型的绝对值。C+语言提供了一种机制,使得对于不同的输入参数,使用统一的函数名定义各种不同输入参数时的实现版本,由编译程序根据函数参数的不同而选择使用相应的实现代码,这就叫做函数的重载。C+程序设计例7.1求绝对值函数abs()的重载。intabs(intx)return(x=0?x:-x);floatabs(floatx)return(x=0?x:-x);longabs(longx)C+程序设计return(x=0?x:-x);doubleabs(doub

289、lex)return(x=0?x:-x);说明:表面上,这4个函数的代码相同,而实际上编译时产生的代码是不同的,程序运行时在内存中的存放位置也是不同的。C+程序设计其实,在C语言中已经存在原始意义上的重载。例如,除法运算符“/”,当两个操作数均为整型时,语义为取商,而当两个操作数中有一个为浮点数或双精度数时,语义为普通有理数除法。这是由于相应于“/”运算符,在两种情况下程序执行的是不同的代码。运算符重载是的一种特殊形式,因为运算符本质上是函数名的特殊形式。C+程序设计7.2 运运 算算 符符 重重 载载 重载运算符是C+的一个特性,它使得程序员可把C+运算符的定义扩展到运算分量是对象的情况。运

290、算符重载的目的是使C+代码更直观、更易读。由简单的运算符构成的表达式常常比函数调用更简洁、易懂。C+程序设计7.2.1运算符重载的概念一个数据类型的定义包括一个值集和一个作用于该值集的操作集。C+在提供内部基本数据类型的同时提供了许多预定义的运算符,用于各种表达式中,如算术运算符、逻辑运算符、比较运算符、取值、取地址、括号等。基本数据类型上的运算符的语义等价为一个函数,如,若声明x,y为int或double等基本类型变量,则表达式x+y可以理解为函数add(x,y)或+(x,y),显然,使用前一种形式比使用后两种形式表达式的书写更简洁、更符合日常习惯。C+程序设计运算符重载实际上是函数重载的特

291、殊形式,C+语言允许系统预定义运算符的语义由程序员重新定义,这一机制称为运算符重载。运算符重载的目的就是用运算符的形式代替自定义类型的函数名,使程序员能以更加自然的方式使用自定义的数据类型,特别是类类型。由程序员重新定义的运算符又称为运算符函数。在一个类中定义的运算符成员函数称类成员运算符,在类之外定义的运算符函数通常使用类的友元函数形式,称为友元运算符。大多数一元运算符和二元运算符都可以重载,但不允许重载三元运算符,不能创建新的运算符。C+程序设计另外,下列运算符不能重载::#?:.以及取指针所指的变量值*等。运算符重载可以改进程序的可读性,但不是非有不可。注意,重载运算符时,不能改变它们原

292、先的优先级顺序,也不能改变运算符所需操作数的个数。C+程序设计7.2.2类成员运算符重载运算符重载的基本语法形式如下:类型类名:operator运算符()/运算符函数头/运算符函数体若将运算符函数头中“operator运算符”部分用普通函数名代替,则与普通函数运算符函数头形式完全相同。Operator是专门用于定义重载运算符的系统保留字。在下面的例子中还可以看到,重载过的运算符还可以再重载。C+程序设计重载一元运算符时参数表为空,当前对象为运算符的单操作数(在函数体中用this指针访问)。重载二元运算符时参数表中有一个操作数,当前对象为运算符的左操作数,参数表中的操作数为运算符的右操作数。注意

293、:左、右操作数的位置在理论上是不能随意互换的,比如,对于减法,操作数位置直接关系到结果的正确性。由于函数add(x,y)x=x+y;改变操作数的值,而在表达式x=x+y求值过程中改变x的值不符合日常习惯,因此,通常的作法是在运算符重载函数中声明一个临时变量(对象)来返回运算结果。C+程序设计例7.2矢量类加法、减法及反向运算符重载。#includeclassvectordoublex,y;public:vector(doublevx,doublevy)x=vx;y=vy;vector()x=0;y=0;vectoroperator+(vectorv1);vectoroperator-(vect

294、orv1);C+程序设计vectoroperator-();voidprint()coutxyn;vectorvector:operator+(vectorv1)vectorv;v.x=x+v1.x;v.y=y+v1.y;returnv;C+程序设计vectorvector:operator-(vectorv1)vectorv;v.x=x-v1.x;v.y=y-v1.y;returnv;vectorvector:operator-()vectorv;v.x=-x;C+程序设计v.y=-y;returnv;voidmain()vectorv1(4.5,-7.8),v2(-1.5,1.2);v1.

295、print();v2.print();v1=v1+v2;v1.print();v2=-v1;v2.print();C+程序设计运行结果为:4.5-7.8-1.51.23-6.6-36.6重载运算符在调用时仍然是靠参数区分的。上例中重载的运算符“-”,若出现在两个数据之间,只要这两个数据为相同的基本数据类型、vector类型或其它重载过的类型,都能正确调用相应的运算代码;若它单独出现在某个基本数据类型或vector类型等重载过的数据前面,则自动执行求“相反数”的运算。C+程序设计7.2.3友元运算符重载在C+语言中,运算符重载更多是使用友元函数实现。这主要基于以下两个原因:第一,C+语言是混合型

296、的面向对象语言,用友元提供了一种有限制地打破类的封装的机制,其目的是增加编程的灵活性,提高函数的访问效率;第二,用成员运算符重载二元运算时,两个操作数必须同为本类型操作数。与两个操作数类型不同时,特别是由于左操作数不属于本类型,而不能隐含使用this指针时,无法实现重载,友元运算符重载为解决这个问题提供了方法。友元运算符的重载语法形式如下:C+程序设计声明:friend返回值类型operator运算符();/在某类中声明的原型定义:返回值类型operator运算符()/运算符函数头/运算符函数体因为友元函数不是某个类的成员函数,在友元函数体中不能出现this保留字,也没有隐含的操作数,所以在一

297、元友元运算符参数表中必须显式声明一个参数,在二元友元运算符参数表中必须显式声明两个函数。C+程序设计例7.3将例7.2中的成员运算符重载改造成友元运算符重载。#includeclassvectordoublex,y;public:vector(doublevx,doublevy)x=vx;y=vy;vector()x=0;y=0;C+程序设计friendvectoroperator+(vectorv1,vectorv2);friendvectoroperator-(vectorv1,vectorv2);friendvectoroperator-(vectorv1);voidprint()cou

298、txyn;vectorvector:operator+(vectorv1,vectorv2)C+程序设计vectorv;v.x=v1.x+v2.x;v.y=v1.y+v2.y;returnv;vectorvector:operator-(vectorv1,vectorv2)vectorv;v.x=v1.x-v2.x;v.y=v1.y-v2.y;returnv;C+程序设计vectorvector:operator-(vectorv1)vectorv;v.x=-v1.x;v.y=-v1.y;returnv;voidmain()vectorv1(4.5,-7.8),v2(-1.5,1.2);v1.

299、print();C+程序设计v2.print();v1=v1+v2;v1.print();v2=-v1;v2.print();运行结果为:4.5-7.8-1.51.23-6.6-36.6C+程序设计7.2.4重载增量运算符增量运算符有两种使用方式:前缀方式+x、-x和后缀方式x+、x-。+和-的重载方式是一样的,下面以+为例说明增量运算符的重载方法。表达式+x返回x加1以后的值,x+返回x加1以前的值,+运算符重载时用不同的参数形式区分这两种形式。C+约定,前缀方式用operate+()定义,是一个普通一元成员运算符重载;后缀方式用operate+(int)定义,用的是二元成员运算符重载的形式

300、。C+程序设计例7.4用成员形式实现+运算符重载。#includeclassintegerlongi;public:integer(longa=0)i=a;integeroperator+();integeroperator+(int);voidprint()coutiendl;C+程序设计integerinteger:operator+()i+;return*this;integerinteger:operator+(int)integerj;j.i=i+;returnj;voidmain()C+程序设计integerx(100000),y(200000),s;z=+x;x.print();

301、z.print();z=y+;y.print();z.print();C+程序设计运行结果为:100001100001200001200002C+程序设计例7.5利用引用返回实现前缀方式和后缀方式。#includeclassIncreasepublic:Increase(intx):value(x);Increase&operator+();/前缀方式Increaseoperator+(int);voiddisplay()coutthevalueisvalueendl;C+程序设计protected:intvalue;Increase&Increase:operator+()value+;/先

302、增量return*this;/再返回原对象IncreaseIncrease:operator+(int)C+程序设计Increasetemp(*this);/临时对象存放原有对象值value+;/原有对象增量修改returntemp;/返回原有对象voidmain()Increasen(20);n.display();(n+).display();n.display();+n;C+程序设计n.display();+(+n);n.display();(n+)+;n.display();运行结果为:thevalueis20thevalueis20thevalueis21thevalueis22th

303、evalueis24thevalueis25C+程序设计例7.6用非成员形式实现+运算符重载。#includeclassIncreasepublic:Increase(intx):value(x);friendIncrease&operator+();/前缀方式friendIncreaseoperator+(int);voiddisplay()coutthevalueisvalueendl;C+程序设计protected:intvalue;Increase&operator+(Increase&a)a.value+;/先增量returna;/再返回原对象Increaseoperator+(In

304、crease&a,int)C+程序设计Increasetemp(a);/临时对象存放原有对象值a.value+;/原有对象增量修改returntemp;/返回原有对象voidmain()Increasen(20);n.display();(n+).display();C+程序设计n.display();+n;n.display();+(+n);n.display();(n+)+;n.display();C+程序设计运行结果为:thevalueis20thevalueis20thevalueis21thevalueis22thevalueis24thevalueis25由例7.6可见,前缀方式和

305、后缀方式增量运算符的定义以及成员形式与非成员形式稍有不同,但前缀方式和后缀方式增量运算符的使用完全相同。C+程序设计7.2.5重载赋值运算符重载赋值运算符与重载其它运算符类似。下面的程序提供了赋值运算符作为Name类的公有成员,以使主函数(普通函数)中两个对象之间可以互相赋值。例7.7重载赋值运算符。#include#includeclassNameC+程序设计public:Name()pName=0;Name(char*pn)copyName(pn);Name(Name&s)copyName(s.pName);Name()deleteName();Name&operator=(Name&s)

306、deleteName();copyName(s.pName);return*this;C+程序设计voiddisplay()coutpNameendl;protected:voidcopyName(char*pN);voiddeleteName();char*pNmae;voidName:copyName(char*pN)pName=newcharstrlen(pN)+1;if(pName)strcpy(pName,pN);C+程序设计voidName:deleteName()if(pName)deletepName;pName=0;voidmain()C+程序设计Names(claudett

307、e);Namet(temporary);t.display();t=e;t.display();运行结果为:temporaryclaudetteC+程序设计Name类在存储区中保留了一个人的名字,在构造函数中,该存储区是从堆中分配来的,存在浅拷贝问题,必须自定义赋值运算符与拷贝构造函数。赋值运算符以operator=()的名称出现,看起来像一个析构函数后面跟着拷贝构造函数。赋值运算符有两部分:第一部分与析构函数类似,用于取消对象已经占用的资源;第二部分与拷贝构造函数类似,用于分配新的资源。C+程序设计C+为每个类生成了一个缺省的赋值运算符,其功能是将赋值号左边的对象的数据成员逐个拷贝到赋值号右

308、边的类对象中,缺省的赋值运算符采用的这种复制对象的方法称为浅复制,在对象成员含有指针或数组的情况下,浅复制会出问题。此时就应该为类编写用户定义的赋值运算符重载函数,实现正确的复制。C+程序设计例7.8浅复制对象赋值。#include#includeclassstrchar*pc;public:str(constchar*s)intn=strlen(s);pc=newcharn+1;C+程序设计strcpy(pc,s);voidprint()coutpcendl;voidmain()stra1(first);stra2(second);a1.print();a1=a2;a1.print();C+

309、程序设计说明:在这个例子中,语句a1=a2;实现调用缺省赋值运算符操作,将对象a2的数据成员逐一复制给a1的各数据成员。当a2的字符串指针变量pc的值复制给a1的指针pc之后,a1的pc将与a2的pc指向同一个字符串,致使a1原来指向的字符串变成一块无法访问和释放的内存垃圾。同时,在释放对象a1和a2时,a2原来指向的字符串将被释放两次。以上问题都需要通过用户定义赋值运算符重载的深复制策略来解决。C+程序设计例7.9深复制对象赋值。#include#includeclassstrchar*pc;public:str(constchar*s)intn=strlen(s);pc=newcharn+

310、1;C+程序设计strcpy(pc,s);coutConstractorcalled!endl;voidstr:operator=(conststr&a);/赋值运算符重载定义voidprint()coutpcendl;str()coutDestractorcalledendl;voidstr:operator=(conststr&a)if(this=&a)return;C+程序设计deletepc;/释放原来的字符串intn=strlen(a.pc);pc=newcharn;/按新长度为字符串分配内存strcpy(pc,a.pc);/字符串拷贝voidmain()C+程序设计stra1(fi

311、rst);stra2(second);a1.print();a1=a2;a1.print();C+程序设计运行结果为:Constractorcalled!Constractorcalled!firstsecondDestractorcalledDestractorcalledC+程序设计说明:(1)深复制方法是将原来的指针释放后重新分配,然后再进行数据复制,避免了内存垃圾和同一内存块重复释放的问题。(2)赋值运算目的就是改变左操作数的值,因此函数体中不需要引入临时对象。注意:对象初始化时的赋值和已创建对象之间的赋值虽然都涉及对象的复制,但它们是两个不同的操作。对象初始化是首先为新对象分配存储空

312、间,然后执行对象复制。C+程序设计如:Pointp1,p2(3.14,4.06);Pointp3=p2;/调用拷贝构造函数P1=p2;/调用赋值运算符重载所以,对于应该采用深复制的对象,用户应该同时为它的类定义拷贝构造函数。C+程序设计7.2.6函数调用运算符()的重载函数调用运算符operator()被看作是一个二元运算符,必须重载为某一个类的成员函数。其一般使用格式为:对象名()其中,对象名为左操作数,为右操作数。若obj是类class1的对象,且class1中重载了函数调用运算符operator(),则函数调用obj(arg1,arg2,.);被解释为obj.operator(arg1,

313、arg2,.);。C+程序设计对于比较复杂的类中频繁使用的复杂算法,使用重载函数调用运算符能够有效地简化程序行的书写。例7.10定义一个一元二次方程类,通过重载函数调用运算符实现方程求解。#include#includeclassax2bxcdoublea,b,c;public:ax2bxc()a=1;b=1;c=1;C+程序设计voidax2bxc:operator()(doubleA,doubleB,doubleC);/重载函数调用运算符求解一元二次方程;voidax2bxc:operator()(doubleA,doubleB,doubleC)a=A;b=B;c=C;if(a=0)cou

314、t错误,a的值不能为0endl;elseC+程序设计cout方程(a)x2+(b)x+(c)=0的解是endl;if(b*b-4*a*c)0)cout无实数解endl;elseif(b*b-4*a*c)=0)coutx1=x2(-b/(2*a)endl;elsecoutx1=(-b+sqrt(b*b-4*a*c)/(2*a)endl;coutx1=(-b-sqrt(b*b-4*a*c)/(2*a)endl;C+程序设计voidmain()ax2bxcsolution;solution(4,4,1);solution(2,-3,-1);solution(1,1,1);solution(0,1,2

315、);solution(2,3,1);C+程序设计运行结果为:方程(4)x2+(4)x+(1)=0的解是x1x2-0.5方程(2)x2+(-3)x+(-1)=0的解是x1=1.78x2=-0.28方程(1)x2+(1)x+(1)=0的解是无实数解错误,a的值不能为0方程(2)x2+(3)x+(1)=0的解是x1=-0.5x2=-1C+程序设计7.2.6编程实例下面程序用成员函数和运算符成员函数计算利率。#includeclassRMBpublic:RMB(doubled)yuan=d;jf=(d-yuan)/100;RMBinterest(doublerate);RMBadd(RMBd);C+程

316、序设计voiddisplay()cout(yuan+jf/100.0)输入流istrstream串输入流类C+程序设计输出流类ostream输出流基础类,将流缓冲区中的数据作格式化和非格式化之间的转换。并输出ofstream文件输出流类ostream_withassignCout、cerr、clog的输出流类,即操作符输出流ostrstream串输出流类文件流类fstreambase文件流基础类串流strstreambase串流基础类输入/输出流类iostream多目的输入/输出流类的基础类fstream文件流输入/输出类strstream串流输入/输出类C+程序设计在iostream.h头文

317、件中定义有cin、cout和cerr等流类对象,分别表示标准输入设备键盘、标准输出设备显示器和保存错误信息的设备显示器。标准设备见表8.2。表8.2标准设备数C+名字设备C中的名字默认的含义cincoutcerrclog键盘屏幕屏幕打印机stdinstdoutstderrstdprn标准输入标准输出标准错误打印机C+程序设计8.2 C+的输入输出的输入输出 输入/输出流可以从文件或设备中读出或写入数据,C运行库提供了两种输入/输出功能:(1)把数据作为单字符流处理。可以处理简单的数据,也可以处理复杂的数据结构。(2)直接调用操作系统的底层功能实现输入/输出操作。这两种输入/输出功能都含文件和标

318、准的输入/输出功能。这里着重讨论流类的标准输入/输出、标准错误输出以及文件输入/输出。C+程序设计8.2.1标准输入输出C+用istream_withassign类和ostream_withassign类来实现标准输入/输出功能。标准输入与输出是指读键盘的数据和将数据输出到屏幕。在iostream.h文件中用以下两条语句定义cin和cout两个标准对象:istream_withassigncin;ostream_withassigncout;C+程序设计8.2.2标准错误输出cerr当程序测试并处理关键错误时,若不希望程序的错误信息从屏幕显示,而是重定向到其它地方,这时使用cerr流显示信息。

319、例8.1在除法操作不能进行时显示一条错误信息。#includevoidfn(inta,intb)C+程序设计if(b=0)cerrzeroencountered.Themessagecannotberedirected.;elsecouta/bendl;voidmain()fn(20,2);fn(20,0);C+程序设计运行结果为:10zeroencountered.Themessagecannotberedirected.说明:主函数第一次调用fn()函数时,没有碰到除0运算,于是得到文件的写内容10,第二次调用fn()函数时,碰到了除0运算,于是在屏幕上输出错误信息。写到cerr上的信息是

320、不能被重定向的,因此它只能在屏幕上显示。C+程序设计8.2.3输入/输出格式控制在程序中有时需要关心输入/输出的格式,C提供了两种格式控制函数:一种是ios类中定义的格式控制成员函数;另一种是基于流对象的操纵函数。相比之下,使用操纵函数更为方便。1.ios类中定义的格式控制标志ios类中定义了一个数据成员和一个格式控制标志字longx_flags。x_flags的每一位的状态值用枚举符号常量定义,其具体含义如下:C+程序设计enumskipws=0x0001,/跳过输入空格left=0x0002,/按左对齐格式输出right=0x0004,/按右对齐格式输出internal=0x0008,/输

321、出符号和基指示符后的填补dec=0x0010,/转换为十进制(In/Out)oct=0x0020,/转换为八进制(In/Out)hex=0x0040,/转换为十六进制(In/Out)showbase=0x0080,/输出显示基指示符C+程序设计showpoint=0x0100,/输出显示小数点uppercase=0x0200,/大写十六进制输出showpos=0x0400,/正整数显示前加上“”scientific=0x0800,/输出用科学表示法表示的浮点数fixed=0x1000,/输出用固定小数点表示的浮点数unitbuf=0x2000,/在输出操作后刷新所有流stdio=0x4000/

322、在输出操作后刷新stdout和stderr这些标志可以由ios类的成员函数flags()、setf()和unsetf()访问,也可以用操纵函数操作。C+程序设计2.操纵函数和操纵符操纵函数分为带参数和不带参数两种。不带参数的操纵函数又叫操纵符,定义在头文件iostream.h中,带 参 数 的 操 纵 函 数 定 义 在 头 文 件 iomanip.h中 。Fostream.h中的操纵符如表8.3所示。Jomanip.h中的操纵函数如表8.4所示。C+程序设计表8.3iostream.h中的操纵符操纵符用法举例结果说明deccoutdecdecintvar;将整数转化为十进制格式输入hexco

323、uthexhexintvar;将整数转化为十六进制格式输入octcoutoctoctintvar;将整数转化为八进制格式输入wscinws忽略输入流中的空格endcoutendl;插入换行符,刷新流ebdscoutends;插入串最后的串结束符flushcoutflush;刷新一个输入流C+程序设计表8.4iomanip.h中的操纵函数操纵符用法举例结果说明setprecision(int)coutsetprecision(15)输入精度为15位小数的浮点数setw(int)coutsetw(6)setw(24)buf;输入宽度为24的数据setiosflags(long)coutsetiof

324、lags(ios:oct|ios:skipws)指定数据输入的格式为八进制格式且跳过输入中的空白reseriosflags(long)coutreseriosflags(ios:hex)取消数据输入的十六进制格式C+程序设计例8.2操纵符(函数)的使用。#includevoidmain()intx=56;coutsetw(6)x;coutsetw(6)octx;coutsetw(6)hexxendl;cout.setf(ios:left);coutsetw(6)decx;C+程序设计coutsetw(6)octx;coutsetw(6)hexxendl;cout.unsetf(ios:left

325、);coutsetw(6)decx;coutsetw(6)octx;coutsetw(6)hexxendl;运行结果为:567038567038567038C+程序设计8.3 文件流与文件操作文件流与文件操作8.3.1标准库文件函数输入/输出标准C+兼容C语言的文件输入输出功能,它们是以函数的形式给出的。常用的文件输入/输出函数有fopen、fclose、fwrite和fread等。标准的C库文件输入输出函数定义在stdio.h头文件中,并且定义了一个FILE型文件结构。C+程序设计在每一个文件被打开时,都有一个FILE型文件指针与之关联,以便保存文件的相关信息,完成文件的读写操作。对文件的操

326、作一般分为3个步骤:l使用文件打开函数打开文件,并与文件指针建立关系;l利用文件指针和写文件、读文件函数对文件进行操作;l使用文件关闭函数后再关闭文件。1.打开文件函数fopen可使用fopen函数完成文件操作第一步,该函数定义为:FILE*fopen(constchar*filename,constchar*mode);C+程序设计其中,参数filename是文件名字符串,mode为文件打开的模式。使用fopen函数时应注意:(1)返回值是返回打开文件的指针,如果文件不存在或者不能找到文件,则fopen调用返回空指针(NULL),表示文件打开失败。C+程序设计(2)参数mode可取以下值:l

327、“r”:以只读方式打开文件;l“w”:以写方式打开文件,如果文件已经存在,其内容将被破坏;l“r+”:用读和写的方式打开文件(文件必须已经存在,否则将导致异常);l“w+”:用读和写的方式打开一空文件,如果该文件已经存在,其内容将被破坏;l“a+”:为了读文件和附加新内容而打开文件;l“t”:以以文本方式打开文件;l“b”:以二进制方式打开文件。C+程序设计2.文件读写函数fread和fwrite第二步操作就是要对文件进行读和写数据。一般常用fread函数和fwrite函数完成该步操作,它们的使用格式为:size_t fread(void * buffer,size_t size, size_

328、t count,FILE *stream);size_tfwrite(constvoid*buffer,size_tsize,size_tcount,FILE*stream);使用fread和fwrite函数时应注意以下几点:(1)fread函数读文件后返回文件数据记录的数目。(2)fwrite函数写文件后返回实际写入文件数据记录的数目。C+程序设计(3)参数buffer指向数据缓冲区。fread函数将读出的数据放在缓冲区中供程序使用;fwrite函数将待写入文件的数据放在缓冲区中以便写入文件。(4)size为从文件中读出和写入文件的字节数。(5)count为一次从文件读出数据的最大记录数和一

329、次写入文件的最大记录数。(6)stream为文件结构指针,在fread函数中为打开文件的指针;在fwrite函数中为把数据要写入文件的指针。C+程序设计3.关闭文件函数fclose可以使用fclose完成第三步操作,该函数的使用格式为:intfclose(FILE*stream);/关闭流文件int_fcloseall(void);/关闭所有流文件使用fclose函数时应注意以下几点:(1)fclose函数返回值为0时表示文件关闭成功,否则返回值为非0。(2)fcloseall成功关闭所打开的文件时,函数返回值为关闭文件的总数。(3)fclose函数的参数为指向FILE结构体的指针。C+程序设

330、计例8.3建立一个名为FREAD.OUT的文件并写入25个字符,然后从该文件中读出这25个字符,输出到屏幕上。#includevoidmain()FILE*stream;charlist30;inti,numread,numwritten;C+程序设计/按文本模式打开文件并写数据if(stream=fopen(FREAD.OUT,W+T)!=NULL)for(i=0;i25;i+)listi=(char)(z-i);numwritten=fwrite(list,sizeof(char),25,stream);printf(Write%ditemsn,numwritten);fclose(str

331、eam);elseprintf(problemopeningthefilen);C+程序设计/读出25个字符if(streamfopen(fread.out,r+t)!=NULL);numread=fread(list,sizeof(char),25,stream);printf(Numberofitemsread=%dn,numread);printf(Contentsofbuffer=%.25sn,list);fclose(stream);elseprintf(Filecouldnotbeopenedn);C+程序设计运行结果为:Write25itemsNumberofitemsread=

332、25Contentsofbuffer=zyxwvutsrqponmlkjihgfedcbC+程序设计8.3.2文件输入/输出流在C中,对文件的输入/输出操作提供了另一种操作方式,即流类库的文件操作,这些类是ofstream与ifstream文件输入/输出流类。对文件的操作也需要8.3.1节讲述的三个步骤。1.文件的输出文件的输出由ofstream完成。ofstream由ostream类继承而来,并继承了它的操作,因此可以利用ofstream重载的操作符函数以及文件打开、从文件中读数据和关闭文件的函数。ifstream也提供了两构造函数:第一个为:ifstream();C+程序设计第二个为:if

333、stream(constchar*filename,intmode=ios:in,intprot=filebuf:openprot);1)文件的打开第一个构造函数用于构造一个不带参数的流,如果需要可以在用它的open函数打开一个文件。open函数的定义如下:voidopen(constchar*filename,intfilemode=ios:in,intaccess=filebuf:openprot);第一个构造函数使用与第二个构造函数相同的文件名打开输入文件。C+程序设计2)文件的数据读出函数可以使用istream类的读数据的函数和操作符从文件中读取数据,下面是读数据函数的原型。/将文件数

334、据读取到字符串内inlineistream&get(char*,int,char=n);inlineistream&get(unsignedchar*,int,char=n);inlineistream&get(signedchar*,int,char=n);C+程序设计/将文件数据读取到字符内istream&get(char&);inlineistream&get(unsignedchar&);inlineistream&get(signedchar&);istream&get(streambuf&,char=n);/将文件一行数据读取到字符串内inlineistream&getline(c

335、har*,int,char=n);inline istream& getline(unsigned char * , int ,char=n);inlineistream&getline(signedchar*,int,char=n);inlineistream&ignore(int=1,int=EOF);C+程序设计/将文件数据读取到字符串内istream&read(char*,int);inlineistream&read(unsignedchar*,int);inlineistream&read(signedchar*,int,char=n);intgcount()constreturn

336、x_gcount;3)文件的关闭函数ifstream类 也 是 从 fstreambase类 继 承 的 , 可 以 使 用fstreanmbase类的close();来关闭文件。C+程序设计3.字符文件操作举例例8.4将100以内的所有奇数存入字符文件a:wr1.dat中。#include#includevoidmain(void)ofstreamf1(a:wr1.dat);/定义输出文件流,并打开相应文件for(inti=1;i100;i+=2)f1i;/向f1文件流输出i值f1.close();/关闭f1所对应的文件C+程序设计例8.5从字符文件a:wr1.dat中依次读出每个整数并按6

337、个一行显示出来。#include#includevoidmain(void)ifstreamf1(a:wr1.dat,ios:in|ios:nocreate);intx,i=1;C+程序设计while(f1x)if(i+%6)coutx;elsecoutxendl;coutendl;f1.close();C+程序设计运行结果为:13579111315171921232527293133353739414345474951535557596163656769717375777981838587899193959799C+程序设计例8.6使用istream和ostream类的文件操作函数把文件fi

338、le.in的内容拷贝到文件file.out中。#include#include#includevoidmain()charch;ifstreamfin(file.in);/创建一个输入流,并与输入文件file.in联系C+程序设计ofstreamfout(file.out);/创建一个输出流,并与输出文件file.out联系if(!fin)cerrCantopenfilefile.in;exit(-1);/如果源文件出错,显示出错信息并退出C+程序设计if(!fout)cerr”又称提取运算符,流的输出运算符“”又称插入运算符,它们都可以重载。下面是一个用户定义的日期类Date。为保持和简单类

339、型的输入/输出同样的形式,需要重载操作符。重载的操作符用于输入Date类的成员变量。由于重载的函数需要访问Date类的私有成员和保护成员,因此应该把这些重载的函数定义为Date类的友元。C+程序设计例8.7在用户定义的Date数据类中对流类运算符进行重载。#includeclassDateintmo,da,inty;public:Date(intm,intd,inty)mo=m;da=d;yr=y;friendostream&operator(istream&is,Date&dt);ostream&operator(ostream&os,Date&dt)osdt.mo/dt.da/(istre

340、am&is,Date&dt)isdt.mo/dt.da/dt.yr;C+程序设计returnis;/返回流的引用voidmain()intmo,da,yr;Datedt(7,1,1997);coutdtmodayr;Dateotherdt(mo,da,yr);coutotherdtendl;C+程序设计从这个例子可以看出,只要定义好了某个类的输入/输出操作符重载,就能很容易地进行此类输入和输出操作,这正是C的一个重要的优点。重载的操作符函数有以下几个特点应该注意:(1)重载的操作符函数有两个参数,第1个参数是出现在左边的操作数,第2个参数是出现在右边的操作数,如果重载的是输出操作符,第1个参数

341、是对流istream类的引用,第2个参数是对要定义的输入/输出类的引用,当然,也可以是该类的指针。C+程序设计(2)运算符重载必须定义为类的友元,因为左操作数必须是流类对象而不是输入/输出类对象,不能使用隐式左操作数。C+程序设计8.5 编编 程程 实实 例例 下面程序利用istream和ostream类的操作符对点数据进行文件操作,从键盘上输入两个点的坐标,把它保存到磁盘文件上,并从磁盘上读出数据,写到显示器上。#include#include#include#includeC+程序设计#include#includeclassPointprotected:intX;intY;public:

342、Point(intx,inty)X=x;Y=y;C+程序设计friendostream&operator(istream&is,Point&aPoint);ostream&operator(ostream&os,Point&aPoint)os(aPoint.X.aPoint.Y(istream&is,Point&aPoint)is(aPoint.X.aPoint.Y);C+程序设计returnis;intmain()intx,y;ofstreamwritefile(crest.sav);if(!writefile)coutCantopenfile;exit(-1);coutxy;PointPo

343、int1(x,y);coutxy;PointPoint2(x,y);writefilePoint1;writefilePoint2;writefile.close();ifstreamreadfile(ctest.sav);if(!readfile)C+程序设计coutPoint1Point2;coutPoint1endl;coutPoint2endl;return0;C+程序设计小结C+的I/O流完全不同于C的I/O系统,它操作更简捷,更容易理解,它使标准I/O流、文件流和串流的操作在概念上统一了起来。有了控制符,C+更具灵活性,由它所重载的插入运算符,完全融入了C+的类及其继承的体系。C+

344、程序设计第第9章章 模板模板9.1 模板的概念模板的概念 9.2 函数模板函数模板9.3 类模板类模板9.4 综合实例综合实例C+程序设计9.1 模板的概念模板的概念 模板是实现类属机制的一种工具,其功能非常强。C+既允许用户构造函数模板,即创建一个通用功能的函数,支持多种不同的形参,也允许用户构造类模板。类模板允许用户为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数和某些成员函数的返回值能取任意类型(包括系统预定义的和用户自定义的)。C+程序设计9.1.1模板的含义在编程时经常会遇到这样的情况:对于不同数据类型的参数需要实现相似的函数功能,例如编写求两个整形数据中最大值的函数与求

345、两个实型数据中最大值的函数,它们的程序逻辑相同,程序代码也相同,只是它们的参数类型与返回值类型不同。对这样的情况,在C语言中程序员不得不定义两个函数,然后将程序代码重复书写一遍。同样有时也会遇到具有类似功能的类,例如一个整型数据集合的类与一个实型数据集合的类,它们实现的功能相同,但存储的数据类型不同。C+程序设计对于上面的情况,C+语言中可以使用模板来避免在程序中多次书写相同的代码。使用模板可以从一个函数模板生成多个函数或从一个类模板生成多个类。建立一个模板后,编译器将根据需要从模板生成多份代码。C+程序设计9.1.2模板的基本语法C+语言中使用关键字template开始一个模板的声明,模板声

346、明的一般形式为:template模板参数表可以包含一个或多个模板参数声明,如果有多个模板参数,参数与参数之间以逗号隔开。例如,带有一个参数的模板可以以下述形式开头:templateC+程序设计这里的class与类定义中的class没有关系,而表示参数T是一个数据类型。在使用该模板时,T可以用用户定义的数据类型,也可以用C+语言固有的数据类型,如int,float等替换。参数标识符T也可以用其它标识符来表示,例如:template通常的习惯是,表示数据类型的参数用T开,T表示英文Type,跟表示该参数的含义的英文单词。C+程序设计多个参数的模板声明的格式为:template类模板参数除了可以是数

347、据类型外,还可以是其它类型的数据,例如,型数据:template参数的具体使用方法将在后面进行详细介绍。C+程序设计9.2 函函 数数 模模 板板 什么是函数模板?为什么要引入函数模板呢?下面来看一个例子:有一个求两数中较大值的函数max,由于C+是强类型语言,因此定义函数时,不同的数据类型需要分别定义不同的版本。如:intmax(intx,inty)/求两个整型数中的最大值C+程序设计return(xy)?x:y;floatmax(floatx,floaty)/求两个浮点数中的最大值return(xy)?x:y;C+程序设计这些版本的基本内容都是相同的,只是参数类型和函数返回值类型不同,程序

348、的代码有重复;使用重载函数,仅是使用相同函数名,函数体仍然要分别定义。但使用函数模板则可避免重复定义函数体,能使程序显得简洁一些。用函数模板将上述函数重新定义如下:template/模板声明Tmax(Tx,Ty)/定义函数模板return(xy)?x:y;C+程序设计其中,T为类型参数,既可以取系统的预定义类型,又可以取用户自定义类型。这样定义的max代表的是一类函数。如果用max函数进行求两数中较大值的操作,首先必须将模板参数T实例化为一般的类型(如int或float等),从这个意义上说,函数模板不是一个完全的函数,它代表了一类函数,必须将它的模板参数T实例化后,才能完成具体函数的功能。把类

349、型参数T实例化后的参数称为模板实参,用模板实参实例化的函数称为模板函数。因此,函数模板可以用来创建一个通用功能的函数,支持多种不同的形参,简化重载函数的函数体设计。C+程序设计函数模板的一般定义形式如下:template类型名函数名(形式参数表)函数体;其中,类型参数表可以包含基本数据类型,也可以包含类类型(前面要加class)。这样的定义函数模板,不是一个实实在在的函数,只是对函数的描述。为了使用函数,必须把模板参数实例化(用实参生成一个重载函数,其函数体相同)。C+程序设计创建函数模板实例的方式如下:函数名(实参表);例9.1用一个main()函数来调用上面定义的函数模板。#include

350、templateTmax(Tx,Ty)return(xy)?x:y;C+程序设计voidmain()inti=lO,j=56;floatx1,x2;doubley1=673.36,y2=465.972;x1=50.34;x2=56.34;coutthemaxofi,jis:max(i,j)endl;coutthemaxofxl,x2is:max(x1,x2)endl;coutthemaxofyl,y2is:max(y1,y2)endl;C+程序设计这里生成了三个模板函数,其中,max(i,j)用模板实参int将类型参数进行了实例化;max(xl,x2)用模板实参float将类型参数T进行了实例

351、化;max(y1,y2)用模板实参double将类型参数T进行了实例化。从这个例子可以看出,函数模板提供了一类函数的抽象,它以任意类型T为参数及函数返回值,还可以看出,模板函数是函数模板的具体实例。C+程序设计例9.2定义一个求绝对值函数的模板。#includetemplateTabs(Tx)returnx0?-x:x;voidmain()C+程序设计intn=-5;doubled=-5.5;coutabs(n)endl;coutabs(d)endl;C+程序设计9.3 类类 模模 板板类模板代表着一类类,它允许用户为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数和某些成员函数的返

352、回值,能取任意类型(包括系统预定义的和用户自定义的)。如果一个类中数据成员的类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为类模板。C+程序设计类模板的一般定义形式如下:templateclassclassName类声明体;类模板定义不是一个实实在在的类,只是对类的描述(称为类模板)。类模板建立后,可以用下面的方式创建类模板的实例。className;C+程序设计例如:template /声明一个模板classvector/vector是类模板名T*date;/在类定义中,使用通用数据类型的成员或函数参数时 /需要在它前面加上模板参数Tintsize;publi

353、c:vector(int);T&opreator(int);C+程序设计;在类定义体外定义成员函数时,如果该成员函数中有模板参数存在,则需要在函数体外进行模板声明,并且在函数名前的类名后加上“”。例如:templatevector:operator(int);templateT&vector:operator(inti);C+程序设计类模板的使用实际上是将类模板实例化成一个具体的类,它的格式为:类名;例如,下面是一个使用上面定义的类模板的main()函数。voidmain()vectorx(5)for(inti=0;i5;i+)xi=i;for(i=0;i5;+i)coutxi;coutend

354、l;C+程序设计实例化的类vector、vector和vector被称为模板类。模板类是类模板实例化后的一个产物,下面通过例子来学习模板类的使用。例9.3使用类模板实现对任意类型的链表处理。分析:要实现任意类型元素的链表处理,必须使用类型参数,也就是说需要定义模板。用类模板定义的通用链表不是一个类定义,只是类定义的一个框架。下面的头文件定义了一个单向链表的模板类。C+程序设计/头文件link.h#ifndefLIST#defineLIST#includetemplateClassListpublic:List();voidAdd(T&);voidRemove(T&);C+程序设计T*Find(

355、T&);voidPrintlist();List();protected:structNodeNode*pNext;T*pT;Node*pFirst;/链首节点指针;C+程序设计templateList:List()pFirst=0;templatevoidList:Add(T&t)Node*temp=newNode;/动态申请一个节点空间temp-pT=&1/把T类对象挂接在这个节点上C+程序设计temp-pNext=pFirst;/让该节点指向链表的首节点pFirst=temp;/让链表首节点指针pFirst指向该节点templatevoidList:Remove(T&t)Node*q=0

356、;/用来定位待删的结点if(*(pFirst-pT)=t)/T类中的需有定义q=pFirst;C+程序设计pFirst=pFirst-pNext;/删除链首节点elsefor(Node*p=pFirst;p-pNext;p=p-pNext)/顺链查找if(*(p-pNext-pT)=t)q=p-pNext;p-pNext=q-pNext;/使链表中被删除节点前后的两个结点链接起来break;C+程序设计if(q)deleteq-pT;/释放被删除节点上的T类对象的空间deleteq;/释放被删除节点的空间templateT*List:Find(T&t)C+程序设计for(Node*p=pFir

357、st;p;p=p-pNext)if(*(p-pT)t)returnp-pT;return0;templatevoidList:PrintList()for(Node*p=pFirst;p;p=p-pNext)C+程序设计coutpT);/需由T的友元处理T对象输出coutend1;templateList:List()Node*p;while(p=pFirst)pFirst=pFirst-pNext;deletep-pT;C+程序设计deletep;pFirst=0;#endif在例模板类中,嵌套了一个Node结构类型变量,Node结构变量是通用链表类中的链表节点,只在通用链表类的范围内使用,

358、因此定义嵌套在模板类中。请读者分析一下该链表类的析构函数的功能。C+程序设计在使用类模板时,必须先在程序开始的头文件中说明类模板的定义(即加入link.h头文件说明),然后在适当的地方创建一个模板类的实例(即一个实实在在的类定义),并创建该模板类的对象,有了对象名,以后的使用就和普通定义的对象一样。例如,建立例9.3中的类模板程序如下:#includelink.h/引入类模板的定义voidmain()C+程序设计ListfloatList;/创建一个模板类的实例,并创建模板类的对象floatListfor(inti=1;i7;i+)floatList.Add(*newfloat(i+0.6);

359、/建立链表floatList.PrintList();floatb=3.6;float*pa=floatList.Find(b);if(pa)C+程序设计floatList.Remove(*pa);floatList.PrintList();运行结果为:6.65.64.63.62.61.6 (这是建立的链表序列)6.65.64.62.61.6(这是输出序列,想一想,为什么?)C+程序设计由这个例子可见,模板是一种安全的、高效的重用代码的方式。它被用于参数化类型,在创建对象或函数时所传递的类型参数可以改变其行为。每个模板类的实例是一个实际的对象,可以像其它对象一样使用,甚至可以作为函数的参数或返

360、回值。与类和函数的定义不同,类模板和函数模板的定义一般放在头文件中(见例9.3)。C+程序设计9.4 综综 合合 实实 例例下面通过本节的几个有关模板应用的综合例子来体会C+中类对面向对象思想的支持,从而加深对面向对象程序设计方法的理解。C+程序设计例9.4定义四个类:Data为基类,它含有一个可传递一个参数的构造函数,用它来为其私有成员x赋值,并显示一句话;类A中含有一个data的成员对象;类B为类A的派生类,也含有一个data类的成员对象;类C为类B的派生类。调用这些类的对象,观察其构造函数的执行顺序。分析:这是一组较为复杂的继承关系。构造函数的调用严格地按照基类、成员对象、派生类的顺序进

361、行。应该在各类的构造函数中均加上输出本类名的语句,这样可以验证这些构造函数的执行顺序。C+程序设计源程序清单如下:#includetemplateTmax(Tx,Ty)couty)?x:y;intmax(intx,inty)C+程序设计couty)?x:y;charmax(intx,chary)couty)?x:y;voidmain()C+程序设计inti=10;charc=a;floatf=43.74:coutmax(i,i)endl;coutmax(c,c)endl;coutmax(i,c)endl;coutmax(c,i)endl;coutmax(f,f)endl;coutmax(f,I

362、)endl;请读者自行分析程序的运行结果,并上机验证。C+程序设计例9.5用函数模板把多个重载函数简单地归为一个。#includetemplateTmax(Ta,Tb)returnab?a:b;/T类的操作需有定义voidmain()C+程序设计coutMax(4,7)ismax(4,7)endl;coutMax(4,7)ismax(4,7)endl;请读者自行分析程序的运行结果,并上机验证C+程序设计例9.6重载模板函数。#include#includetemplateTmax(Ta,Tb)returnab?a:b;char*max(char*a,char*b)return(strcmp(a

363、,b)?a:b);C+程序设计voidmain()coutMax(Guang,Zhou)ismax(Guang,Zhou)endl;请读者自行分析程序的运行结果,并上机验证。C+程序设计例9.7栈模板。分析:栈又叫堆栈,是一种常用的数据结构,它是一种运算受限的线性表。栈仅允许在表的一端进行插入和删除运算,是一种后进先出表。#includeenumboolFalse,True;/抽象堆栈类模板定义templateclassAbsstackprotected:C+程序设计unsignedheight:public:boolisEmpty()return(height=0)?True:False;v

364、irtualvoidpush(T&)=0;virtualvoidpop(T&)=0;virtualvoidempty()=0;virtualvoidReadTop(T&)=0;/使用数组存储数据元素的堆栈类模板C+程序设计templateclassArraystack:absstackboolError;Tvecsize;public:ArrayStack()Empty();Virtualvoidpush(T&);Virtualvoidpop(T&);VirtualvoidEmpty();VirtualvoidRradtop(T&)Booliserror()returnError;C+程序设计

365、/压栈操作templatevoidArrayStack:push(T&elem)Error=False;If(height=(size-1)Error=True;Return;vecheight=elem;height+;C+程序设计/出栈操作templatevoidArraystack:pop(T&elem)if(height=0)Error=True;Return;Elem=vecheight-1;Height;-/读取栈顶元素C+程序设计templatevoidArraystack:ReadTop(T&elem)if(height=0)Error=True;Return;elem=vec

366、height-1;/置堆栈为空C+程序设计templatevoidArrayStack:Empty()height=0;Error=False;/测试用主函数voidmain()intj;ArrayStacks;C+程序设计s.push(10);s.push(20);s.push(30);s.pop(j);coutjn;s.pop(j);if(s.isError()couterrorn;elsecoutnoerrorn;s.pop(j);if(s.isError()C+程序设计couterrorn;elsecoutnoerrorn;该程序首先定义了一个抽象的堆栈模板类Absstack,该类模板

367、只定义了堆栈类的接口,没有定义存储堆栈数据元素的内部数据结构,可以从其派生出使用不同数据结构存储堆栈数据元素的堆栈类。本例中使用了常见的存储方法数组存储数据元素,然后实现了堆栈的常见运算。C+程序设计模板函数与同名的非模板函数的重载方法均遵循如下约定:(1)寻找一个参数完全匹配的函数,如果找到了就调用它。(2)在(1)失败后,寻找一个函数模板,使其实例化,产生一个匹配的模板函数,若找到了,就调用它。(3)在(1)、(2)均失败后,再试低一级的对函数的重载方法,例如通过类型转换可产生参数匹配等,若找到了,就调用它。(4)若(1)、(2)、(3)均失败,则说明这是一个错误的调用。C+程序设计小结1

368、.模板的概念使用模板可以创建一个具有通用功能且支持多种不同的形参的参数,以避免在程序中多次书写相同的代码。使用模板可以由一个函数模板生成多个函数或由一个类模板生成多个类。使用模板建立一个模板后,编译器将根据需要从模板生成多份代码。模板声明的一般形式为:template说明体C+程序设计2.函数模板使用函数模板可以避免重复定义函数体,仅是使用相同函数名,使程序显得简洁一些。函数模板的一般定义形式如下:template类型名函数名(形式参数表)函数体;创建函数模板实例的方式如下:函数名(实参表);C+程序设计3.类模板类模板代表着一类类,它允许用户为类定义一种模式。如果一个类中数据成员的类型不能确

369、定,或者某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为类模板。类模板的一般定义形式如下:templateclassclassName类声明体;建立类模板后,可以用下面的方式创建类模板的实例:className;C+程序设计4.使用模板应注意的问题使用模板应注意以下几点:(1)在每个模板定义之前,不管是类模板还是函数模板,都需要在前面加上模板声明语句template。(2)在使用模板时,必须在名字后面加上模板参数,如list和node。(3)对象是类的特例,类又可以看作类模板的特例。类模板与函数模板有同样的应用目的。(4)类模板与函数模板不同的是:由函数模板生成的模板函数的调用是由

370、编译器自动决定的,而类模板的解释由程序设计者自行指明;格式定义不同。C+程序设计第第10章章 Visual C+编程实例编程实例10.1 MFC编程流程编程流程10.2 常用常用FC类和消息处理类和消息处理10.3 对话框的应用对话框的应用10.4 菜菜 单单 的的 应应 用用10.5 工具栏应用工具栏应用C+程序设计10.1 MFC编程流程编程流程 在普通的C/C+程序中,可以看到程序从main函数开始到结束的所有代码,但在VisualC+中MFC封装了一部分类,同时也隐藏了一部分代码,因此我们看不到源程序的所有代码,例如从项目的所有源文件中找不到main函数。基本对话框的MFC程序流程图如

371、图10-1所示。C+程序设计图10-1C+程序设计一个MFC程序运行的一般过程如下:(1)生成CwinApp的一个实例(调用CwinApp的构造函数),初始化全局对象;(2)Win32入口程序函数WinMain调用函数AfxWinMain;(3) AfxWinMain调 用 函 数 CwinApp的 成 员 函 数InitInstance;(4)AfxWinMain调用函数CwinApp的成员函数Run;(5)AfxWinMain函数返回到WinMain,WimMain结束,程序结束。C+程序设计例10-1吹泡泡程序。每当用户在窗口客户区中按下鼠标左键时即可产生一个泡泡(彩色圆形)。设计思路:

372、显示一个泡泡所需的数据包括其位置和大小,在MFC中可用其包含矩形表示。可设置一数组,每当用户按下鼠标左键时,就产生一个泡泡的数据并存入数组中。最后,由框架窗口类的OnPaint()函数显示所有的泡泡。C+程序设计#include#defineMAX_BUBBLE250classCMyWnd:publicCFrameWndCRectm_rectBubbleMAX_BUBBLE;intm_nBubbleCount;public:CMyWnd()m_nBubbleCount=0;C+程序设计protected:afx_msg void OnLButtonDown(UINT nFlags,CPoint

373、 point);afx_msgvoidOnPaint();DECLARE_MESSAGE_MAP();/消息映射BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)ON_WM_LBUTTONDOWN()ON_WM_PAINT()END_MESSAGE_MAP()C+程序设计/框架窗口类的成员函数void CMyWnd:OnLButtonDown(UINT nFlags,CPoint point)if(m_nBubbleCountMAX_BUBBLE)intr=rand()%50+10;CRectrect(point.x-r,point.y-r,point.x+r,point.

374、y+r);m_rectBubblem_nBubbleCount=rect;m_nBubbleCount+;InvalidateRect(rect,FALSE);C+程序设计voidCMyWnd:OnPaint()CPaintDCdc(this);CBrushbrushNew;CPenpenNew;brushNew.CreateSolidBrush(RGB(rand()%255,rand()%255,rand()%255);penNew.CreatePen(PS_SOLID,1,RGB(255,0,0);dc.SelectObject(&brushNew);dc.SelectObject(&pe

375、nNew);C+程序设计for(inti=0;iCreate(0,_T(吹彩色泡泡);pFrame-ShowWindow(m_nCmdShow);this-m_pMainWnd=pFrame;returnTRUE;CMyAppThisApp;/全局应用程序对象按【Ctrl+F5】运行程序,运行结果如图10-2所示。C+程序设计说明:该程序声明了两个类,一个是由应用程序类CwinApp派生出来的CmyApp类,一个是由框架窗口CframeWnd类派生出来的CmyWnd类。MFC的基本类名均以字母C打头,习惯上在为使用MFC编写的应用程序中的类起名时也这样做。在程序中还声明了一个CmyWnd类的全

376、局对象ThisApp。C+程序设计图10-2C+程序设计在CmyWnd类中声明了一个数组成员m_rectBubble,用于存放泡泡的数据;另外,还声明了一个整型数据成员m_nBubbleCount,用来存放数组中泡泡的实际数量。在框架窗口类的构造函数中该成员变量被初始化为0,由于构造函数非常简单,因此使用了内联函数的形式。C+程序设计OnPaint()函数用于绘制客户区的内容。要完成这项任各,需要先建立一个设备环境(DeviceContext),这可以通过声明一个CpaintDC类的对象dc实现。在声明语句中,应将当前窗口对象指针this传给CpaintDC类的构造函数,把绘图区域确定为当前窗

377、口的客户区。Windows在窗口更新、移动、改变尺寸或移去覆盖在其上的其它窗口对象时均会向该窗口发送WM_PAINT消息,从而触发应用程序调用OnPaint()函数重绘窗口客户区。C+程序设计OnPaint()函数根据数组m_rectBubble的内容画出一个泡泡。其中语句。dc.SelectStockObject(LTGRAY_BRUSH);用于选择一个库存画刷,画刷决定了所画图形(如椭圆、矩形和多边形等)内部的颜色。在处理鼠标消息的ONLButtonDown()函数中,语句intr=rand()%50+10;随机确定了要画出的泡泡的半径(范围为1050像素点),其中全局函数rand()可产

378、生一个随机整数。C+程序设计调试:首先,使用VisualC+集成开发环境中的菜单选项“文件/新建”,并在项目选项卡中选择“Win32Application。其次,在编译时要确定应用程序的可执行程序如何使用MFC的类库。一种方法是使用共享的动态链接库(DLL)。这种链接方式显著地减小了应用程序的可执行文件的大小,并能有效地利用系统资源。C+程序设计用系统资源。然而,动态链接到MFC时要求提供Mfcnn.dll库文件,文件名中的nn代表MFC的版本号。该文件通常在WindowsSystem或System32文件夹下。如果一个应用程序动态链接到了MFC,但该应用程序通常用在那些可能没有Mfcnn.d

379、ll库的计算机系统上,则应把这个库文件作为应用程序包的一部分提供给用户。Microsoft允许程序员自由地把这些库文件附在应用程序中。C+程序设计另外,也可以选择应用程序静态链接到MFC。静态链接意味着,应用程序不依赖于MFC库文件的存在(但仍然可能需要Msvcrt.dll文件)。静态链接的代价是可执行文件更大,而且MFC内存的利用可能不够充分。在菜单中选择“工程”中的“设置”对话框,在对 话 框 右 方 的 “General”选 项 卡 中 通 过 组 合 框“MicrosoftFoundationClasses”选择使用MFC类库的方法。可选项有三种,分别为“NotUsingMFC(不使用

380、MFC),UseMFCinaSharedDLL(以动态链接库方式使用MFC)”和“UseMFCinaStaticLibrary(以静态库方式使用MFC)”。C+程序设计10.2 常用常用FC类和消息处理类和消息处理10.2.1常用MFC类MFC的类构成了一个完整的体系,该体系由一个被称为Cobject的类作为基类,其它类大部分由该类派生而来,如CWnd(窗口类)、Cdocument(文档类)、Cfile(文件类)等。也有一部分类如字符串类,Cstring和时间类Ctime等则不从Cobject继承。C+程序设计1.窗口公共基类CWnd类CWnd对所有Windows窗口提供功能支持,它是所有窗口

381、类的直接或间接父类。一个CWnd对象和一个Windows窗口是有本质区别的,尽管它们有密切的联系。CWnd对象是一个概念,即类的实例,而Windows窗口则是指向Windows内部数据结构的一个句柄,它的创建和显示要消耗系统资源。一个CWnd对象通过其构造函数被创建,被析构函数销毁,而一个Windows窗口则通过CWnd的Create函数创建,被DestroyWindow函数销毁。C+程序设计1)窗口句柄(1)窗口句柄的创建:当CWnd:Create被调用时,Windows窗口被创建,窗口句柄存放在CWnd的成员变量m_hWnd中。(2)程序中窗口句柄的取得:可以直接利用成员变量m_hWnd,

382、但安全的方法是调用函数CWnd:Get(),它返回与窗口对象相关联的句柄,并且当窗口对象没有关联句柄时或当前CWnd指针为NULL时返回空指针。(3)窗口句柄的销毁:调用CWnd:DestroyWindow。C+程序设计2)窗口的大小和位置(1)IsIconic:窗口是否处在最小化状态。(2)IsZoomed:判断窗口是否在最大化状态。(3)MoveWindow:改变窗口的大小、位置和Z轴顺序。Z轴顺序指本窗口和其他窗口的覆盖关系。C+程序设计3)窗口的状态(1)ShowWindow:显示或隐藏一个窗口,显示可以有多种方式:最大化、最小化、正常显示等。(2)IsWindowEnabled:判断

383、一个窗口是否可见。(3)IsWindowEnabled:判断窗口的禁止/使能状态,禁止状态的窗口不能响应用户的键盘和鼠标的输入。(4)EnableWindow:设置窗口的禁止/使能状态,参数为true表示使能。C+程序设计4)定时器函数(1)SetTimer:开始一个系统定时器。定时器的作用是每隔指定时间发一次WM_TIMER消息。(2)KillTimer:结束一个指定的系统定时器。5)提示函数(1)FlashWindow:闪烁窗口。(2)MessageBox:弹出一个标准消息框。C+程序设计例如 : 开 始一个系 统定 时器每 0.5秒 发一次WM_TIMER消息,代码为:SetTimer(

384、1,500,NULL):其中,第一个参数表示定时器的标识,第二个参数表示发送WM_TIMER的间隔时间,第三个参数是一个回调函数,一般设为NULL。可以在WM_TIMER消息的处理函数OnTimer(通过ClassWizard添加)中加入处理代码来响应定时器消息。结束定时器的方法是调用以定时器标识为参数的函数KillTimer,比如:KillTimer(1);C+程序设计2字符串类CStringCstring类是MFC对字符串的封装,它包含一个可变长的字符序列,提供了很多串操作,使用它比使用其它字符串类型更加方便。可以说,几乎每个MFC程序都要用到这个类。CString没有继承任何类,且其各个

385、字符都是TCHAR类型。CString对象可以随着串合并操作动态增加其长度,而无须用户来对其长度进行专门设置。CString可以看作一个串类型,而不是一个指向字符串的指针。C+程序设计1)CString的构造函数CString类拥有众多的构造函数,用于以不同的数据类型和不同的方式构造一个字符串对象,它们是(1)CString();/无参数的构造函数,产生一个空的Cstring对象(2) CString(const Cstring&stringSrc); /用另外一个Cstring对象的值初始化对象(3)CString(TCHARch,intnRepeat=1);/用一个字符重复若干次来初始化对

386、象C+程序设计(4)CString(LPCTSTRlpch,intnLength);/用一个字符数组和一定长度初始化对象(5)CString(constunsignedchar*psz);/从一个无符号字符指针构造对象例如:CStrings1;CStrings2(big);CStrings3=s2;CStrings4(s2+s3);C+程序设计2)CString的基本操作Cstring的基本操作如下:(1)求得到字符串长度:GetLength返回一个int类型的数。(2)判断字符串是否为空:BOOLIsEmpth()const。(3)强制字符串长度为0:voidEmpty()。(4)得到字符串

387、某位置的字符:TCHARGetAt(intnIndex)const。(5)设置字符串某位置的字符:voidSetAt(intnIndex,TCHARch)。(6)强制转换为字符串指针类型:operatorLPCTSTR。C+程序设计例如,获取字符串str最后一个字符的语句如下:CStringstr=aabbcde;Charc1=str.GetAt(str.GetLength()-1);C+程序设计3)串提取串提取函数由于根据某种原则从串中提取一个子串。相关函数包括Mid,Left和Right。(1)CStringMid(intnFirst)const;/获取从nFirst位置字符开始的子串(2

388、)CStringMid(intnFrist,intnCount)const;/获取从nFirst位置的字符开始包含nCount/个字符的子串(即到nFirst+nCount-1位置/的字符为止)C+程序设计(3) CString Left(int nCount)const; /获取左边nCount个字符所构成的子串(4) CString Right(int nCount)const; /获取右边nCount个字符所构成的子串4)转换字符串(1)voidMakeUpper();/将字符串中所有字符换成大写(2)voidMakeLower();/将字符串中所有字符换成小写(3)voidMakeRe

389、verse();/将字符串中各字符的顺序倒转(4)voidEmpty();/将字符串中所有字符删除C+程序设计5)字符串格式化函数字符串格式化函数是CString:Format,它根据一个参数字符串(格式控制字符串)和几个变量来格式化一个串。该函数的格式如下:voidFormat(LPCTSTRlpszFormat,.);voidFormat(UINTnFormatID,.);C+程序设计该成员函数用于根据格式lpszFormat,用其它数据构造一个字符串。其中省略号“.是输出参数表,每个参数可以是一个变量或表达式。函数Format常常用于把其它类型的变量转换为字符串形式,或者把几个不同类型的

390、值合并成一个字符串的形式。C+程序设计例如:CStrings1,s2,s3;s1.Format(%c,65);s2.Format(float:%f,int:%d,hexint:%x,3.48,20,0xFF);s3.Format(string:%s,formatedfloat:%2.1f,hehe,3.14159);执行以上代码后s1字符串是A,s2字符串是float:3.480000,int:20,hexint:ff,s3字符串是string:hehe,formatedfloat:3.1。C+程序设计3CPoint、Crect和Csize类1)Cpoint类MFC中的Cpoint类是对Win

391、dows结构POINT的封装,凡是能用POINT结构的地方都可以用Cpoint代替。Cpoint提供了一些成员函数,使得操作POINT结构和Cpoint类更加方便。结构POINT表示一个屏幕上的二维点,它的定义如下:C+程序设计typedefstructtagPOINTLONGx;LONGy;POINT;其中x和y分别是点的横坐标和纵坐标。C+程序设计2)CSize类MFC中的Csize类是对indows结构SIZE的封装,凡是能用SIZE结构的地方都可以用CSize代替。结构SIZE表示一个矩形的长度和宽度,它的定义如下:typedefstructtagSIZEintcx;intcy;SIZ

392、E;其中cx和cy分别是矩形的长度和宽度。C+程序设计3)CRect类Crect类是对Windows结构RECT的封装。结构RECT表示一个矩形。TypedefstructtagRECTLONGleft;LONGtop;LONGright;LONGbottom;RECT;其中,left和top分别表示矩形左上角顶点的横、纵坐标;right和bottom分别表示矩形右下角顶点的横、纵坐标。C+程序设计4CPaintDC类在CpaintDC类中封装了大量的绘图和文字输出方法(成员函数)(1)文字信息显示。文字信息显示的成员函数为:BOOLTextOut(intx,inty,LPCTSTRlpszS

393、tring);该 函 数 用 于 在 指 定 坐 标 (x,y)处 显 示 字 符 串lpszString的内容,显示成功返回非0值,否则返回0。坐标原点(0,0)在客户区左上角,Y轴向下。C+程序设计(2)画点。画点的成员函数为COLORREFSetPixel(intx,inty,COLORREFcolor);COLORREFSetPIxel(POINTpoint,COLORREFcolor);该函数在指定坐标处按给定颜色画点,返回值为原来此坐标处的颜色。(3)取指定坐标点的颜色。取指定坐标点的颜色的函数为:COLORREFGetPixel(intx,inty)const;COLORREFG

394、etPixel(POINTpoint)const;该函数的返回值为指定坐标处的颜色。C+程序设计(4)画线。画线分两步完成:首先确定线的起始位置,然后再调用画线函数。用MovoTo将绘图位置移动到指定位置的原型为:CPointMoveTo(intx,inty);CpointMoveto(POINTpoint);用LineTo函数画线的原型为:BOOLLineTo(intx,inty);BOOLLineTo(POINTpoint);C+程序设计(5)绘制矩形。绘制矩形的成员函数为:BOOLRectangle(intx1,inty1,intx2,inty2);BOOLRectangle(LPCRE

395、CTlpRect);该函数的参数为需要绘制的矩形的左上角坐标(x1,y1)和右下角坐标(x2,y2)。(6)绘制椭圆。绘制椭圆的成员函数的原型为:BOOLEllipse(intx1,inty1,intx2,inty2);BOOLEllipse(LPCRECTlpRect);该函数的参数的含义为所绘椭圆的包含矩形的左上角和右下角坐标。C+程序设计10.2.2绘制图形1.图形对象的使用方法普通绘图对象使用的模式为以下4步:(1)生成绘图对象。例如:Cpenpen,*op;pen.CreatePen(PS_SOLID,1,RGB(0,0,0);C+程序设计这里定义的Cpen类指针op用于存储绘图前的

396、画笔,使绘图结束后能够恢复系统原有状态。上面代码中的RGB(0,0,0)是一个宏,用于定义COLORREF类型的颜色。COLORREF实际上是一个32位整数类型,用于表示颜色,其第0、1、2字节分别用于存放该颜色的红、绿、蓝色分量。如果已知某颜色的3个分量,则可使用宏RGB()构造出该颜色:COLORREFRGB(BYTEbRed,BYTEbGreen,BYTEbBlue);C+程序设计其中,第1个参数是颜色的红色分量,第2个参数是颜色的绿色分量,第3个参数是颜色的蓝色分量,各个分量的取值范围为0255。例如:RGB(0,0,0)为黑色,RGB(255,255,255)为白色。C+程序设计(2

397、)将绘图对象选入绘图设备环境。其代码为:op=pDC-SelectObject(&pen);可以看出,将绘图对象选入绘图设备环境的方法是调用CDC:SelectObject()函数,与此同时,还要保留旧的画笔。(3)进行绘图。其代码为:pDC-Moveto(100,100);pDC-Lineto(200,200);C+程序设计(4)绘图工作结束后,进行绘图设备环境的恢复工作。其代码为:pDC-SelectObject(op);如果还要改变pen的参数的话,必须将pen删除:pen.DeletObject();C+程序设计2.画笔和画刷画笔是用来画线的工具,是Cpen类的对象。画刷是用来填充图形

398、的工具,是Cbrush类的对象。1)画笔画笔由Cpen类管理,使用Cpen类对象时,需要无对其进行初始化工作,初始化Cpen类对象的方法有以下3种:(1)调用Cpen类的构造函数。C+程序设计(2)调用Cpen:CreatePen(intnPenStyle,intnWidth,COLORREFcrColor)函数。其中第1个参数是画笔的样式,可取值为:PS_SOLID/创建实线笔PS_DASH/创建由短线构成的虚线PS_DOT/创建由点构成的虚线PS_DASHDOT/创建由短线和点构成的虚线C+程序设计(3)调用Cpen:CreatePenIndirect()函数,通过LPLOGPEN结构来设

399、置Cpen的属性。例如:CpenpenRed;/说明画笔对象penRed.CreatePen(PS_SOLID,3,RGB(255,0,0); /创建宽度为3的红色实线画笔/使用新的画笔时,要保存原来的画笔以便恢复Cpen*pOldPen;POldPen=dc.SelectObject(&penRed);/以下是作图代码,且使用新画笔画线dc.SelectObject(pOldPen);/恢复原来的画笔C+程序设计2)画刷画刷是用来填充图形的工具,创建画刷有两种方法:一种是调用构造函数,另一种是调用相关的成员函数。使用画刷叶要定义画刷对象,创建画刷并保存原来的画刷,在绘图工作结束后还要恢复原来

400、的画刷。例如:CbrushNewBrush;/声明画刷对象Cbrush *POldBrush;/保存原画刷的指针NewBrush.CreateSolidBrush(PS_SOLID,21,RGB(0,0,255);/建立画刷C+程序设计Pold_Brush=pDC-SelectObject(&NewBrush);/将画刷选入设备文本对象/使用画刷/恢复原先的画刷对象pDC-Selectobject(PoldBrush);C+程序设计10.2.3消息处理Windows操作系统是一个基于消息的操作系统,其程序总要同消息打交道,这正是Windows程序同DOS程序不同的地方。实际上,Windows系

401、统通过Windows消息告诉所有应用程序发生了什么事件,例如用户点击了鼠标、用户点击了键盘的哪个键等,每个应用程序都有消息处理或一组消息响应函数,用于对消息进行响应。C+程序设计Windows操作系统中定义了900余个Windows消息,系统将用户的输入传给应用程序,然后又为每个输入产生消息。此外,系统还为应用程序的变化产生消息,如窗口的尺寸发生了变化等。应用程序自已也可能产生消息,用于对应用程序内部发生的特殊事件进行处理或其它应用程序之间的通信。C+程序设计系统向应用程序发送消息时,将向应用程序发送个参数:(1)一个窗口的句柄:窗口句柄用于标识窗口的一个常数,Windows的每个窗口都有一个

402、窗口句柄。消息参数中的窗口句柄标识的是接受消息的窗口。(2)一个消息标识:标识了产生的消息。消息标识的例子有WM_CREATE,其中,WM_代表indowsMessage,是indows窗口产生的消息,而WM_CREATE代表窗口正在被创建,WM_PAINT则代表窗口的客户区需要被重画。C+程序设计(3)两个32位的参数:消息参数定义了Windows应用程序处理消息所需的数据或数据所在的位置,这两个参数的含义与具体的消息有关。消息参数可以包含一个整数、一组标志或一个结构对象的指针。消息参数也可以是NULL,表示消息没有参数。1.Windows消息分类Windows消息大体上可以分为两大类:一类

403、是系统定义的消息,另一类是用户自定义的消息。其中Windows系统定义的消息可以分为以下种:C+程序设计(1)Windows消息:这类消息主要是以WM_作前缀的消息(WM_COMMAND除外,WM_COMMAND消息专门被用于处理菜单和控件发出的消息),且必须由CWnd类或CWnd类的派生类进行处理。消息响应函数也必须被定义在发送消息CWnd类或CWnd派生类中。可见,这类消息属于面向Windows窗口的消息。C+程序设计(2)控件通知消息:这类消息主要由控件或其它子窗口发出,并对各控件或子窗口的父窗口进行处理。这 类 消 息 属 于 WM_COMMAND消 息 , 只 不 过 在WM_COM

404、MAND消息的两个消息参数中包含了每个控件的句柄、标识(ID)和通知消息等信息。(3)Windows命令消息:这类消息是指由菜单、工具条和加速键等发出的WM_COMMAND通知消息。C+程序设计2.鼠标消息处理移动鼠标或点击鼠标按键,Windows便产生一个或多个消息并将其发送给位于鼠标光标下的窗口。编程时常用的鼠标消息有:WM_LBUTTONDOWN/按下鼠标左键WM_LBUTTONUP/释放鼠标左键WM_LBUTTONDBLCLK /双击鼠标左键WM_RBUTTONDOWM /按下鼠标右键WM_RBUTTONUP/释放鼠标右键WM_RBUTTONDBLCLK /双击鼠标右键WM_MOUSE

405、MOVE/移动鼠标C+程序设计对应的Wnd类的消息处理成员函数分别为:voidOnLButtonDown(UINTnFlags,CPointpoint);voidOnLButtonUp(UINTnFlags,CPontpoint);voidOnLButtonDblClk(UINTnFlags,Cpointpoint);voidOnRButtonDown(UINTnFlags,Cpointpoint);voidOnRButtonUp(UINTnFlags,Cpointpoint);voidOnRButtonDblClk(UINTnFlags,Cpointpoint);voidOnMouseMov

406、e(UINTnFlags,Cpointpoint);C+程序设计其中,参数point表示鼠标的位置,nFlags是几个控制键的状态,可以是下列值及其组合:MK_CONTROL/Ctrl键被按下MK_LBUTTON/鼠标左键被按下MK_RBUTTON/鼠标右键被按下MK_SHIFT/Shift键被按下例如:“MK_SHIFT|MK_LBUTTON”表示同时按下了Shift键和鼠标左键。C+程序设计10.3 对话框的应用对话框的应用 对话框是一些弹出式窗口信息的弹出式窗口。它通常包含,应用程序利用它可和用户进行交互式操作。对话框是应用程序,用于显示或提示并等待用户输入一个或多个控件,利用这些控件,

407、用户可以输入文本、选择选项,并完成某一特定命令。C+程序设计对话框分为模式对话框和非模式对话框两种。模式对话框是指这种对话框出现时,它的父窗口将暂时失效,只有处理完对话框所要求的动作后,才会将控制权交回给父窗口。非模态对话框图类似普通的窗口,并不垄断用户的输入。在非模式对话框打开时,用户随时可用鼠标点击等手段激活其它窗口对象,操作完毕后再回到本对话框。本节只通过一个实例来介绍模式对话框。图10-3是用AppWizard生成的一个基于对话框的程序运行界面。下面详细介绍它的实现步骤:C+程序设计图10-3C+程序设计1.产生对话框模板在VC+6.0中选择菜单“File”“New”或按【Ctrl+N

408、】键,弹出一个对话框,如图10-4所示。在对话框中选择工程标签页,用鼠标选中左边列表框中的“MFCAppWizard【exe】”一行;在“C位置:”处输入存放源代码的目录的名字,单击“确定”后出现图10-5所示对话框。C+程序设计图10-4C+程序设计图10-5C+程序设计2.设置对话框模板设置对话框模板一般需要以下几步:设置对话框的属性;向对话框模板放置控件;通过控件属性对话框设置各个控件的属性和控件的跳表顺序。1)设置对话框的属性首先调整模板的大小:把鼠标放在对话框模板的边或角上,等鼠标变为双箭头形状,拖动鼠标即可。点击对话框模板(而不是它上面某一控件),按【Enter】键弹出属性设置对话

409、框,图10-6所示为该对话框的General页。可以设置对话框的风格和各边界滚动条的类型,对话框的标题等。C+程序设计图10-6C+程序设计2)添加控件并设置控件属性添加控件要借助于控件工具条(图10-7所示),控件工具条提供了25种控件。常用的控件是:图形控件(Picture)、静态文本框(Static Text)、 编 辑 框 (Edit Box)、 组 框 (Gorup Box)、 按 钮(Botton)、复选框(CheckBox)和单选框(RadioButton)。图形控件用于显示位图(Bitmap)和图标(Icon);静态文本框用于显示静态文本(当然显示的内容可以通过程序改变);编辑

410、框用于数据的输入和显示;组框用于把若干控件从视觉上组合成一组,形成友好的界面;按钮用于用户向程序提供命令;复选框完成开关功能;单选框也提供选择,它与复选框的区别是在一组单选框中,用户只能选择其中一个,并且单选框自已不能取消自已的选择,而只能通过选中别的单选框来取消。C+程序设计图10-7C+程序设计向对话框增加一个控件的方法是在控件工具条上通过单击选择一个控件,然后在对话框模板上按下鼠标左键,控件就会按默认大小放置在对话框上。通过单击选中控件,把鼠标光标移动到控件边缘,等光标呈箭头状时拖动鼠标可以改变控件的大小。删除一个控件的方法是先选中控件后按【Delete】键。设置控件的属性:先选中控件,

411、按【Enter】键(或者按鼠标右键,在弹出的菜单上选择Properties项)弹出图10-8所示的属性对话窗口,在该窗口中可以设置控件的标识符(ID),标题(Caption)和各种风格(Style)等。C+程序设计图10-8C+程序设计在图10-9中,将对话框模板上的“TODO:在这是设置对话控制”静态文本框和“确定”按钮删除。为对话框增加3个编辑框,它们的ID分别为ID_EDIT_ADD1,ID_EDIT_ADD2和ID_EDIT_ADD3;添加两个静态文本框,它们的Caption分别为“+”和“=”。另外,“+”和“=”的属性页对话框的Styles属性页要作两点改变:将“X对齐文本”属性设

412、为Center,选择“C中垂直”复选框,如图10-10所示。C+程序设计图10-9C+程序设计图10-10C+程序设计再添加两个命令按钮,它们的Caption分别为“计算结果”和“退出”,“计算结果”的ID是IDC_BUTTON1,“退出”的ID是IDC_BUTTON2。3)定义成员变量增加了3个编辑框后,获取三个编辑框中的输入数字的常用方法是:先定义一些与控件相联系的变量,然后在程序中通过这些变量来完成对控件的控制。为控件定义变量最方便的方法是通过ClassWizard。按 【 Ctrl+W】 键 , 弹 出 图 10-11所 示 的 MFCClassWizard对话框,选择MemberVa

413、riables窗口如图10-12所示。C+程序设计图10-11C+程序设计图10-12C+程序设计选 中 “IDC_EDIT1”, 双 击 鼠 标 或 单 击 AddVariable.按钮,弹出图10-13所示的对话框,这个对话框用来增加与控件相联系的成员变量。先为变量取名为m_add1,填在Membervariablename中。这个变量用来存放用户在IDC_EDIT1编辑框中输入的数,供程序计算结果用;再选择变量的类别填在Category中,可以为Value或Control,后者表示所定义的变量是控件类的一个对象,比如对于编辑框而言,Control类变量的类型是Cedit,前者表示所定义的

414、变量是与控件相联系的一个值,C+程序设计这个值的含义随不同类型的控件而不同,如对于编辑框而言,变量表示在编辑框所输入的内容。有的控件不能定义与之相关的Value变量,如按扭;最后选择变量的类型填在Variabletype中,变量m_add1的类型定义为double类型。用同样的方法可以为其它控件增加变量,最终增加的所有变量如下:IDC_EDIT1,double,a_add1IDC_EDIT2,double,a_add2IDC_EDIT3,double,a_add3C+程序设计图10-13C+程序设计4)增加事件处理函数在程序中希望点击图10-3中的命令按钮“计算结果”时,会将输入的两个数的和填

415、入第3个编辑框。要想实现以上操作,必须为“计算结果”这个命令按钮填加鼠标左键单击命令按扭的处理函数。这样当鼠标左键单击这个事件发生时,就会执行这个处理函数。增加事件处理函数有两种方法:(1)通过专门的事件处理对话框C+程序设计(2)可以在10-11窗口中所示的ClassWizard的MessageMaps标签页为控件添加事件处理函数。下面介绍第1种方法:选中图10-3中“计算结果”按钮,单击鼠标右键,在弹出菜单中选择Events,弹出的对话框如图10-14所示。C+程序设计图10-14C+程序设计此窗口用于添加、删除窗口的消息和事件处理函数。本程序需为按钮增加单击事件处理函数:双击左边列表框中

416、的“BN_CLICKED”(这时会弹出一个对话框让用户修改事件处理函数的名字,若保持默认的名字,直接单击“确定”即可)。添加完单击事件处理函数后的对话框如图10-15所示,注意到增加了处理函数的事件名移动到了右边的列表框中,在图10-15中单击EditExisting按钮可以直接进入对话框的源文件(.CPP),并为事件响应函数增加代码。C+程序设计图10-15C+程序设计因为添加的函数全都是空的,所以需要手工添加代码来实现用户想要的功能。在下面的程序中有一个函数UpdateData()出现了两次,其格式为:BOOLUpdateData(BOOLbSaveAndValidate=TRUE);Up

417、dateData函数是MFC中的CWnd类的成员函数,CWnd类是很重要的MFC类,所有窗口类都直接或间接地继承于它。本程序主对话窗口类ClitleAdderDlg继承了Cdialog,而Cdialog又继承了CWnd,因此可以在程序中使用函数UpdateData。C+程序设计用户在程序运行过程中通过鼠标或键盘修改了对话框控件的状态后,对话框中与控件相关联的变量值并没有马上更新。以参数TRUE调用函数UpdateData()的作用就是更新所有与对话框图控件相关联的变量值,而以参数FALSE调用此函数则更新与变量相关联的控件的显示状态,使之与变量一致。也可以通俗地说,以TRUE和FALSE作为参

418、数可分别实现控件关联变量的“里传”和“外传”。下面的语句起刷新编辑框的作用。C+程序设计voidCSf1Dlg:BUTTON1()/TODO:Addextravalidationhere(在这里增加用户代码)UpdateData(true);m_add3=m_add1+m_add2;UpdateData(false);C+程序设计各关联变量的初值在函数CSf1Dlg:CSf1Dlg(它是程序主对话框类的构造函数)中初始化,它是由程序自动生成的。如果想修改某些变量的初值,可以在源程序中找到该函数,修改某些变量的初始值。即CSf1Dlg:CSf1Dlg(CWnd*pParent/*=NULL*/)

419、:CDialog(CSf1Dlg:IDD,pParent)C+程序设计/AFX_DATA_INIT(CSf1Dlg)m_add1=0.0f;m_add2=0.0f;m_add3=0.0f;/AFX_DATA_INIT/ Note that LoadIcon does not require a subsequentDestroyIconinWin32m_hIcon=AfxGetApp()-LoadIcon(IDR_MAINFRAME);C+程序设计到此本程序编制完成,可以按【Ctrl+F5】键看它的运行结果。本程序并不是很完善,但作者仅仅是想通过这个例子告诉读者简单的对话窗口是如何编制的。如果

420、读者感兴趣,你可以试着增加一些控件或功能使它更加完善。C+程序设计10.4 菜菜 单单 的的 应应 用用菜单是一个专业程序不可缺少的程序界面构件,对于不同的程序,这些构件会以不同的面貌出现。菜单是Windows使用者天天都要见到的应用元素。C+程序设计菜单由上层水平列表项及与其相连的弹出式菜单项组成,用户选择了上层某个列表项时,与之相连的弹出式菜单就会随即出现。菜单的各个菜单项用来响应用户的鼠标单击而产生命令消息,从而提供一种用户对程序进行控制的方式。由于弹出式菜单平时是隐藏的,只露出上层水平列表项,因此菜单能容纳大量的操作。C+程序设计菜单也是一种资源,因此要通过DeveloperStudi

421、o的资源编辑器编辑。“弹出式菜单”、“选项”和“分隔线”是构成“树状菜单”的三大要素。通过“弹出式菜单”可以调出一个子菜单,分隔线用来区分一组选项。通过菜单中的“选项”可以调用应用程序的某项功能。每个选项均有一个标识符,而且只有选项才有标识符。在应用程序中只对选项编程。当用户选择了一个菜单选项后,就会向应用程序发送一个命令消息WM_COMMAND。该消息的格式为ON_COMMAND(id,memberFxn)C+程序设计1.编辑菜单选择“插入”“资源”菜单项或按【Ctrl+R】键,在弹出的添加资源对话框中选择“菜单”,然后按【Enter】键,即可向项目添加一个菜单资源。这时,菜单资源编辑器打开

422、,菜单编辑器的窗口如图10-16所示。其中,灰色的横条是菜单,周围有一个白框的灰色块是要添加的菜单项。下面分几步编辑菜单:C+程序设计图10-16C+程序设计图10-17C+程序设计1)添加菜单上层水平列表项单击选中要添加的菜单项小灰块,按【Enter】键弹出其属性对话框(如图10-7所示),上层水平列表项是具有弹出属性的菜单项,它没有ID,它的“C标题”是菜单上显示的字符串,也叫菜单的名字。在图中的“C标题”处输入了字符串“文件(&F),当程序运行时界面上将显示“文件(F)(字符&并不显示出来),实际上,字符&的作用是使紧跟在它后面的字符下面加下划线。这样按【Alt+V】键可激活此菜单项,这

423、是利用键盘选择菜单项的一种方法。C+程序设计2)添加各上层水平列表项的子菜单项单击已经添加的某一个水平列表菜单项,其下面会出现一个空的菜单项,选中这个空的菜单项,按【Enter】键弹出要新加子菜单的属性对话框,在对话框中输入菜单项的ID和Caption,然后设置菜单的其它属性。有时希望把功能相近的菜单项放在一起成为一组,通过一个横的分割线把它与其它菜单项分割开。产生一条分割线的方法是在菜单项的属性对话框中选择“分隔符”复选框。C+程序设计图10-18C+程序设计2.为菜单单击增加消息响应函数1)方法一设置好菜单的各个菜单项后,就可用ClassWizard为菜单增加消息响应函数了。(通过按【Ct

424、rl+W】键)弹出ClassWizard对话框后,选择ClassWizard的MessageMaps页(如图10-19所示)C+程序设计图10-19C+程序设计在图10-19中的Project和Classname组合框中分别选择要为哪个项目的哪个类增加消息响应函数,所以Project中选择“Menutest和Classname组合框中选择“CmenuTestView。在ObjectIds列表框中选中一个菜单ID(比如ID_TEST_COMMAND),可以为这个ID的菜单增加单击响应函数,方法是:选中右边Message列表框中的COMMAND行,单击Add Function.(或双击COMMAN

425、D行),在弹出的对话框中单击OK按钮。用同样的操作为所有菜单项增加单击响应函数。最后单击ClassWizard对话框的“确定”按钮来确认添加(或者单击editCode按钮直接跳到源文件为函数增加实现代码)。C+程序设计例10.2在单文档界面的应用程序中,添加一个“画图”菜单项包括“画矩形”和“画圆形”,并添加不同的颜色。第一步:创建一个基于单文档界面的应用程序,将工程命名为MenuTest(参照1.3.2节的步骤)第二步:在“工程管理区”中点击“ResourceView”,在Menu文件夹中选择“IDR_MAINFRAME”,双击菜单中添加一个菜单,如图10-20所示。菜单项“画矩形”的ID标

426、识为“ID_RECT_COMMAND”。菜单项“画圆形”的ID标识为“ID_CIRCLE_COMMAND”。C+程序设计第三步:为“画图”菜单项添加消息响应函数。按【Ctrl+w】键弹出如图10-21的窗口,选择“Pojec为工程名“MenuTest”,选择“Classname为“CmenuTestView”,选择“ObjectIds:”为画矩形的标识“ID_RECT_COMMAND”,选中右边Message列表框中的COMMAND行,单击AddFunction.(或双击COMMAND行),在弹出的对话框中单击OK按钮。用同样的操作为“画圆形”菜单项增加单击响应函数。最后单击ClassWiza

427、rd对话框的“确定”按钮来确认添加(或者单击EditCode按钮直接跳到源文件为函数增加实现代码)。C+程序设计图10-20C+程序设计图10-21C+程序设计两个菜单项的响应函数为:voidCMenuTestView:OnRectCommand()/TODO:Addyourcommandhandlercodehere(在这里加入命令处理代码)CClientDCdc(this);CBrushbrushNew;CPenpenNew;brushNew.CreateSolidBrush(RGB(255,0,0);penNew.CreatePen(PS_SOLID,1,RGB(255,0,0);dc.

428、SelectObject(&brushNew);C+程序设计dc.SelectObject(&penNew);dc.Rectangle(20,20,200,180);voidCMenuTestView:OnCircleCommand()/TODO:Addyourcommandhandlercodehere(在这里加个命令处理代码)CClientDCdc(this);CBrushbrushNew;CPenpenNew;C+程序设计brushNew.CreateSolidBrush(RGB(0,0,255);penNew.CreatePen(PS_SOLID,1,RGB(255,0,0);dc.S

429、electObject(&brushNew);dc.SelectObject(&penNew);dc.Ellipse(280,20,460,200);分析:首先生成一个CclientDC类型的对象dc,Cclient继承了设备上下文类CDC。CDC用于指定设备上下文(如窗口客户区、打印机)进行绘图、显示文本等操作,而CclientDC用于在窗口客户区画图和显示文本。C+程序设计BrushNew.CreateSolidBrush()是用于设置画刷的颜色,penNew.CreatePen()是用于设置画笔的颜色,dc.Ellipse()是画椭圆的函数。代码输入完成后,按Ctrl+F5键开始运行,运

430、行结果如图10-22所示。C+程序设计图10-22C+程序设计2)方法二(1)载入菜单的工作可以在CWnd类的PreCreateWindow()函数中进行,其原型为virtualBOOLPreCreateWindow(CREATESTRUCT&cs);其中,参数cs的类型为CREATESTRUCT。该类型用于存放建立窗口的初始化参数。PreCreateWindow()函数在窗口创建前被调用,通过重载该函数,可以设置各种窗口参数,也可以使用LoadMenu()载入菜单资源。其代码为:cs.hMenu=LoadMenu(NULL,MAKEINTRESOURCE(IDR_mAINMENU);C+程序

431、设计(2)为每个菜单选项添加消息映射WM_COMMAND()和消息处理成员函数。例10.3在窗口中显示一个位图文件,设计一个菜单,使图像能放大、缩小和正常显示。#include#includeresource.h/框架窗口类classCMyWnd:publicCFrameWndC+程序设计CBitmapm_Bitmap;floatm_fTimes;intm_nHeight;intm_nWidth;public:CMyWnd();BOOLPreCreateWindow(CREATESTRUCT&cs);protected:C+程序设计afx_msgvoidOnShrink();afx_msgvo

432、idOnBestFit();afx_msgvoidOnZoomOut();afx_msgvoidOnPaint();DECLARE_MESSAGE_MAP();/消息映射BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)ON_WM_PAINT()C+程序设计ON_COMMAND(ID_SHRINK,OnShrink)ON_COMMAND(ID_BESTFIT,OnBestFit)ON_COMMAND(ID_ZOOMOUT,OnZoomOut)END_MESSAGE_MAP()/主窗口类的成员函数CMyWnd:CMyWnd()BITMAPBM;m_Bitmap.LoadBit

433、map(IDB_BITMAP1);/载入位图资源C+程序设计m_Bitmap.GetBitmap(&BM);/读位图信息m_nWidth=BM.bmWidth;m_nHeight=BM.bmHeight;m_fTimes=1.0;/装入菜单BOOLCMyWnd:PreCreateWindow(CREATESTRUCT&cs)C+程序设计cs.hMenu=LoadMenu(NULL,MAKEINTRESOURCE(IDR_MENU1);returnCFrameWnd:PreCreateWindow(cs);/缩小图像voidCMyWnd:OnShrink()m_fTimes=0.5;Invali

434、date();/放大图像C+程序设计voidCMyWnd:OnZoomOut()m_fTimes=2.0;Invalidate();/正常显示voidCMyWnd:OnBestFit()m_fTimes=1.0;Invalidate();/响应绘制窗口客户区消息C+程序设计voidCMyWnd:OnPaint()CPaintDCdc(this);CDCMemDC;MemDC.CreateCompatibleDC(NULL);MemDC.SelectObject(&m_Bitmap);/CDC类的成员函数,用来显示位图资源dc.StretchBlt(0,0,(int)(m_nWidth*m_fT

435、imes),(int)(m_nHeight*m_fTimes),&MemDC,0,0,m_nWidth,m_nHeight,SRCCOPY);C+程序设计/应用程序类classCMyApp:publicCWinApppublic:BOOLInitInstance();/应用窗口类的成员函数/初始化应用程序实例C+程序设计BOOLCMyApp:InitInstance()CMyWnd*pFrame=newCMyWnd;pFrame-Create(0,_T(ShowBitmap1.0);pFrame-ShowWindow(SW_SHOWMAXIMIZED);pFrame-UpdateWindow(

436、);this-m_pMainWnd=pFrame;returnTRUE;CMyAppThisApp;/全局应用程序对象C+程序设计调试:首先建立Win32Application空白项目和源程序代码文件,然后为项目添加建立资源文件。建立资源文件的方法如下:l使用菜单项“文件”“新建”,从文件选项卡中选择“ResourceScript(资源描述)”,并在选项卡右方添入资源文件名(通常与项目名相同)后按“确定”键。此时即可发现在工作区窗口中新添了一个ResourceView,通过它可以查看项目中的各种资源。C+程序设计l 选 择 菜 单 选 项 “插 入 ”“资 源 ”, 调 出“InsertRes

437、ource对话框,然后在其中选择相应的资源“Bitmap(位图)”和“Menu(菜单),再选择选项卡右边的“导入”按钮,弹出一个窗口选择位图文件所在的路径和文件名,再按“Import”按钮,则所选的位图文件会插入到工作区窗口的“ResourceView”中并显示出来,再如前所述,在菜单中添加菜单项和标识符ID。至些,资源文件便建好了,再输入源程序代码,然后按【Ctrl+F5】开始运行程序。运行结果如图10-23所示。C+程序设计图10-23C+程序设计10.5 工工 具具 栏栏 应应 用用 工具条是一种重要的控制条,是一个包含按扭、列表框或其它控制的子窗口。大多数工具条是一行用于激活命令的位图

438、化的按钮。按一个工具条按钮类似于选择一个菜单项。这些按钮可以起命令按钮、复选框和单选钮的作用。C+程序设计图10-24C+程序设计工具条的特点:通常排列在框架窗口的顶部,随着用户在工具条的按钮上移动鼠标,工具条还可以在按钮附近显示“工具提示”。若 在 MFC AppWizard Step 4 of 6中 设 置 了“Dockingtoolbar选项,则AppWizard自动生成一个缺 省 的 工 具 条 , 如 图 10-24所 示 。 该 工 具 条 为APPWizard生成的标准Windows菜单提供了另外一种快捷操作。若项目中没有工具条,可采用这种方法加入:选择“插入”“资源”菜单项或按

439、【Ctrl+R】键,在弹出的添加资源对话框中选择“ToolBar”,然后按【Enter】键,即可向项目添加一个工具条资源。这时,工具条资源编辑器打开,菜单编辑窗口如图10-25所示。C+程序设计图10-25C+程序设计工具条编辑器实际是一个图像编辑器,利用绘图面板上的各种绘图工具和在颜色面板选择各种颜色可以绘制一些简单的图形和图像。增加一个工具条按钮:最初工具条上只有一个待定制的按钮,但只要利用绘图面板上的任何一种工具对这个按钮进行了处理,工具条上马上出现另一个按钮待定制,也就是说,始终有一个待定制的按钮在工具条的最右边,它的作用仅仅是增加按钮,在程序运行时不会出现。一个工具条中所有按钮的图形

440、放在一个位图中,而该位图定义在应用程序的资源文件中,当工具条中的按钮被按下时,它会发送相应的命令消息(与菜单类似)。C+程序设计修改工具条的属性:在WorkSpace窗口的ResourceView页中Toolbar文件夹下面的工具条名字(IDR_TOOLBAR)上按【Alt+Enter】(或按鼠标右键),弹出工具条的属性对话框,如图10-26所示。工具条中的按扭常常对应于常用的菜单项命令,因而只需将“按扭”数组中的ID置为相应的菜单项命令ID,就可实现工具条按钮和菜单项命令执行同一代码段。与此对应,应用程序能对任何命令消息作出响应,并将其发送给同一处理函数处理,而不论消息来自菜单项还是来自工具条按钮。C+程序设计图10-26C+程序设计小结本章讲述了基于对话框的应用程序的基本编程思路、基于单文档编程的菜单设计和工具栏设计的基本方法。1.首先介绍基于对话框的应用程序编程的工作流程,然后介绍了一个吹彩色泡泡的实例,讲述了相关的一些函数和常用的MFC类库的成员函数。2.讲述了基于对话框的应用程序的实例。主要讲述了设计一个简单的对话框用户界面的步骤。3.讲述了菜单的概念和设计一个菜单的步骤。4.讲述了工具栏的设计步骤。

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 资格认证/考试 > 自考

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号