C++程序设计教程实验指导书 下载本文

面向对象程序设计

实验指导书

前 言

本书是本科课程《面向对象程序设计》的实验指示书,一般人员也可以使用它作为学习C++语言的上机指导。因为选择以Microsoft公司的C++开发环境Visual C++作为实验环境,因此书的前一部分介绍了Visual C++的一些基本知识,并且在实验进行的过程中穿插介绍使用Visual C++的一些技巧。

书的内容分为两大部分:第一部分介绍Visual C++实验环境;第二部分是具体的实验安排。书中共安排了八次实验,每个实验3个学时。具体安排是:

实验序号 实验内容 熟悉实验环境 1 简单程序开发 2 函数与程序结构 3 复杂数据类型 4 结构和类 5 继承与虚函数 6 重载与文件I/O 7 面向对象程序设计 8 其中第一个实验和最后一个实验是有关Visual C++实验环境的,其他每个实验与C++语言的一个重要知识点对应。每个实验都列出了实验目的、实验要求以及思考问题,一些实验还列出了测试数据。

使用本书前,请先阅读以下内容:

1.C++语言实验环境配置要求

硬件配置:586以上PC兼容机或品牌机,配有彩色显示器、鼠标、键盘,内存不小于20MB,硬盘自由空间不少于60MB。推荐配置为内存32MB或64MB(或以上),硬盘自由空间500MB以上。

软件配置:操作系统:Windows95, Windows98, Windows NT3.51以上版本。 开发集成环境:Microsoft Visual C++5.0以上版本 2.建立自己的工作目录

你需要在计算机上先建立自己的工作目录,所有的实验都在该工作目录下进行。使用Visual C++建立新项目时,需要指定该目录作为项目所在的目录。本书中假设你的工作目录为c:\\student\\your_name,具体的实验目录由你的指导教师指定。

3.安装Visual C++

Visual C++ 6.0和Visual C++ 5.0最大的不同是在联机帮助系统上做了很大改变。Visual C++ 5.0的帮助系统直接集成在开发环境之中,在安装Visual C++ 5.0时就可以选择安装帮助内容。而Visual C++ 6.0的联机帮助系统采用了MSDN(Microsoft Developer Network)库,在安装Visual C++ 6.0时,只安装了MSDN的索引,实际的内容在光盘上。如果希望能脱离光盘使用帮助系统,需要在安装了Visual C++ 6.0以后,再运行MSDN的安装程序,把完整的库装到硬盘上。

1

目 录

第一部分 VISUAL C++实验环境介绍 ..........................................3

一、Visual C++简介 .............................................................................................................. 3 二、项目开发过程 ................................................................................................................. 4 三、集成开发环境Developer Studio ................................................................................. 4 四、常用功能键及其意义 ..................................................................................................... 7

第二部分 实验 .................................................................................... 8

实验一

实验二 实验三 实验四 实验五 实验六 实验七 实验八

熟悉实验环境 ........................................................................................................ 8 简单程序开发 ...................................................................................................... 15 函数与程序结构 .................................................................................................. 17 复杂数据类型 ...................................................................................................... 20 结构和类 .............................................................................................................. 23 继承与虚函数 ...................................................................................................... 28 重载与文件I/O .................................................................................................... 32 面向对象程序设计 .............................................................................................. 34

2

第一部分 Visual C++实验环境介绍

一、Visual C++简介

Visual C++是Microsoft公司的Visual Studio开发工具箱中的一个C++程序开发包。VisualStudio提供了一整套开发Internet和Windows应用程序的工具,包括VisualC++, Visual Basic, Visual Foxpro, Visual InterDev, Visual J++以及其他辅助工具,如代码管理工具Visual SourceSafe和联机帮助系统MSDN。Visual C++包中除包括C++编译器外,还包括所有的库、例子和为创建Windows应用程序所需要的文档。

从最早期的1.0版本,发展到最新的6.0版本,Visual C++已经有了很大的变化,在界面、功能、库支持方面都有许多的增强。最新的6.0版本在编译器、MFC类库、编辑器以及联机帮助系统等方面都比以前的版本做了较大改进。

Visual C++一般分为三个版本:学习版、专业版和企业版,不同的版本适合于不同类型的应用开发。实验中可以使用这三个版本的任意一种。

Visual C++集成开发环境(IDE)

集成开发环境(IDE)是一个将程序编辑器、编译器、调试工具和其他建立应用程序的工具集成在一起的用于开发应用程序的软件系统。Visual C++软件包中的Developer Studio就是一个集成开发环境,它集成了各种开发工具和VC编译器。程序员可以在不离开该环境的情况下编辑、编译、调试和运行一个应用程序。IDE中还提供大量在线帮助信息协助程序员做好开发工作。Developer Studio中除了程序编辑器、资源编辑器、编译器、调试器外,还有各种工具和向导(如AppWizard和ClassWizard),以及MFC类库,这些都可以帮助程序员快速而正确地开发出应用程序。

向导(Wizard)

向导是一个通过一步步的帮助引导你工作的工具。Developer Studio中包含三个向导,用来帮助程序员开发简单的Windows程序,它们是:

AppWizard:用来创建一个Windows程序的基本框架结构。AppWizard向导会一步步向程序员提出问题,询问他所创建的项目的特征,然后AppWizard会根据这些特征自动生成一个可以执行的程序框架,程序员然后可以在这个框架下进一步填充内容。AppWizard支持三类程序:基于视图/文档结构的单文档应用、基于视图/文档结构的多文档应用程序和基于对话框的应用程序。也可以利用AppWizard生成最简单的控制台应用程序(类似于DOS下用字符输入输出的程序)。

ClassWizard:用来定义AppWizard所创建的程序中的类。可以利用ClassWizard在项目中增加类、为类增加处理消息的函数等。ClassWizard也可以管理包含在对话框中的控件,它可以将MFC对象或者类的成员变量与对话框中的控件联系起来。

ActiveX Control Wizard:用于创建一个ActiveX控件的基本框架结构。ActiveX控件是用户自定义的控件,它支持一系列定义的接口,可以作为一个可再利用的组件。

MFC库

3

库(library)是可以重复使用的源代码和目标代码的集合。MFC(Microsoft Fundamental Casses)是Visual C++开发环境所带的类库,在该类库中提供了大量的类,可以帮助开发人员快速建立应用程序。这些类可以提供程序框架、进行文件和数据库操作、建立网络连接、进行绘图和打印等各种通用的应用程序操作。使用MFC库开发应用程序可以减少很多工作量。

二、项目开发过程

在一个集成的开发环境中开发项目非常容易。一个用C++开发的项目的通用开发过程可

以用左图表示。

建立一个项目的第一步是利用编辑器建立程序代码文件,包括头文件、代码文件、资源文件等。然后,启动编译程序,编译程序首先调用预处理程序处理程序中的预处理命令(如#include,#define等),经过预处理程序处理的代码将作为编译程序的输入。编译对用户程序进行词法和语法分析,建立目标文件,文件中包括机器代码、连接指令、外部引用以及从该源文件中产生的函数和数据名。此后,连接程序将所有的目标代码和用到的静态连接库的代码连接起来,为所有的外部变量和函数找到其提供地点,最后产生一个可执行文件。一般有一个makefile文件来协调各个部分产生可执行文件。

可执行文件分为两种版本:Debug和Release。Debug版本用于程序的开发过程,该版本产生的可执行程序带有大量的调试信息,可以供调试程序使用,而Release版本作为最终的发行版本,没有调试信息,并且带有某种形式的优化。学员在上机实习过程中可以采用Debug版本,这样便于调试。

选择是产生Debug版本还是Release版本的方法是:在Developer Studio中选择菜单Build|Set Active Configuration,在弹出的对话框中,选择所要的类型,然后选择OK关闭对话框。

Visual C++ 集成开发环境中集成了编辑器、编译器、连接器以及调试程序,覆盖了的开发应用程序的整个过程,程序员不需要脱离这个开发环境就可以开发出完整的应用程序。

三、集成开发环境Developer Studio

进入Developer Studio

如果你使用的是Visual C++ 6.0,则要进入Developer Studio,需要单击任务栏中“开始”后选择“程序”,找到Microsoft Visual Studio 6.0文件夹后,单击其中的Microsoft Visual C++6.0图标,则可以启动Developer Studio。

4

如果你使用的是Visual C++ 5.0,则要进入Developer Studio,需要单击任务栏中“开始”后选择“程序”,找到Microsoft Visual C++ 5.0文件夹后,单击其中的Microsoft Visual C++5.0图标,则可以启动Developer Studio。

Developer Studio的界面

Developer Studio用户界面是一个由窗口、工具条、菜单、工具及其他部分组成的一个集成界面。通过这个界面,用户可以在同一环境下创建、测试、调试应用程序。

VC5和VC6的Developer Studio的初始化界面有一些小的差异,VC5的界面如下:

主要部分介绍如下: ? 工具条和菜单,用于提供用户操作的命令接口。菜单以文字和层次化的方式提供命令接

口,工具条由一系列按钮组成。这些按钮用一系列小的位图标志。工具条以图标方式提供快速的命令选择。菜单和工具条在开发的不同进程有不同显示内容。当第一次打开Developer Studio时,标准的工具条和菜单就会显示出来,随着开发的不同步骤,不同的工具条就会自动显示出来,菜单也会有所变化。工具条有很多种,你可以显示任意多的工具条,只要屏幕空间允许。工具条可以任意移动,也可以放大缩小。工具条和菜单条功能基本相同,唯一的区别是:菜单条总占据一行,并且一般不能隐藏。 ? 工作区窗口,这个窗口包含关于正在开发的这个项目的有关信息。在没有开发任何项目

时,该窗口显示系统的帮助目录。当打开一个项目以后,工作区窗口将会显示关于当前项目的文件信息和类的信息。下图是打开一个项目hello以后的工作区窗口(假设该项目由两个文件cpp1.cpp, cpp2.cpp组成)。

5

? 文档窗口区,这个区域可以显示各种类型的文档,如源代码文件、头文件、资源文件等。

可以同时打开多个文档。

? 输出窗口,输出窗口用来显示几种信息,可以通过选择不同的标签显示不同的信息。这

些信息包括:编译连接结果信息(Build标签)、调试信息(Debug标签)、查找结果信息(Find in Files标签)。其中查找结果信息有两个标签,可以显示两次在文件中查找指定内容的结果

VC6因为在联机帮助系统上比VC5做了很大改进,所以在工作区窗口中没有VC5的InfoView,初始化界面的其他部分都与VC5相似。Deleveloper Studio使用Microsoft Developer Network(MSDN)库作为它的联机帮助系统。其界面如下:

总的来说,窗口和命令接口(包括工具条和菜单条)是构成界面的最主要组成部分。通常有两种窗口:文档窗口和可附着(docking)窗口。文档窗口显示在文档窗口区,用于显示和编辑文档,其的大小和位置可以随其所处的Developer Studio窗口的改变而改变,可以最大化和最小化。可附着窗口可以附着于应用程序窗口的边界,也可以浮在屏幕上的任何位置。可附着窗口有:工作区(workspace)窗口,输出(output)窗口,调试窗口(包括variable, watch, local等窗口)等。

文档窗口的位置、大小及是否可见和它所在的项目有关,docking窗口的位置、大小及是否可见则与项目进行的状态以及各种编辑和调试的操作有关。

各种窗口和各种工具条以及菜单构成了界面的布局。一旦用户决定了一种界面布局,系统就会为一直为用户保持这种布局,直到用户下一次改变该布局为止。

6

获得帮助信息

大多数时候,你可以通过按F1得到上下文帮助。如在编辑文件时按F1可以得到有关编辑的帮助,在编译连接错误信息上按F1可以得到关于该错误的帮助信息。如果想系统地获得帮助,在VC5中可以单击工作区窗口的InfoView标签,从其中选择要想了解的内容。要想查找关于某个话题的帮助,可以选择菜单Help|Search,在查询对话框中进行查找。VC6中,可以通过选择菜单Help|Contents来启动MSDN查阅器,MSDN查阅器是一个功能强大的程序,可以方便地浏览、查找信息,要想知道具体如何使用MSDN查阅器,可以在MSDN查阅器中选菜单Help下的命令。

Visual C++的编辑器

Developer Studio包含一个功能强大的编辑器,可以编辑将被编译成Windows程序的Visual C++源文件。这个编辑器有点象字处理器,但是没有字处理器具备的复杂的排版、文本格式等功能,它注重的是如何帮助程序员快速高效地编制程序。它具有以下特点::

? 自动语法。用高亮度和不同颜色的字来显示不同的语法成分,如注释、关键字和一

般代码用不同的颜色显示

? 自动缩进。帮助你排列源代码,使其可读性更强

? 参数帮助。在编辑时用到预定义的windows函数时,可以自动为你显示函数参数 ? 集成的关键字帮助。能够使你快速得到任何关键字、MFC类或Windows函数的帮

助信息(按F1即可)

? 拖放编辑。能够用鼠标选择文本并自由拖动到任意位置

? 自动错误定位。能自动将光标移动到有编译错误的源代码处。

当你打开一个源代码文件时,就可以利用编辑器对其进行编辑。源代码文件在文档显示区显示,每个文件有独立的显示窗口。如果你选择用其他编辑器编辑源文件,必须将它以纯文本的方式保存。VC的编译器不能处理其中有特别格式字符的文件。

四、常用功能键及其意义

为了使程序员能够方便快捷地完成程序开发,开发环境提供了大量快捷方式来简化一些常用操作的步骤。键盘操作直接、简单,而且非常方便,因而程序员非常喜欢采用键盘命令来控制操作。下面是一些最常用的功能键,希望学员在实验中逐步掌握。 操作类型 功能键 对应菜单 含义 Ctrl+N File|New 文件操作 创建新的文件、项目等 Ctrl+O File|Open 打开项目、文件等 Ctrl+S File|Save 保存当前文件 Ctrl+X Edit|Cut 编辑操作 剪切 Ctrl+C Edit|Copy 复制 Ctrl+V Edit|Paste 粘贴 Ctrl+Z Edit|Undo 撤消上一个操作 Ctrl+Y Edit|Redo 重复上一个操作 Ctrl+A Edit|Select All 全选 Del Edit|Del 删除光标后面的一个字符

7

建立程序操作 调试

Ctrl+F7 Ctrl+F5 F7 F5 F5 F11 shift+F11 F10 F9 Ctrl+F10 shift+F9 Shift + F5 Build| Compiler current file Build|Run exe Build|Build exe Build|Start Debugging Debug|Go Debug|Step into Debug|Step out Debug|Step over Debug|Run to cursor Debug|QuickWatch Debug|Stop debugging 编译当前源文件 运行当前项目 建立可执行程序 启动调试程序 继续运行 进入函数体内部 从函数体内部运行出来 执行一行语句 设置/清除断点 运行到光标所在位置 快速查看变量或表达式的值 停止调试 第二部分 实验

注意事项:

(1) 每次实验以前,需要详细阅读实验目的、实验要求和实验提示,以便能准确地理解实验要求,达到实验的目的。有测试数据要求的,需要给出测试结果,有要求回答问题的,需要给出问题的回答。每次实验都有几个题目,要求为每个题目创建不同的项目,以便于检查。

(2) 有的实验前后有联系,需要先完成前面的实验再进行后面的实验,如类和继承的实验。请务必按照先后顺序完成实验。

(3) 辅导老师那儿有关于实验的详细解答,如果有疑问,可以请教辅导老师。

实验一 熟悉实验环境

实验目的

了解和使用VC集成开发环境

熟悉VC环境的基本命令和功能键,熟悉常用的功能菜单命令 学习使用VC++环境的帮助 学习完整的C++程序开发过程 理解简单的C++程序结构

了解用Visual C++开发Windows MFC应用程序的过程

实验内容

本次实验你将学习有关Visual C++开发环境的一些知识,并尝试实现一个简单的DOS程序和Windows程序。通过本次实验,你可以了解用Visual C++开发C++应用程序的过程。

1. 熟悉Visual C++实验环境

8

[实验步骤]

(1) 启动Developer Studio,看看初始化界面由哪些部分组成 (2) 查看各菜单项,看看都有哪些子菜单和命令

(3) 将鼠标放置于各工具条图标上,系统会自动显示该图标代表的命令含义,了解一下都有哪些命令。

(4) 在任意工具条上单击鼠标右键,弹出式菜单上将显示所有可用的工具条,选择其中没有对号(√)的项,看看有什么效果,再选择有对号的项,又有什么效果?

(5) 将鼠标移动到任意工具条上,将鼠标放到图标间隙,按下鼠标左键不放,移动鼠标到屏幕中间,有什么现象发生?再将它拖回到原来位置,有什么现象发生?

(6) 将鼠标移动到左边的工作区窗口,按下鼠标左键不放,移动鼠标到屏幕中间,有什么现象发生?再将它拖回到原来位置,有什么现象发生? (7) 将鼠标移动到下边的输出窗口,按鼠标右键,弹出一个菜单,选择其中的菜单项”Hide”,结果如何?要重新显示该窗口,选择菜单View|Output,窗口是不是又显示出来了?

(8) 学习使用帮助系统。如果你用的是Visual C++5.0,则在工作区窗口的InfoView中选择你感兴趣的内容,双击它,在文档区显示具体的帮助信息。如果你用的是Visual C++6.0,选择菜单Help|Contents,启动MSDN联机帮助系统,学习使用该帮助系统。联机帮助系统是一个相对独立的程序,它和Developer Studio是两个程序,但是它的启动和停止都受Developer Studio影响。MSDN联机帮助系统运行的前提条件是Developer Studio在运行。 (9) 选File|Exit退出Developer Studio。

2. 控制台应用

用AppWizard建立一个控制台应用,在终端上输出”Hello”。 术语:“控制台应用程序”是一个在DOS窗口中运行的基于字符的程序。由于这种模式的应用程序比Windows程序简单,我们先选择利用Visual C++来建立这样一个应用,这样使得我们可以将精力先投入到学习使用C++编程语言,而不需要把过多的精力投入到学习复杂的Windows编程中去。

[实验步骤]

1) 创建第一个应用

首先创建一个项目(project),项目将代表你的应用,存放你应用的所有信息,包括源文件、资源文件、编译连接设置等。创建项目的步骤为:

(1) 启动Developer Studio

(2) 从主菜单中选择File|New,将显示出New对话框

(3) 选择Projects标签,并从列表中单击Win32 Console Application

(4) 在“Location”编辑框中输入你的工作目录名称,如c:\\student\\your_name(问你的指导教师)

(5) 在对话框的右上角的“project name”编辑框内键入项目的名字,如”Hello”,系统将自动为你的项目分配一个默认的目录 (6) 单击OK继续 (7) 如果是VC 6.0,系统将显示一个询问项目类型的程序向导,选择“an empty project” (8) 单击Finish或OK结束配置,创建应用程序

这时系统为你创建一个新的项目,并且在左边的工作区窗口中将出现你项目的名字。工

9

作区窗口除原来的InfoView标签外又增加了两个标签(如果是Visual C++6.0,则没有InfoView标签):ClassView和FileView。ClassView从类的角度显示项目中建立的各个类,双击某个类名将会在右边的文档显示区显示类的定义文件并把文件的当前位置定位到所选的类;FileView显示构成项目的各个文件,选择某一文件将会在右边的文档显示区显示文件内容;InfoView是VC5的帮助文件目录,在这里可以选择所要获取帮助的标题,在右边将显示帮助内容。

2) 编辑你的第一个C++源程序

用下面的方法在你创建的项目中添加一个文件: (1) 在主菜单上选择File|New

(2) 在New对话框中选择File标签,单击“C++ Source File” (3) 选中Add to Project复选框

(4) 在右边的File name编辑框中为文件指定一个名字,如Hello,系统将自动为你加上后缀.cpp。

新的空白文件将自动打开,显示在文档显示区。在文件中输入以下内容(不包括上下两条横线):

//hello world example

#include

int main() { }

cout << \; return 0;

//正常返回

仔细检查你输入的内容,确保内容正确

?[常用编辑命令]

虽然许多编辑命令可以通过菜单和工具栏实现,但大量的编辑命令都可以通过键盘实现。以下命令通常用键盘实现:

? 撤消前一次操作。当你进行了一次错误的操作时,可以通过敲击键盘上的Ctrl+Z

完成;

? 重复前一次操作。通过敲击Ctrl+Y实现

? 剪切一行。用Ctrl+L来删除一行并将它放到剪切板中

? 剪切。将选中的文本删除并将它放到剪切板中,用Ctrl+X实现 ? 复制。将选中的文本复制到剪切板中,用Ctrl+C实现

? 粘贴。将剪切板中的内容放到编辑器中文本的当前位臵处(由光标指示)。,用

Ctrl+V实现

要想了解关于键盘操作命令的完整列表,可以选择Help菜单下的Keyboard Map。你没有必要记住所有的命令,有些根本不常用。

3) 保存你的源文件

单击工具栏中的”save”图标,或者选择File|Save来保存你的文件。

C++源文件的扩展名为.cpp。扩展名非常重要,Developer Studio根据文件的扩展名来区

10

分文件类型,并且根据文件类型提供相应的编辑帮助(如正确的语法高亮显示)。

4) 编译、连接得到可执行程序

编辑结束后,仔细检查你输入的内容,看有无错误。确认没有错误之后,选择主菜单的Build|Build Hello.exe来编译你的项目(也可以按功能键F7)。如果你输入的内容没有错误,那么,在屏幕下方的输出窗口将会显示:

hello.exe –0 error(s), 0 warning(s)

如果在编译时得到错误或警告,是你的源文件出现错误,再次检查你的源文件,看是否有错误,改正它。

5) 改正源程序中的错误

编译的错误会在Developer Studio的下方的输出窗口显示出来,逐个查看这些错误的内容,用鼠标双击,光标可以自动移动到发生错误的源程序的相应地点,仔细检查你的源程序,改正发生错误的地方,注意是否否遗留了分号、引号或括号等。改正后,再重复步骤4)的操作,直到编译连接通过为止。

6) 运行你的第一个程序

你可以有三种方式运行你的程序:

? 在开发环境中运行程序

选择Build|Execute hello.exe(或者Ctrl+F5),在开发环境中执行你的程序。程序运行以后将显示一个类似于DOS的窗口,在窗口中输出一行“hello”,紧接着在下面显示“Press any key to continue”,这句话是系统提示你按任意键退出当前运行的程序,回到开发环境中。按任意键,窗口关闭,退回到Visual C++开发环境。我们实验中将用这种方式运行程序。

? 在DOS环境下运行程序 打开DOS窗口,改变工作路径到项目目录,该目录是你在创建目录时指定的。如果你不记得了,可以在Developer Studio中的工作区窗口中选择项目名称(这里是“hello files”),然后选择菜单View|Properties,将可以显示出项目路径。 切换到debug子目录下,运行hello.exe,程序将输出:’hello’。

? 在Windows环境下运行程序

打开Windows的资源管理器,找到程序所在的目录,运行它。你看到的结果是怎样的?

3. Windows应用

用AppWizard建立一个MFC Windows应用,在窗口中输出”Hello,World!”

术语:AppWizard是一个工具,利用该工具,你可以创建一个建立在MFC基础上的窗口应用程序框架,然后在这个框架中加上自己的应用逻辑。你可以选择所创建的应用类型,最常用的是多文档应用(就象你用的字编辑器Microsoft Word一样,可以同时打开多个文档窗口的应用)、单文档应用(类似于Windows提供的notepad,一次只能打开一个文档)和对话框应用(类似于Windows的时钟程序)。

[实验步骤]

1) 创建一个新项目

11

利用Developer Studio的AppWizard创建一个新的项目,步骤为: (1) 选择菜单File|New,系统将显示New对话框 (2) 选择Projects标签,在显示的项目类型中选择MFC AppWizard(exe) (3) 在右边的Project Name编辑框中输入项目名称,如“helloMFC”,然后按OK (4) MFC AppWizard 将分几步询问你有关要建立的新项目的配置。第一个对话框问你

是创建哪种类型的应用(单文档、多文档还是对话框类型),选择创建单文档应用“Single document”,然后按Next按钮 (5) 翻过后面的五个页面(按Next),每个页面可以让你改变项目的不同选项,这个

例子暂时不设置这些选项。 (6) 最后一个MFC AppWizard屏幕告诉你App Wizard为你自动产生的类。单击Finish

键,AppWizard显示一个关于该项目的摘要,列出这些类和你所选择的特征。如图所示:

(7) 单击OK,系统自动产生helloMFC所需要的文件。

2) 浏览helloMFC项目 当你用MFC AppWizard创建了helloMFC项目后,这个项目的工作区窗口将会打开,工作区窗口除了原来的InfoView以外(VC5.0),增加了三个标签(如下图):

ClassView、ResourceView和FileView,其中ClassView显示工作区中所有项目的类及类的成员;ResourceView显示项目中包含的资源文件;FileView显示项目中的各种文件资源。 你可以先选择FileView看一下AppWizard为你创建了哪些文件,然后选择ClassView看一下定义了哪些类。ClassView中还可以看到一个Globals文件夹,单击它前面的加号,可以看到,有一个预定义的全局变量theApp,这是你的Windows应用程序类的对象。 3) 编译连接运行

12

按F7或者选择菜单Build|Build helloMFC.exe,编译连接得到可执行程序,再按Ctrl+F5或者选择Build|Execute helloMFC.exe运行该程序。程序的结果如下:

4) 用MFC处理输出

现在是你来修改程序的时候了。我们希望在程序中间的窗口上显示一行文字“Hello, World” 。如何修改呢?

(1) 在工作区窗口中选择ClassView标签,单击helloMFC classes前面的加号(如果已

经变成减号则不做此操作) (2) 单击类CHelloMFCView类前面的加号 (3) 双击OnDraw()函数,在右边的文档将显示窗口显示文件helloMFCView的内容,

并且自动将光标定位到函数OnDraw()处。 (4) 修改OnDraw函数的定义,在最后一行加一句:

pDC->TextOut(50,50,\

(5) 按Ctrl+s或者选菜单File|Save来保存所作的修改

5) 编译连接并运行

重新编译连接该项目,运行程序,你可以用Ctrl+F5直接运行程序,系统将询问你是否重新编译该项目,回答“是(Yes)”,如果有编译错误,仔细检查你加的一句话,是否有错。当编译连接通过后,系统会自动运行该程序。结果与上面有什么不同?

经过上述修改后,程序可以输出一行文字“Hello, World”。这是你实现的第一个Windows程序!是不是很简单?!不要担心有很多不懂的地方,后面的实验中你会慢慢理解。现在你应该会觉得:哦,原来Windows程序的开发这么简单!

4. 编写简单的计算程序

输入圆的半径,计算圆的周长和面积并输出。 [测试数据]

输入:2 输出:

The perimeter of the circle : 12.5664 The area of the circle : 12.5664 输入:10

13

输出:

The perimeter of the circle : 62.8318 The area of the circle : 314.159 [实验步骤]

1) 创建一个控制台项目

选择菜单File|New,在Projects标签下选择Windows32 Console Application,输入项目名称“circle”,然后按OK

2) 在项目中增加一个文件

选择菜单File|New,在Files标签下选择C/C++ Source File,输入文件名称“circle”,然后按OK

3) 在文件中输入以下内容

//-------------------------------------------------------------------------------------------------- //该程序让用户输入圆的半径Radius,输出圆的周长Perimeter和面积Area #include #include

const double PI = 3.14159; //定义一个常量PI int main() { int radius; double perimeter, area; cout << \ cin >> radius; perimeter = 2 * PI * radius; //周长=2πR area = PI * pow(radius, 2); //面积=πR2 cout << \ cout << \ return 0; }

注:power(radius,2)表示求radius的平方,power(x, y)是系统预定义的函数,该函数计算x的y次方。该函数的原型在文件math.h中说明。

4) 编译、连接并运行程序 如果你输入的程序有误,用下面的方法定位并修改错误。直到编译连接通过。运行程序,测试数据。

?[编译和连接错误定位] ? 开发环境下方的输出窗口(Output)显示编译和连接过程中出现的错误,错误信息包括:

错误出现的文件名、行号、错误代码。

? 如果不懂错误消息,将光标移动到该错误信息,按F1,就可以显示该错误的帮助信息。 ? 在output窗口,双击错误或者选择该错误再按ENTER键,系统自动将光标移动到发生

错误的源程序行,你然后就可以改正错误。 ? F4键可以选择并定位下一个错误

[思考问题]

(1) 程序中为什么要将头文件math.h包含进来?

14

(2) 建立控制台应用程序的通用步骤是怎样的?

实验二 简单程序开发

实验目的

了解基本数据类型的字节宽度和范围表示

理解并掌握程序的分支、循环结构 提高程序可读性

学习过程化程序设计方法

进一步学习掌握查找与修改编译错误的方法 初步学习调试方法

实验内容

1. 基本数据类型的长度

编写一个程序,输出基本数据类型char, short, int, long, float, double和指针类型void *, char *, short *, int *, long *, float *, double *的数据类型的长度。

[实现要求]:

搞清你所使用系统上运行的C++编译器中每个基本数据类型的长度。

[实现提示]:

利用函数sizeof(数据类型名)来得到各个数据类型的长度

?编辑技巧

Visual C++编辑器功能非常强大,它具有许多优点,你可以在不断的探索中对其了解。下面的特点你可能已有所体会:

? 自动语法。用高亮度和不同颜色的字来显示关键字和注释内容 ? 自动缩进。帮助你排列源代码,使其可读性更强 ? 参数帮助。显示预定义的windows函数的参数

? 集成的关键字帮助。能够使你得到任何关键字、MFC类或Windows函数的帮助信息(按F1即可) ? 拖放编辑

? 自动错误定位。能自动将光标移动到有编译错误的源代码处。

拖放编辑在本次实验中非常有效,因为你需要写很多类似的代码行,借助于拖放功能,你可以方便地实现代码的移动或复制。具体操作方式为:

(1) 将鼠标放臵在要复制的内容的开始部分,按下鼠标左键不放,拖动鼠标,直到要复制内容的结束部分,放开鼠标,此时你选的部分成为反显;

(2) 将鼠标放在选中内容的任意部位,按下鼠标左键,此时鼠标右下方出现一个虚的长方形标志,该标志就表示你将要拖动的内容。如果你想复制所选的内容,则再按住Ctrl键(缺省为移动操作),此时鼠标右下方的长方型标志中间出现了一个十字形; (3) 按住鼠标左键不放,拖动鼠标,你会看到一个虚的光标跟随鼠标移动,将它移动到想要放代码的新位臵,松开鼠标左键(如果按了Ctrl键,在松开鼠标以后再松开按键)。

15

(4) 你所选的代码就可以移动(或复制)到新的位臵。

[思考问题]

为什么所有的指针长度一样?

2. 循环与分支结构

编写一个程序,循环从标准输入读入某雇员的工作时间(以小时计)和每小时的工资数,计算并输出他的工资。若雇员月工作小时超过40小时,则超过部分按原工资的1.5倍的加班工资来计算。若雇员月工作小时超过50小时,则超过50的部分按原工资的3 倍的加班工资来计算,而40到50小时的工资仍按照原工资的1.5倍的加班工资来计算。

[测试数据] 输入:30 4 输出:120 输入:45 4.5 输出:213.75 输入:60 5 输出:425 输入:0 0 程序结束 [实现要求]

(1) 分别用三种循环(for, while, do while)完成程序要求

(2) 要求有输入提示和输出提示,如要输入雇员的工作时间和每小时的工资值时,可以提示:

“Please input employee’s work time and wage_per_hour:” 输出时,提示:

“The employee’s wage :”。

(3) 循环在用户输入的工作时间为0时结束。 (4) 为你的程序加上注释,使得其清晰可读。 (5) 尝试利用调试程序来修改你程序的逻辑错误。

[实现提示]

(1) 可以利用永久循环(while(1))加break语句的方式控制程序流程

?调试(debugging) Visual C++内臵了强大的调试功能。调试发生在你已经成功地进行了编译、连接,得到了可执行程序,但是程序执行的结果不正确的情况下。调试是修改你的代码以便它能够正确工作的过程。Developer Studio提供了许多工具帮助你跟踪和定位错误。调试系统提供特殊的菜单、窗口、对话框等来为开发者提供帮助。

调试命令 有关调试的命令分散在Build、Debug、View和Edit菜单中。Build菜单包含一个Start Debug子菜单,其中的命令是Debug菜单命令的子集,包括:启动调试过程(Go)、单步跟踪( Step Into) 和运行到光标处( Run To Cursor).当启动调试进程后,Build菜单会被Debug菜单代替,Debug菜单包含各种控制程序执行的命令,如单步执行、进入函数体、从函数体中出来、运行到光标所在位臵等。View菜单包含一些命令,可以控制显示各种与调试有关的窗口,如变量窗口(Variables window)、调用栈窗口(Call Stack window)等。

16

Edit菜单下的Breakpoints命令可以打开一个对话框,在其中可以插入、删除、启动、停止各个断点。

设臵断点 你可以控制程序直接运行到指定地点,然后查看运行到这个地方时程序的状态,如变量的值、调用栈的情况等。你可以通过设臵断点来达到这一目的。设臵断点的方式是:将光标移到要设臵断点的地方,按F9,这时会有一个红的圆点出现在代码行的左边。

如果你想取消断点,将光标移动到设臵断点的代码行,按F9。

启动调试 按F5或者在Build菜单中,选择Start Debug然后选择Go,就可以启动调试程序。程序会一直运行到需要用户输入或者有断点的代码处。

查看变量值 查看变量值有多种方式,你可以选择你喜欢的方式进行。1)你可以将鼠标移动到程序的变量名处,系统会自动为你显示变量的值;2)复杂变量(如对象)可以通过QuickWatch查看,方法是:将光标定位到所要查看值的变量处,按鼠标右键,选择QuickWatch菜单,就可以看到变量值。3)启动调试程序后,屏幕下方将会出现两个输出窗口,一个是Watch,另一个是Variable。Watch窗口显示变量名和变量值,你可以在Watch窗口中加上你想观察值的变量名,也可以直接从源代码中选择变量名,并把它拖动到Watch窗口中。Variable窗口显示程序当前运行上下文涉及的变量的值。

控制程序执行 你可以控制程序单步执行(F10)、跟踪到一个函数内部(F11)、从一个函数运行出来(shift+F11)、运行到光标所在位臵(Ctrl+F10),以便方便地调试程序。这些命令用于在某个局部范围详细地调试程序。你也可以通过设臵断点(F9)然后用直接运行(GO或者F5)来控制程序直接运行到断点位臵。如果你设臵了多个断点,程序将会在遇到的第一个断点处停下来。要从断点处继续运行,可以用上面所说的各种命令(F5, F10, F11, Shift+F11, Ctrl+F10)。

结束调试 要结束调试,可以按shift+F5或者选择菜单Debug|Stop Debugging。当结束调试后,所有调试窗口会自动关闭,Debug菜单也会自动还原为Build菜单。

[思考问题]

(1) 哪种循环语句最适合本应用?如果已经知道要计算的雇员的数目(如5个),用哪种循环方便?

(2) 本实验能否用switch语句完成对输入值的判断?

实验三

实验目的

函数与程序结构

掌握函数声明、定义和使用的方法 掌握函数递归调用的方法

掌握全局变量、局部变量、静态变量的使用方法 掌握内联函数、重载函数及默认函数参数的使用方法 掌握自定义头文件的方法,学会建立和调试多文件程序

17

实验内容

1. 分析程序运行结果

输入下列程序,运行它,分析得到的结果。 #include int n = 0;

int func(int x = 10);

void main() {

int a,b; a = 5; b = func(a); cout << \ << \ << \ a++; b = func(a); cout < < \ << \ << \

func(); }

int func(int x ) { int a=1; static int b=10; a++; b++; x++; n++; cout << \ << \ << \ return a+b; }

[实现要求]:

? 运行该程序,得到运行结果

? 分析得到的结果,说明为什么得到这样的结果

2. 递归与非递归函数

18

编写一个函数,求从n个不同的数中取r个数的所有选择的个数。其个数值为:

rCn?n!r!*(n?r)!其中: n! = n*(n-1)*(n-2)*...*1。

[测试数据]: 输入:5 3

输出:10 输入:10 20

输出:Input Invalid ! 输入:-1 4

输出:Input Invalid! 输入:50 3 输出:19600 输入:0 0 程序结束 [实现要求]: (1) 分别用递归和非递归两种方式完成程序设计; (2) 主程序中设计一个循环,不断从输入接收n和r的值,计算结果并输出,当用户

输入0 0时,程序结束;

(3)能检查输入数据的合法性,要求n>=1并且n>=r; (4)上面的测试数据能得到正确结果。 [实验步骤]

(1) 利用一个非递归函数fn(int n)计算n!,利用另一个函数Cnr(int n, int r)计算Cnr,在该函数中调用fn(),

问题:你打算用什么样的变量类型来存放n!函数返回的值?注意各种数据类型的内存字长不同,整数能存放的数据范围有限,你如何解决? (2) 利用一个递归函数实现,实现时利用公式:

C(n,r) = C(n, r-1) * (n – r + 1) / r

递归实现. [实现提示]:

(1) 可以用double数据类型来存放函数的计算结果 (2) 递归结束条件:

如果 r = 0 ,则C(n, r) = 1 如果 r = 1, 则C(n, r) = n

[思考问题]

(1) 你对各种数据类型的字长是否有了新的认识? (2) 递归函数的书写要点是什么?

(3) 你觉得递归和非递归函数哪种好些?

3. 将上面的程序改成多文件结构 [实验要求]

将上面用非递归方式写成的程序改成用多文件结构表示。要求将main()函数放在一个文件中,将另外两个函数放在另一个文件中,将函数原型说明放在一个头文件中。建立一个

19

项目,将这三个文件加到你的项目中,编译连接使你的程序正常运行。

[实验步骤]

(1) 新建一个项目,命名为“multifile”

(2) 用File|New创建一个新的”C++ Source File”,命名为main.cpp (3) 用File|New创建一个新的”C++ Source File”,命名为func.cpp (4) 用File|New创建一个新的”C/C++ Header File”,命名为func.h

(5) 用File|Open打开你前面实验中用非递归方式求C(n,r)的C++源程序文件,将其中的主函数部分拷贝到main.cpp中,将其中的两个函数实现放到func.cpp中,再将两个函数的原型写到func.h中

(6) 在main.cpp 中包含进头文件:

#include “func.h”

(7) 编译连接该项目,运行它。你得到的结果应该和上一个实验一样。

[思考问题]

(1) 多文件结构中头文件的作用是什么? (2) 将程序划分为多个文件有什么好处?

实验四 复杂数据类型

实验目的

学习数组的定义、初始化、赋值和使用的方法 学习给函数传递数组的方法

学习指针和引用的定义和使用方法 学习字符串的使用方法

学习用指针和引用给函数传递参数

实验内容

1. 数组排序

从键盘读入若干整数,将它们按由低到高排序输出。 [测试数据]: 程序先输出: Please input array number: 用户输入: 5

程序再输出: Please input all the integer: 用户输入: 300 700 600 450 500

程序输出: 300 450 500 600 700 [实现要求]:

(1) 用一个数组存放各个整数;

(2) 在主函数main()中实现数据的输入和输出操作,并用一个函数实现对数组元素的排序操作。

(3) 排序函数调用另一个函数swap()实现两个数组元素的交换。可以使用指针、引用两种方式实现函数参数的传递:

swap(int *pa, int *pb);

20

swap(int & a, int & b);

[实现提示]:

排序可以用最简单的选择排序法:

选择排序法:

1) 从n个数中选择最小的一个,把它和第一个数组元素交换;

2) 从剩下的n-1个数中选择最小的一个,把它和第二个数组元素交换;

3) 依此类推,直到从最后两个元素中选出倒数第二小的元素并把它和倒数第二个元素交换为止。

如要按选择排序法对数组30 50 21 39 20排序,则各趟排序后的结果如下所示(带下划线的数表示参加交换的数):

开始: 30 50 21 39 20 第一趟排序:20 50 21 39 30 第二趟排序:20 21 50 39 30 第三趟排序:20 21 30 39 50 第四趟排序:20 21 30 39 50

[实验步骤]

(1) 用数组实现程序要求

说明:用一个长度为10的数组存放待排序的数据,数组的定义为

int iArray[10];

数组排序函数的原型为: void sort(int num, int iArray[]); 其中num表示数组元素的个数,iArray是数组。 (2) 用动态申请空间的方式实现程序要求。 说明:使用指针来实现前面数组的功能

int *piArray;

piArray = new int[num];

其中数组的大小num需要由用户预先输入。

[思考问题]

(1) 上面两种实现方式对程序的改动大吗? (2) 尝试用不同的方式访问数组中的元素

iArray[i], *(iArray+i), piArray[i], *(piArray+i), (3) iArray和piArray有何共同点?

2. 字符排序

修改上面的程序,将数组的操作改为对字符串操作,即从键盘输入一串字符,将它们存放在字符数组中(形成一个字符串),然后对字符数组中的各个字符排序。

[测试数据]:

输入内容:kapdobc 输出内容:abcdkop [实现要求]:

(1) 用字符数组代替上一个实验的整数数组;

(2) 不要先输入字符串的长度,在程序中自动计算出字符串的长度。

21

[实现提示]:

(1) 字符串的输入输出操作可以简化,不用一个字符一个字符的输入输出

(2) 字符的长度可以借助于预定义的函数strlen()求出,该函数所在的库函数名为

string.h

[思考问题] 对字符的比较遵循什么样的约定(为什么字符a比字符b小)?

3. 字符串操作

要求和上面类似,但数组中的元素变为字符串。程序对已有的字符串进行排序,并输出排序后的结果。字符串数组中的元素为:

January, February, March, April, May, June, July, September [测试数据]: 程序直接输出排序后的结果: May July June April March January Februrary September [实现要求]:

(1) 排序的规则为:先比较两个字符串的长度,长度短的字符串排在前面,如果长度相等,则比较字符串的值,按从小到大排序输出。

(2) 用字符串数组存放各字符串,并在定义数组时对其进行初始化 (3) 利用库函数qsort实现排序操作 [实现提示]:

(1) 使用库函数qsort必须包含头文件; (2) qsort的函数原型为:

void qsort(void *base, //所要排序的数组第一个元素的地址

size_t nelem, size_t width,

//要排序的元素的个数 //要排序的元素的宽度

int (*fcmp)(const void *, const void *));//用于比较元素大小的函数名字

其中,比较数组元素大小的函数原型为:

int (*fcmp)(const void *, const void *);

其两个参数分别指向两个要比较的数,结果用小于零、等于零和大于零分别表示第一个数小

于、等于和大于第二个数。你需要定义自己的字符串比较函数,其原型和上面的一样。函数的定义如下:

int sort_function( const void *a, const void *b) {

if (strlen((char *)a) != strlen((char *)b)) }

return strlen((char *)a) - strlen((char *)b);

return( strcmp((char *)a,(char *)b) );

22

[思考问题]

如果让你来实现qsort函数的功能,如何实现?

实验五 结构和类

实验目的

学习结构的定义和使用

学习使用结构构建链表式数据结构 理解结构与指针的关系

学习类的定义、实例化的方法 学习使用构造函数和析构函数 学习类成员访问控制的运用

学习使用静态成员、内联成员函数 学习堆对象的分配、使用与释放 体会面向对象程序设计方法

进一步熟悉Visual C++的编译连接错误,掌握Visual C++调试工具

实验内容

1. 用结构构建链表

设计一个单向链表。从标准输入读取若干整数,建立链表,每次读入的数放入链表结尾。当用户输入 0时,结束链表的建立工作。然后从前往后依次输出链表节点中的内容。链表的结构类似于下图:

11 56 4 43

表尾指针 表头指针

每个节点包含两个值,一个是真正存放的整数值,另一个为指向链表中下一个节点的指针。链表中最后一个节点不指向任何节点,所以指针为空(NULL)。表头指针和表尾指针分别指向链表的头节点和尾节点。 [测试数据]

程序输出:Please input integers to build the link(0 TO END): 用户输入:3 4 5 6 7 8 9 0

程序输出:Link elements:3 4 5 6 7 8 9 [实现要求]

(1) 用链表存放输入的整数。链表节点空间动态申请。链表节点结构和链表数据类型的参考定义为:

//定义链表节点类型 typedef struct node{ int elem;

struct node *next; } Node; //定义链表类型

23

typedef Node * Link;

(2) 在链表建立结束后,输出链表节点内容的同时释放节点空间。 (3) 处理申请不到空间的情况。 [实现提示]

(1) 因为每次插入节点是在链表尾,而输出链表是从链表头,所以可以用两个指针记录链表。一个为头指针,一个为尾指针。

(2) 第一次插入节点时需要考虑链表为空的情况。 (3) 建立一个新节点的过程为:

// 建立一个新的节点 Node * pNode = new Node; if (pNode == NULL) { }

pNode->elem = k; pNode->next = NULL; Node *pNode = head; while (pNode != NULL) { }

访问pNode所指节点的内容; pNode = pNode->next;

cout << \break;

(4) 顺序遍历链表的过程为:

(5) 将新节点加到链表中的过程为:

if (head == NULL) }

head = tail = pNode; tail->next = pNode; tail = pNode; else {

[思考问题]

(1) 如果是双向链表,程序要做那些改动?双向链表的示意图如下:

11 56 43 4

表头指针 表尾指针

2. 队列类

设计一个队列类,模拟实际生活的队列,队列中的元素服从先进先出的规则。每次有新的元素入列时,就放在队列尾。元素出列时,从队列头出。开始时队列为空。队列的示意图为:

出队列 入队列

24

队头指针 队尾指针

[测试数据] 输出结果:

Queue empty:Yes

4 elements enter queue Queue empty:No

2 elements leave queue:0 1 2 elements enter queue Elements left:2 3 4 5

[实现要求]

(1) 利用上一实验设计的链表结构存放队列类中的队列元素。也即队列元素的空间是动态申请的。

(2) 在构造队列对象时,初始化该链表,在析构队列对象时,释放链表所占的空间。 (3) 队列类用单独的文件”queue.cpp”实现,队列类的定义放在一个头文件”queue.h”中,主文件名为main.cpp。

(4) 队列类中的元素为整数,并提供以下服务:

void put (int newVal); //在队尾加入一个新元素 int get ( ); //取出队头元素,并释放节点空间 int getCount(); //取队列中元素的个数 bool empty() ; //判断队列是否为空

bool是Visual C++定义的数据类型,它其实是一种整数类型。具有bool类型的变量只有两种值:true或false。一个条件表达式返回的值就是bool类型的。如表达式i!=0在i的值为0时返回false,在不为0时返回true。

要求将函数empty()和getCount()定义为内联函数,另外两个定义为非内联函数。数据成员全定义为私有成员或保护成员。

(5) 使用队列类的主程序为:

#include #include \int main() {

Queue q; int i;

//输出队列是否为空 cout << \if (q.empty())

cout << \else

25

cout << \

//往队列中依次放入四个数

cout << endl << \for(i = 0; i < 4; i++)

q.put(i);

//输出队列是否为空 cout << \ if (q.empty()) cout << \ else

cout << \ //取出两个数

cout << endl << \ cout << q.get() << \ cout << q.get() << \

//再放入两个数

cout << endl << \ q.put(4); q.put(5);

//按顺序输出剩下的队列成员 cout << \ int num = q.getCount(); for(i = 0; i < num; i++)

cout << q.get() << \ cout << endl; return 0;

}

[实现提示]

(1) 队列中使用的链表定义为:

//定义链表节点类型 typedef struct node{ int data;

struct node *next;

} QueueDataNode; //定义链表类型

typedef QueueDataNode * QueueData;

队列类的私有数据成员定义如下:

26

int count; //队列元素个数

QueueData dataLinkHead, dataLinkTail;//队头、队尾指针

(2) 在队列中插入一个元素的程序如下:

void Queue::put (int newData) {

//建立一个新的结点

//将新节点插入到链表中 if (dataLinkTail == NULL)

else { //队列不空

dataLinkTail->next = pNew; dataLinkTail = pNew;

//队列为空, 新结点成为第一个结点

dataLinkHead = dataLinkTail = pNew; QueueDataNode *pNew = new QueueDataNode; if (pNew == NULL) {

return;

//为新节点填充内容

cout << \

//判断是否申请到空间

}

pNew->data = newData; pNew->next = NULL;

} count++; }

3. 静态成员

修改上一个实验得到的队列类,为其增加一个静态数据成员,可以记录程序中产生的队列个数。 [测试数据] 实验结果:

Total Queues:3 Total Queues:5 Total Queues:4

实现要求]

(1) 提供静态成员函数getQueueNumber(),可以返回队列的个数,函数原型为: int getQueueNumber(void);

(2) 每当构建一个新的队列对象时,队列计数自动增1,而队列对象消亡时,队列计数自动减1

(3) 下面是测试你设计的类的主程序:

#include #include \

void main() { Queue q1,q2,q3;

27

Queue *q4,*q5;

cout << \

q4 = new Queue; q5 = new Queue;

cout << \

delete q4;

cout << \

delete q5; }

[实现提示]

在队列类的构造函数和析构函数中分别做计数值增减的操作。 [思考问题]

(1) 在什么地方初始化类的静态数据成员? (2) 非静态成员函数能否访问静态数据成员?

实验六 继承与虚函数

实验目的

了解类的两种使用方式

学习从现有类派生出新类的方式 了解在派生类中如何使用基类的成员 了解基类成员在派生类中的访问控制 了解虚函数对多态性的支持

实验内容

1. 继承

队列具有先进先出的特点,所有新来的元素都放在队列尾部,出队列的元素从队列头部出去。下面是队列的示意图:

出队列 入队列

队尾指针 队头指针

入栈 出栈 栈具有后进先出的特点。所有入栈的元素都放在栈顶,出栈时栈顶元素先出。右面是栈的示意图。 栈顶 这两种结构具有很多相似的地方:都存放了一系列的元素,元素的操作都在两头进行,元素个数都是动态可变的。我们可以设计一个基类,完成它们共同的功能,然后分别派生出

28

栈底 队列类和栈类。这样可以减少代码,提高效率。设计的基类也可以用于派生出其他类。本实验要求设计这个基类以及它的两个派生类。

[实验要求]

(1) 设计基类LinkList。LinkList的实现类似于实验五中的队列类,用链表结构实现。要求链表类具有以下功能:

? 能够在链表的头尾增加节点以及在链表头删除节点 ? 能够记录链表的个数(用静态成员) ? 能返回链表中的节点个数 ? 能查看链表头节点的元素值 ? 能告知链表是否为空

? 在链表类的构造函数中初始化链表

? 在链表类的析构函数中释放链表所有元素的空间

下面给出链表类的类定义,你需要根据该定义完全实现该类。

//用链表实现的列表类 class LinkList { //定义链表节点类型 typedef struct node{ int data;

struct node *next;

} ListDataNode; //定义链表类型

typedef ListDataNode * ListData;

protected:

int count;

//列表中元素的个数

ListData dataLinkHead, dataLinkTail;//表头、表尾指针

static ListCount; //列表个数 public:

LinkList(void);

//构造函数

virtual ~LinkList(void); //析构函数 void putTail (int newData); //在表尾加入一个新元素 void putHead (int newData); //在表头插入一个新元素

int getHead (void);

//从表头取出一个元素

int peekHead(void) ;//查看表头元素的值,假定列表至少有一个元素 bool empty ( );

//检查列表是否空

int getElemCount() ; //取列表元素个数

static int getListNumber();

//取列表个数

};

29

(2) 在上面实现的链表类的基础上派生队列类和栈类,要求队列类可以进行元素入队列和出队列操作以及取队列长度操作,栈类可以进行入栈和出栈操作,还可以查看栈顶元素的值。

(3) 在队列类中实现一个输出队列内容的函数printQueue,输出格式为:

Queue head-->0 : 1 : 2 : 3

其中,0、1、2、3为队列中的元素,0是队头。

在栈类中实现一个输出栈中内容的函数printStack,输出格式为:

Stack member: | 3 | | 2 | | 1 | | 0 | -----

其中,3、2、1、0是栈中元素,3为栈顶元素。 (4) 用多文件结构实现程序。三个类的定义放在一个头文件中,类的实现放在另一个源文件中。主程序用于测试你所设计的三个类的正确性。测试内容包括:

? 在队列中加入几个元素,用printQueue()打印队列内容,然后再从队列中取出这些元素,看是否正确

? 在栈中加入几个元素,用printStack()打印栈的内容,然后再从栈中取出这些元素,看是否正确

? 测试取队列长度的函数getQueueLength()的正确性 ? 测试判断栈是否为空的函数empty()的正确性

[实现提示]

(1) 链表类的实现可以参考实验五中队列类的实现。 (2) 测试程序可以用如下程序:

#include #include \

void main() {

Queue *q1 = new Queue; Stack *s1 = new Stack;

//输出总的列表数

cout << \

//在队列和栈中加入元素 for (int i = 0; i < 4; i++) { }

//输出队列长度和队列中的元素个数

q1->enQueue(i); s1->push(i);

30

cout << \ q1->printQueue();

//输出栈的内容

cout << \ s1->printStack();

//取出队列和栈中的元素 for (i = 0; i < 4; i++) { }

//输出队列长度

cout << \ //检查栈是否为空 cout << \ if (s1->empty())

delete q1; delete s1;

//输出总的列表数

cout << \}

cout << \cout << \

else

cout << endl;

q1->delQueue(); s1->pop();

[思考问题]

(1) 为什么要将LinkList类的析构函数定义为虚函数?

(2) 如果想让LinkList类更通用一些,如可以随机访问表中任意位置的节点值,可以顺序依次访问链表中的各个节点,类的定义应该做哪些修改?参考Visual C++MFC库中预定义的列表类CObList,看看通用的列表类会提供哪些操作。

(3) 如果如果有兴趣,可以在三个类中加入能够分别记录链表类实例、队列类实例和栈类实例各自个数的成员,并测试一下,观察基类的静态成员和派生类的静态成员是什么关系。

2. 虚函数

修改上面实验的程序,将printQueue和printStack改为用虚函数print实现。在基类LinkList中定义一个虚函数print(),输出链表中的成员,输出形式为:

LinkHead-->0-->1-->2-->3

31

其中0、1、2、3是链表中的节点的内容。 在派生类中重定义该函数,Queue类和Stack类的print函数的实现和前一实验一样。测试你修改正确与否的程序如下:

//测试其他功能的程序代码 ……

//测试虚拟函数:

cout << \pl = q1;

pl->print(); //应调用Queue::print() pl = s1;

pl->print(); //应调用Stack::print()

pl->LinkList::print(); //应调用LinkList::print(); Queue *q1 = new Queue; Stack *s1 = new Stack; LinkList *pl;

[思考问题]

(1) 为什么同一个指针pl调用的函数print会得到不同的结果? (2) 虚函数带来的好处是什么?

实验七 重载与文件I/O

实验目的

学习函数和操作符重载的方法 学习进行格式化输入输出

学习使用C++预定义的文件I/O类进行文件输入输出

实验内容

1. 文件输入输出

从输入文件\中读入文件内容,为每一行加上行号后,输出到输出文件“file.out\中,最后,输出所读文件总的字符数

[测试数据]:

输入文件内容(file.in):

#include int main() {

}

cout << \return 0;

输出文件内容(file.out):

1 #include

32

2 3 int main() 4 { 5 6 7 }

Total charactors:67

cout << \return 0;

[实现要求]:

(1) 行号占5个字符宽度,且左对齐; (2) 能处理文件打开错误;

(3) 文件字符总数不包括换行符 [实现提示]: (1) 利用setw和setiosflags(ios::left)来控制行号的输出(需要在程序中包含头文件

iomanip.h); (2) 利用长为1000的字符数组作为缓冲区存放读取的一行内容,利用函数

istream::getline进行读取一行的操作; (3) 利用strlen求字符串长度(需要在程序中包含头文件string.h) [实验步骤]

(1) 在你的程序目录下创建一个文本文件file.in,在其中输入上面的测试数据

(2) 完成所要求的程序,该程序读取文件file.in的内容,并产生输出到文件file.out中 (3) 打开文件file.out查看输出的文件内容

?[在VC中创建一个独立的文本文件] 选择菜单File|New,在new对话框中选择Files标签,选择列表中的“Text File”,并清除右上角的“Add to Project”复选框,此时其他编辑框都变灰。按OK结束创建。在Developer Studio的文档显示区会显示一个空白的文档,在上面输入你想要输入的内容,然后选菜单File|Save,此时系统会问你该文档的名字,将文件以合适的名字存放到合适的目录。

2. 操作符重载

为实验六中的队列类重载抽取与插入运算符 [实验要求]

(1) 重载队列类的抽取与插入运算符,使用这些运算符的主程序如下:

#include #include \

int main() {

cin >> q;

//输入队列元素

Queue q;

cout << \

cout << \cout << \

33

}

return 0;

cout << \cout << q;

//输出队列元素

cout << endl << \输出队列长度

(2) 抽取操作符从输入流中读取队列对象的信息,读入的格式为:

队列元素个数n:元素1,元素2,...,元素n

队列元素之间用逗号隔开,队列个数值和第一个元素之间用冒号隔开。如队列有5个元素,分别为12,24,31,45,22,则输入流中的内容为:

5: 12, 24, 31, 45, 22

(3) 插入操作符将队列对象的内容放到输出流中,格式为:

元素1,元素2,...,元素n

如上面读入的队列的输出为: 12, 24, 31, 45, 22 [实现提示] (1) 将重载的两个操作符定义为类Queue的友元函数 (2) 两个函数的原型分别为:

ostream & operator << (ostream & , Queue &); istream & operator >> (istream & , Queue &); [思考问题]

(1) 为什么要将抽取和插入操作符定义为友元,而不是直接定义为成员函数? (2) 重载操作符的第一个参数和返回值为什么都用引用?

实验八

实验目的

面向对象程序设计

了解Windows程序的消息机制和编程模式 了解MFC类库结构

了解AppWizard自动生成的程序框架 了解Windows程序运行结构 学习简单的绘图操作

了解利用VC++的MFC类库设计面向对象应用程序的过程

实验内容

1. Windows编程模式

[实验要求]

阅读以下内容,了解Windows程序和控制台应用程序的不同。 ?[Windows编程模式]

Windows程序不同于控制台模式程序。在编程时有以下特点:

(1) 多任务。 Windows 是一个多任务的操作系统,在同一时间内可以执行多个应用程

34

序。应用程序无法独占所有系统资源(CPU、内存、屏幕、键盘、鼠标等)。Windows操作系统必须小心管理所有系统资源,以便所有应用程序可以分享,而所有Windows应用程序则必须根据Windows操作系统特有的接口来执行操作,以确保Windows操作系统有效地管理系统资源。基于控制台模式的程序假定是在单用户操作系统下运行,运行的应用程序可以独占所有系统资源,不必考虑和其他应用的分享。

(2) 通过窗口进行输入输出。Windows环境下,若想执行输入输出操作,必须在屏幕上开一个窗口,然后通过此窗口,执行输入与输出。应用程序也可以开多个窗口,执行多文档操作。而控制台模式下,只要执行简单的函数调用,就可以将信息输出到屏幕上。 (3) 通过消息接受数据输入。Windows环境下,所有的用户输入都由系统统一管理,系统接收到用户输入后,进行分析,将该输入以消息的形式发到合适的应用程序的消息队列中,每个应用程序都有一个消息队列。应用程序的运行过程就是不断从消息队列中取消息并进行处理的过程。

(4) 数据输出以绘图模式进行。Windows环境下,绘图模式是基本的工作模式,用户所有的输出都需要通过图形设备接口进行。

2. Windows应用程序的结构

阅读以下内容,然后完成后面实验要求中的内容 WinMain()

Windows应用程序都有一个主程序WinMain(),该程序是Windows应用程序的主过程。在MFC应用框架下产生的应用程序不用显式写这个函数,系统自动提供。开发人员只需在自己的应用程序对象(该对象是从类CwinApp派生的应用程序类的实例)中重载有关应用程序初始化、应用程序退出的函数来使程序按照自己的意愿执行。

WinMain()的执行过程是:调用应用程序对象的InitInstance 成员函数来初始化应用程序,然后调用它的Run()成员函数来处理应用程序的消息循环。当程序运行结束时,Run()调用应用程序的ExitInstance成员函数来做一些清除工作。下面是这一过程的示意图:

注:上图中粗体字表示由系统提供的函数,正常体字表示由程序员提供或重载的函数

CWinApp

所有使用MFC类库的应用程序都有且只有一个“应用程序对象”,该对象负责应用程序初始化和退出时的清理工作,并且进行应用级的消息处理。应用程序对象所属的类从CWinApp类派生而来。应用程序对象提供初始化应用程序和运行应用程序的成员函数。该对象是整个应用程序创建的第一个对象,在系统调用WinMain()之前就已经生成,因此必须将该对象声明为全局变量。

从CWinApp派生的应用程序类必须重载InitInstance成员函数以便建立应用程序的主窗口对象。此外,在应用程序对象中还可以重载以下函数:

? Run() 循环进行消息处理。它负责检查消息队列,如果有消息,则分发它进行处理,如果没有消息,则调用OnIdle进行空闲时间处理。Run还调用 ExitInstance来退

35

出应用程序。

? ExitInstance() 负责程序退出时的清理工作。它只能由Run函数来调用。

? OnIdle() 当应用`程序的消息队列为空时,会执行一个缺省的消息循环,在该循环中调用OnIdle()函数。应用程序可以通过重载该函数来完成一些后台工作。

消息

用VC写出的应用程序是消息驱动的。诸如鼠标单击、敲键盘、窗口移动之类的事件,由Windows以消息形式分发给正确的窗口进行处理。许多消息是用户与应用程序的交互产生的,当鼠标单击一个菜单项或工具条上的某一按钮时,就会产生命令消息,用户移动一个窗口或是放大、缩小一个窗口时,也会产生消息。程序的启动或停止、窗口失去焦点等都会产生消息。应用程序的run函数就负责检查并分发消息给合适的窗口处理。

能够接受消息的类一般会在定义时声明一个“消息映象(MESSAGE_MAPPING)”,该映象说明了该类对象可以接受并处理的消息,并且建立了消息和处理消息的成员函数之间的对应关系。

VC++中可以接受消息的类都会定义一个消息映象,消息映象的定义自成一体,形式为: BEGIN_MESSAGE_MAP(类名,父类名) ON_COMMAND(消息名,处理消息的成员函数名) …

END_MESSAGE_MAP()

[实验要求]

用实验一中介绍的方法,用AppWizard创建一个使用MFC库的应用程序HelloMFC。查看构成项目的各个文件,看看上面介绍的内容你能理解多少。

[实验步骤]

(1) 单击菜单File|New,选择projects标签下的MFC AppWizard(.exe),在项目名字编辑框中输入helloMFC,然后单击OK

(2) 在下一个对话框中,选择Single Document创建一个单文档应用。然后按Finish略国后面几个对话框。在最后一个对话框中按OK。此时新的项目产生

(3) 在工作区窗口中,选择FileView标签,然后单击窗口中的Header Files前面的+号,在展开的文件名中选文件HelloMFC.h,双击它,在右边显示文件内容。

在该头文件中定义了你的应用程序类CHelloMFCApp,它从类CWinApp派生而来。可以看到,该类中重载了函数InitInstance。

(4) 在工作区窗口中,选择FileView标CHelloMFCApp签,然后单击窗口中的Source Files前面的+号,在展开的文件名中选文件HelloMFC.cpp,双击它,在右边显示文件内容。在文件中找到下面两行:

// The one and only CHelloMFCApp object CHelloMFCApp theApp;

theApp是你的应用程序类的唯一一个实例,它负责你应用程序的初始化(看到该类重载的函数initInstance的实现吗?)。在你的程序找不到类似于控制台应用程序的main()函数的WinMain()函数,因为系统已经帮你实现好了。

(5) 如果你想了解系统是如何执行你的程序的,可以选择菜单Build|Start Debug|Step into或者直接按F11来启动调试程序,跟踪系统的执行路径,你会发现,系统首先执行的是

36

一个WinMain()(也许叫AfxWinMain())函数。

(6) 查看你的HelloMFC应用程序,分别打开HelloMFC.CPP, HelloMFCDoc.CPP, HelloMFCView.CPP, MainFrm.CPP,查看每个文件中的消息映象。看看每个类都能接受并处理那些消息。

3. Visual C++类库

阅读以下关于MFC类库的介绍,然后通过查看Visual C++帮助,了解Visual C++所带类库的结构。 ?[MFC类库]

MFC类库中的所有类一起构成了一个应用程序框架,这个框架提供了一般Windows程序所具有的成分,程序员的任务就是在该框架下填充与应用程序的具体逻辑相关的内容。MFC类库中包含的类大致可以分为以下几类:

(1) 应用体系结构类。这些类提供应用程序框架,它们提供大多数应用程序所具有的功能,程序员在这些框架下填充具体的应用逻辑。程序员一般是从这些框架类派生出自己的类,然后在派生类中增加新的成员或重载原来的成员函数来实现自己程序的功能。 使用AppWizard可以自动生成应用程序框架,构成这个框架的类就是从应用体系结构类中的各个类派生出来的。应用体系结构类中包含: 1)应用、线程支持类;2)文档、视图、框架窗口类;3)命令路由类。

(2) 文件、数据库类。通过这些类,应用程序可以将信息存放到数据库或文件中。有两大类数据库类:DAO和ODBC,它们的功能类似。有一些类负责处理标准的文件操作、ActiveX流以及HTML流。 (3) 绘图、打印类。Windows中,所有的图形输出都是送到一个称为DC(Device Context)的虚拟的绘图区域,MFC提供了各种类来封装各种类型的DC以及Windows的绘图工具如位图、刷子、调色板、画笔等。

(4) 窗口、对话框、控制类。CWnd类是这一分类中的所有类的基类。它们定义了各种类型的窗口。包括框架窗口、视图、对话框、对话框中的各种控制等。 (5) 简单数据类型类。这些类封装了各种常用的简单的数据类型,如绘图坐标(CPoint, CSize,CRect)、字符串(CString)、时间与日期信息(CTime, COleDateTime, CTimeSpan, and COleTimeSpan)等。这些对象通常用做Windows类的成员函数的参数。它们都提供了许多有用的成员函数。

(6) 数组、表和映象类。这些类用于处理有聚集数据的情形,包括数组、列表和映象(maps)。映象是一种非常有用的类,它可以容纳不同类型的对象的聚集。这些集合类都支持动态分配空间,而且可以用在非Windows程序中。类的使用方式也很灵活,你可以直接使用这些类,可以从它们派生出自己的类,也可以从模板类中构造自己的聚集类。 (7) 互联网和网络类。这些类提供了利用ISAPI或者Windows Socket与其他计算机交互的功能。利用这些类,可以编制Internet服务程序、网络通讯程序。

(8) OLE类。OLE类可以和其他的应用程序框架类一起工作,提供对ActiveX API的方便的访问方式。

(9) 调试及异常类。这些类支持对动态内存分配的调试以及异常信息的产生、捕获与传递。

?[MFC中几个重要的类]

(1) CObject CObject是MFC类库的主要基类。它不仅可以作为库中的类的基类,还作为你所写的类的基类。用CObject作为基类可以提供以下好处:

37

? 串形化(serialization)支持。\是将对象存入永久存储媒体(如磁盘)或从永久存储媒体读取对象信息的过程。MFC的CObject对象内臵对Serialization的支持,因此所有从该类派生的类的对象也继承了这一特征。serialization的基本思想是:对象应该能将它的当前状态信息保存起来,在将来的某一时刻能够重新恢复其状态;对象自己应该负责其状态的存取。因此,支持Serialization的对象应该实现一个成员函数完成这一功能。MFC使用类CArchive的对象来担任存储媒体和要存储的对象的中介。这个对象一般会和一个文件类CFile的对象相连。CArchive对象使用重载的抽取(>>)和插入(<<) 操作符来完成读写操作。

? 运行时类(Run-time class)信息支持。从CObject派生的所有类都与一个 CRuntimeClass结构相关联,这个类可以在运行时提供你对象的有关信息,如对象所属的类的名字、对象是否是从某个类派生而来等,还支持对象的动态创建。这在某些程序中非常有用。

? 对象诊断输出。从CObject派生的类可以重载Dump()成员函数,将对象的成员以文本的形式写入一个dump设备,这些信息可以用作调试时的诊断信息。

? 与集合(collection)类的兼容。集合类中存放的对象要求以CObject为基类。这样可以提供多态性。所有从CObject派生出来的类的实例都可以作为对象存放到CObList的实例中。

(2) CWnd CWnd是CObject的派生类,也是所有Windows类的基类,它提供MFC中所有窗口类的基本功能。它可以接受并处理与窗口操作有关的消息(OnMessage成员函数),你可以在你的CWnd派生类中重载其成员函数OnMessage来控制其对消息的响应。在你的应用程序中可以创建若干子窗口。在MFC类库中,有很多类从CWnd派生出来以提供不同的功能。其中很多类如CFrameWnd, CMDIFrameWnd, CMDIChildWnd, CView, CDialog又都是设计成由用户进一步派生而用。另外一些控制类,如CButton,则既可以直接使用,也可以进一步派生。

[实验步骤]

(1) 启动Developer Studio。

(2) 如果你使用的是Visual C++6.0,则启动MSDN,选择“Visual C++ Document”为活动子集,选择“目录”标签,单击”MSDN Library Visual Studio 6.0”左边的加号,如果你使用的是Visual C++5.0,则点击工作区窗口的InfoView标签。

(3) 在上面打开的目录中查找关于MFC Class Library Reference的内容

(4) 双击上面所展开的目录下的Hierachy Chart,右边的文档显示窗口显示Visual C++MFC类库中的所有类的层次结构。你会看到,除很少一部分类外,大多数MFC类都是从类CObject派生而来。

(5) 单击你想了解的类名,阅读有关该类的一些说明信息。当你看完,想返回到前面一页时,按”Standard”工具条上的后退符号(?)返回。

4. 理解AppWizard自动创建的程序框架

阅读以下内容,了解WindowsMFC应用程序框架结构。然后再次查看你生成的HelloMFC应用程序,进一步理解你应用程序的各个组成部分。

?[文档、视图与框架窗口]

MFC程序框架的核心概念是由文档、视图和框架窗口组成的文档模板。

38

文档是一个用户可以与之交互(如编辑、阅读)的数据对象,它通过使用文件菜单中的New或者Open命令创建,通常可以存放在一个文件中。

视图是一个窗口对象,文档的内容显示在这个窗口中,用户也只有通过这个窗口对象才能与一个文档交互。视图对象可以控制用户如何看到文档中的数据以及如何与之交互。一个文档可以有多个视图。

框架窗口提供了视图生存的场所。视图显示在框架窗口内部,框架窗口提供了工具条(以便接受用户命令)和状态条(以便显示文档状态)。

文档模板是用来组织文档、视图和框架窗口之间关系的一个类。它可以控制在一个文档打开时,创建相应的框架窗口和视图来显示文档。

MFC中提供文档、视图、框架窗口和文档模板的基类,应用程序可以从这些基类派生出自己的类来实现自己的应用逻辑。文档类可以从类CDocument派生,视图类可以从类CView, CScrollView, CEditVie等类派生,框架窗口类可以从类CFrameWnd(在SDI应用中)或者类CMDIFrameWnd(在MDI应用中)派生;文档模板类可以从类CDocTemplate派生而来,一种文档模板类控制一类文档的创建和显示。支持多种文档类型的应用需要定义多个文档模板。SDI应用程序使用模板类CSingleDocTemplate而MDI应用使用模板类CMultiDocTemplate。

所有这些类对象都是从应用程序对象直接或间接地生成出来。在程序运行开始,只有一个应用程序对象。应用程序对象可以创建文档模板。文档模板然后可以控制文档的创建、打开和关闭,在文档打开时,相应的框架窗口和视图也由文档模板自动创建,文档关闭时,相应的框架窗口和视图也会自动关闭。下面的图显示了在一个SDI应用程序中对象彼此之间的关系:

[实验步骤]

(1) 在你的HelloMFC应用程序中,查看文件HelloMFC.cpp中的应用程序类CHelloMFCApp的成员函数InitInstance的实现,在其中找到创建文档模板的语句:

CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CHelloMFCDoc),

RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CHelloMFCView));

AddDocTemplate(pDocTemplate);

可以看到,文档模板创建时使用了三个类(文档类、视图类和框架窗口类)作为其参数。

39

(2) 分别打开定义CHelloMFCDoc、CMainFrame和CHelloMFCView这三个类的头文件,看看它们都是从哪个类继承而来。CHelloMFCView中定义了一个函数GetDocument(),这个函数可以返回视图所对应的文档的指针。

(3) 打开HelloMFC.cpp,在InitInstance函数的最后两行是显示主窗口的语句: m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow();

这个主窗口就是整个应用程序的框架窗口。它也是构成文档模板的三要素之一的框架窗口。在HelloMFC程序一启动时,就已经创建了一个文档,因此,该文档对应的框架窗口也自动打开。

(4) 了解了应用程序框架后,打开你应用程序所在的目录,查看一下该目录下都有哪些文件。下面的一段文字介绍这些文件的含义。看看你已经了解了多少? ?[AppWizard自动生成的文件]

项目文件

helloMFC.DSW工作台文件

helloMFC.OPT工作台选项文件,存放关于工作台的所有选项(如工作台布局)

helloMFC.DSP项目文件。存放特定于项目的内容,包括项目中包含的文件、编译方式、连接选项等等。与.mak文件的作用相同

C++源文件和头文件:

helloMFC.cpp.helloMFC.h项目的主要源文件,该文件中创建类CHelloMFCApp的实例,并重载其成员函数InitInstance。对于可执行程序,CHelloMFC::InitInstance做以下事情:登记文档模板(作为文档和视图之间连接的机制)、创建主框架窗口、创建一个空文档(或如果在命令行指定了一个文档,则打开一个已有文档)。

helloMFCDoc.cpp,helloMFCDoc.h实现了文档类CHelloMFCDoc,该类从类CDocument派生而来,可以完成文档的存取、修改等操作。文档内容的显示通过与文档相联系的类CHelloMFCView的对象完成。

helloMFCView.cpp, helloMFCView.h实现了视图类CHelloMFCView,视图类用于显示和打印文档数据。CHelloMFCView可以从类CEditView, CFormView, CRecordView, CDaoRecordView, CTreeView, CListView, CRichEditView, CScrollView,或CView派生而来。这个项目中的CHelloMFCView从类CView派生而来。该类中实现了一些框架性函数,包括绘制视图函数、调试诊断语句,如果选择打印支持,则还实现关于打印、打印设臵、打印预览等命令处理。

MainFrm.cpp,MainFrm.h实现了从类CMainFrame派生而来的类CFrameWnd (SDI应用程序) 或CMDIFrameWnd (MDI应用程序),该类负责处理工具条和状态条的创建。

StdAfx.cpp,StdAfx.h预编译头文件,用于建立预编译头文件helloMFC.PCH和预编译类型文件STDAFX.OBJ

资源文件

helloMFC.rc, resource.h项目的资源文件及其头文件。资源文件中包含缺省的菜单定义和加速器、字符串表等。还有一个缺省的About对话框和一个icon。资源文件中还包含了标准的MFC资源AFXRES.RC。如果有工具条支持,则还有一个工具条位图文件

40

(RES\\TOOLBAR.BMP).

helloMFC.ico项目的图标文件,在应用程序变为最小或在对话框中可以出现图标。 helloMFC.ic2用于存放那些不是由Developer Studio编辑的资源。 helloMFCDoc.ico 项目中文档的图标文件。 RES\\Toolbar.bmp工具条位图

文本文件

readme.txt描述项目下由系统的AppWizard或 ControlWizard.自动产生的各个文件的含义。

5. 学习使用画笔和画刷

前面你已经了解了Windows 应用程序的基本结构,知道AppWizard为你生成的文件的作用。这个实验中,你将尝试在这个程序框架上加上自己的应用逻辑:在视图中画个矩形或是椭圆。

在实验一中,你已经尝试在窗口中输出一行文字“Hello, World!”,当时是在类CHelloMFCView的成员函数OnDraw()中加了一个语句:

pDC->TextOut(50,50,\

这里涉及到Windows程序如何输出信息。Windows程序使用“设备上下文(Device Context)”来向输出设备(显示器、打印机等)输出文字、图形信息。

?[设备上下文]

设备上下文,简称为DC,是由Windows程序保存的一个结构,该结构里存储着程序向设备显示输出时所需要的设备信息,包括图形对象以及它们相关的属性和输出的图形模式。DC是图形设备接口(GDI)的重要组成部分,在使用任何GDI输出函数之前,你必须建立一个设备上下文。使用设备上下文的最大好处是硬件无关性。因为所有的输出都通过DC进行,程序不需要关心DC对应的具体输出设备。

与DC关联的图形对象有画笔、画刷、位图、字体、调色板等。在需要用输出某种图形对象以前,需要先将它与一个设备上下文关联起来,然后通过设备上下文来输出。

在Visual C++中,总是通过MFC类来访问设备上下文。这些类封装了DC数据结构,并提供一些有用的功能来简化应用程序。CDC是所有设备上下文类的基类,在实验一中我们修改了函数CHelloMFCView::OnDraw(),该函数用于视图窗口在它的窗口区输出内容,其中就用到了CDC:

void CHelloMFCView::OnDraw(CDC* pDC) {

CHelloMFCDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

// TODO: add draw code for native data here pDC->TextOut(50,50,\}

在OnDraw函数调用前,MFC框架结构就建立好了OnDraw函数所用的设备上下文,并且将它作为参数传递给OnDraw函数,OnDraw函数然后就可以利用这个pDC进行输出。大多数需要设备上下文的函数都需要用DC作为函数参数。

41

?[画笔和画刷]

画笔和画刷是常用的两种GDI对象,画笔是WindowsGDI提供的用来绘制直线和图形的对象,它的作用就想我们通常使用的笔,可以用它绘制直线、正方形、矩形、圆等基本的图形。构造画笔时至少要指定三个属性:画笔的类型(画实线、虚线还是点划线等)、宽度和颜色。Windows中提供的画笔类是CPen,你可以构造这个类的对象来创建自己的画笔。

画刷是Windows程序中用来填充一个空间、窗体或其他与区域有关的GDI对象。它主要用来对一个区域着色。画刷具有颜色、图案、填充类型等各种属性。在构造画刷对象时,你至少需要指明画刷的颜色。Windows中提供的画刷类是CBrush,你可以构造这个类的对象来创建自己的画刷。

[实验要求]

下面是在CHelloMFCView::OnDraw()函数中使用画笔和画刷的例子,把这些代码加到你的程序中,运行它,看看运行结果是什么样的?

void CHelloMFCView::OnDraw(CDC* pDC) {

//将该对象选进设备上下文

CBrush *pOldBrush = pDC->SelectObject(&brBackGround);

//建立一个绿色的画刷对象

CBrush brBackGround(RGB(0,255,0)); //恢复原来的画笔对象 pDC->SelectObject(pOldPen);

//用选定画笔画四条直线,构成一个矩形 pDC->MoveTo(10,10); pDC->LineTo(50,10); pDC->LineTo(50,50); pDC->LineTo(10,50); pDC->LineTo(10,10); //将画笔对象选进设备上下文

CPen *pOldPen = pDC->SelectObject(&aPen);

//建立一个画笔对象,可以画红色的宽度为1个象素的实线 CPen aPen(PS_SOLID,1,RGB(255,0,0)); //用缺省画笔画十个椭圆 for(int i = 1; i < 20; i+=2)

pDC->Ellipse(50+i,50+i,100+i,100+i); CHelloMFCDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

42

}

//在规定范围内画一个填充椭圆 CRect rcEllipse(100,100,15,200); pDC->Ellipse(rcEllipse); //恢复原来的画刷对象

pDC->SelectObject(pOldBrush);

如果有时间和兴趣,你可以尝试修改画笔和画刷的属性,或者画一些其他形状。你可以通过查阅关于CDC、CBrush、CPen等类的详细说明来得到具体的帮助信息。查阅有关MFC类的帮助信息,可以在InfoViewer的标题目录中选:Developer Products|Visual C++|Microsoft Foundation Class Library|Class Library Reference,然后选择你想要了解的类。

43