C++程序设计教程实验指导书

更新时间:2023-11-13 08:51:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

面向对象程序设计

实验指导书

前 言

本书是本科课程《面向对象程序设计》的实验指示书,一般人员也可以使用它作为学习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

(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

(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

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

[实验步骤]

(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

本文来源:https://www.bwwdw.com/article/hcev.html

Top