C++实验01.C++程序设计上机指导-谭成予

更新时间:2024-06-01 20:54:01 阅读量: 综合文库 文档下载

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

武汉大学计算机学院《C++程序设计》课内实验要求

C++程序设计上机指导

谭成予 编著

目录

上机指导1 C++与C语言的基本语法差异性编写练习 ................................ 1 上机指导2 多文件编程练习.............................................................................. 12 上机指导3 类与对象 .......................................................................................... 14 上机指导4 运算符重载和函数重载 ................................................................. 17 上机指导5 泛型编程:模板与STL ................................................................. 18 上机指导6 windows编程之一,可视化编程:窗体、控件、事件、视图和文档 ........................................................................................................................... 20 上机指导7 windows编程之二,组件编程:DLL编写 .............................. 22 上机指导8 windows编程之三,windows SDK编程:windowsAPI ... 31 上机指导9 常用设计模式编程练习 ................................................................. 34

上机指导1 VC编译环境的使用和C++基本语法编写练习

目的和要求:

1. 开始练习Visual C++中编写、编译、调试和运行Windows应用程序以及可视化编程的

基本使用方法。

2. C++和C语言有区别的基本语法编程练习,主要包括C++中数据类型、引用、new和

delete、函数重载等。

3. 通过编程练习,初步掌握面向过程和面向对象的本质差别。 内容和步骤:

1. VC编译环境的安装

请Visual C++ (建议2005版本及以上)编译环境,按照03-C++程序设计课内上机参考资料\\01-Visual C++ 2005简介.ppt和03-C++程序设计课内上机参考资料\\02-vs 2005使用简介.pdf的介绍,完成以下操作。: (1) VC的安装,回答以下问题

安装过程中,设置开发语言为C++,操作方法: ; 安装之后,修改开发语言为C++,操作方法: ;

(2) 安装MSDN,安装好之后,尝试使用MSDN查找资料。 (3) 按照如下描述的步骤,尝试设置VC的选项。

a) 用户可通过“选项”(Options)对话框来设置各种选项,当从主菜单中选择“工具”

/“选项”时该对话框将显示出来,如图 1所示。

图 1 Visual C++2005选项对话框

b) 单击左边窗格中任意一项旁边的加号(+),将显示出子主题列表。如图 1显示的

是“项目和解决方案”下面的“常规”子主题的选项。右边窗格显示对应于左边窗格选定主题的设置选项。单击选项对话框右上方的Help按钮(符号?),将显示当前选项的解释。

用户平时关心的只有少数几个选项,但花点时间看看都有哪些选项可以设置将对用户很有帮助。用户可能希望选择某个路径作为创建新项目时的默认路径,这可以通过如图 1所示的“项目和解决方案”下面的“常规”子主题的选项来实现。

c) 通过在左边窗格中选择“项目和解决方案”下面的“VC++项目设置”主题,可以

设置应用于所有C++项目的选项。

d) 通过选择主菜单上的“项目”下面的“属性”菜单项,可以设定当前项目所特有的

选项,该菜单项是定制的,以反映当前项目的名称。

2. VC IDE(创建,编辑、编译等)的基本操作使用 (1) 创建新项目和源程序

1

a) 创建新项目 i. 在Visual C++中编写C程序的第一步是:创建新的项目,例如Win32控制台

项目。这个操作可以通过两种方法来实现:其一,单击主菜单中“文件”下面的“新建”/“项目”选项;其二,是单击标准工具栏中的“新建项目”工具按钮。无论是上面的哪种方式,都将弹出如图 2所示的“新建项目”对话框。

图 2 Visual C++2005新建项目对话框

ii.

iii.

在“新建项目”对话框中,请选择左边窗格中“项目类型”的“Visual C++”下面的“Win32”。然后,选择右边窗格中“模板”下方的“Win32控制台应用程序”。第三,可以在下方的窗格内输入新建项目及其解决方案的名称,例如输入项目名称“aa”;并确认新建项目的位置。最后,单击“确认”按钮,将显示如图 3所示的“Win32应用程序向导”对话框。

单击“Win32应用程序向导”对话框中的“下一步”按钮,将显示如图4所示的“Win32应用程序向导——应用程序设置”对话框。

图 3 Win32应用程序向导

iv.

在如图4所示的“Win32应用程序向导——应用程序设置”对话框中,用户可在“应用程序类型”中,选择“控制台应用程序”。在“附件”选项中可以选择“空项目”或者“预编译头”,这里将要编写的是纯C程序,因此推荐选择“空项目”。最后,点击“完成”按钮,将显示如图4所示的新建项目的编辑主界面。

2

图4 Win32应用程序向导——应用程序设置

b) 创建新的源程序文件 i. 图5所示的是刚刚新建的项目aa的编辑主界面,注意此时项目aa是一个空的

容器,其中还没有任何源程序文件。此时,需要做的是创建新的源程序文件,这可通过两种方式完成:第一种方法是,选择主菜单中“项目”下面的“添加新项”;第二种方法是,在图5中左边窗格的解决方案管理器中的“aa”|“源文件”上面右击,在弹出的菜单中选择“添加”|“新建项”。这时,将显示如图6所示的“添加新项”对话框。

图5 编辑项目的主界面

图6 添加新项对话框

3

ii.

在“添加新项”对话框中,在“类别”窗格中选择“代码”选项。在“模板”窗格中选择“C++文件”。在下方的窗格中输入新建的源文件名称,例如hello。最后,单击“添加”按钮,系统将显示如图7所示的项目及源程序编辑窗口。

图7 项目及其源程序编辑界面

在图7所示的项目及源程序编辑界面中右边窗格是源文件的编辑窗口,在此窗口中输入用户所需要编写的C程序。例如,现在输入下面的C程序代码:

例题1-1 整数溢出范例程序。

/*整型数据溢出范例。源文件:LT1-1.C*/ #include #include

int main(void) {

short int a,b;

a=32767; b=a+1;

printf(\,a,b);

system(\); return 0;

}/*main函数结束*/

iii.

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

用户可用快捷键Ctrl-S来保存编写的源文件,也可以通过“文件”或者“编辑’菜单中相应选项来实现保存、粘贴等编辑功能。 现在请回答以下的问题: 回答以下问题

你使用的VC环境中,有哪些类型的项目文件?

(2) 编辑已存在的项目及源程序

a) 如果需要修改或者重新编译之前已经编写好的项目和源程序,可以通过“文件”菜

单下面的“打开”|“项目/解决方案”,或者通过标准工具栏中的“打开文件”按钮。这时将出现如图8所示的“打开项目”对话框。

4

iv.

图8 打开项目对话框

b) 用户可以在图8所示的“打开项目”对话框中,选择之前保存项目的文件夹以及该

项目的文件名,然后单击“打开”按钮即可。Visual C++中的项目文件夹的名称将与之前创建时给出的项目名称相同,该文件夹还将容纳所有构成该项目定义的文件。如果不加修改,则解决方案具有与项目文件夹相同的名称,而且包含项目文件夹和定义解决方案内容的文件。

用户在解决方案文件夹中可以看到该文件夹包含如下3个文件: ? 扩展名为.sln的文件,记录着关于解决方案中项目的信息。 ? 扩展名为.suo的文件,记录着应用于解决方案的用户选项。

? 扩展名为.ncb的文件,记录着与解决方案的Intellisense有关的数据。Intellisense是

在编辑器窗口中输入代码时提供自动完成和提示功能的工具。

例如在“D:\\Program Files\\MultiGen-Paradigm\\aa”文件夹中可以看到与之前创建的解决方案aa有关的3个文件:aa.sln、aa.suo和ss.ncb。

用户在项目文件夹中查看,可以看到最初有6个文件,其中名称为ReadMe.txt的文件包含该项目所有文件的内容摘要。在项目文件夹中可能存在一个文件是ProjectName. vcproj.ComputerName.UserName.user,例如之前创建的aa项目文件夹中存在文件aa.vcproj. LENOVO-DB8C1480.user.user,这个文件的作用是存储为该项目设定的选项。其中“LENOVO-DB8C1480”是作者使用的计算机名。

c) 如果希望在源代码编辑窗口中显示行号,请从主菜单上选择“工具”|“选项”,在

“选项”对话框中左边窗格选择“文本编辑器”下面的“C/C++选项”|“常规”,在右边窗格中选择“行号”即可。

(3) 编译并构建解决方案

在Visual C++中编写程序被称为“构建解决方案”,可通过F7快捷键或者选择主菜单下的“生成”|“生成解决方案”。另外还可通过单击“生成”工具栏上的相应按钮。如果程序中有错误,则会在主界面下方的输出窗格中显示错误提示信息,例如图1-17所示的窗口中提示程序存在一个错误:错误信息是“error C2146: 语法错误: 缺少“;”(在标识符“system”的前面)”,用户可按照提示在

printf(\这一行的最后面加上分号(;),然后重新选择生成解决方案,直到系统提示成功为止。

在成功创建了解决方案之后,用户可以在相应的项目文件夹中看到一个新的子文件夹Debug,例如之前创建的项目aa中出现的新文件夹是:D:\\Program Files\\ MultiGen-Paradigm\\ aa\\ debug。在该文件夹中包含多个文件。这些文件的扩展名和用途分别是;

? .exe文件:这是程序的可执行文件,仅当编译和链接步骤都成功之后才能生成该文

件。

? .obj文件:编译器根据程序源文件生成这些包含机器代码的目标文件,它们与库文

5

件一起被链接器使用,最后生成.exe文件。

? .ilk文件:该文件在重新构建项目时被链接器使用,它使链接器能够将根据修改的

源代码生成的目标文件增量地链接到现有的.exe文件,从而避免每次修改程序时都重新链接所有文件。

? .pch文件:这是预编译头文件。使用预编译头文件,大块无需修改的代码(尤其是

那些C++库提供的代码)可以被处理一次并存储在.pch文件中。使用.pch文件能够大大减少构建程序所需的时间。

? .pdb文件:该文件包含在调试模式中执行程序时要使用的调试信息。在调试模式

中,可以动态检查程序执行过程中所生成的信息。 ? .idb文件:包含重新构建解决方案时要使用的信息。 (4) 运行解决方案

在成功编译过解决方案之后,可以按下Ctrl-F5组合键来执行程序。也可以通过主菜单下的“调试”|“开始执行”,或者单击标准工具栏中的“开始调试”按钮来执行程序。之后会看到如图9所示的程序执行窗口。

图9 程序运行窗口

3. Visual C++中调试工具的使用

故障是程序中的错误,而调试就是寻找并消除故障的过程。调试是编程过程中不可缺少的组成部分,它自始至终与编程相伴。在详细描述Visual C++的调试工具之前,先来看看程序中故障的产生过程。 (1) 调试器使用简介

调试器是一个程序,它控制着程序的执行过程:程序员可以一行一行地单步调试源程序,或者运行程序中特定的位置。在源代码中每个使得调试器停下来的位置,程序员可以在继续执行之前检查乃至修改变量的值。如果修改了源代码,必须重新编译并使程序从头开始执行。

首先需要将该实例项目的编译配置设定为Win32 Debug而不是Win32 Release。编译配置为程序的编译操作选择一组项目设定值,当我们选择主菜单上的“项目”|“属性”菜单项时可以看到这些设定。当前有效的编译配置显示在标准工具栏上一堆相邻的下拉列表中。程序员还可以通过主菜单上的“生成”|“管理配置器”菜单项来设置调试配置。标准工具栏如图10所示。

图10 标准工具栏

调试工具栏如图11所示,其中“逐语句”和“逐过程“按钮代表着调试器的一种特定工作模式:单步调试代码,即每次执行一条语句,二者的区别是“逐语句”会跟踪到函数内部,对被调用的函数中的每条语句都采用单步调试方式;“逐过程”不会跟踪到函数内部,

6

每次函数调用都把函数体内的所有代码当作一条语句一次执行。当然,无论哪种单步调试方式,执行一条语句之后,程序员都必须通过检查变量的值来判断这条语句是否出现错误。

对于大型程序而言,单步调试方式显然是不切实际的。相对于单步调试方式,断点模式

逐语句 逐过程 是指执行到源代码中特定的位置(被称为断点)处暂停,所以,断点模式更节省时间,更为实用。

图11 调试工具栏

(2) 设置断点

断点是程序中使调试器自动暂停执行的位置。程序员可以设置多个断点,这样程序在运行过程中就可以在程序员选定的感兴趣的位置停止。程序员可以在执行到各个断点处查看程序中变量的值,一旦变量值不符合预订值时,就可以帮助判断程序代码中哪里出现错误,并进行修改。

通常只需要检查可能有错误的特定区域,所以,通常应该在人为包含错误的位置设置断点,然后运行程序,使程序停止在第一个断点处。然后,如果愿意,程序员可以从该断点处开始单步执行。

把源程序的某行设置为断点或者取消断点的常用方法有两种: ? 第一方法是:选择主菜单上的“调试”|“切换断点”,就可以把当前行所在的代码

设置为断点。

? 第二种方法是:在该语句行号左边的灰色显示列单击即可。

某个代码行被设置为断点之后,在该代码行左边的灰色区域中将出现一个红色的圆圈符号,它被称为图示符,表明该行存在断点。我们可以通过右击图示符的方法来删除断点。图12所示的编辑器窗格中,已经为之前的示例程序设置了一个断点。

图12 断点设置

更为高级的设置断点的方法是: ? 使用Alt-F9组合键;

? 选择主菜单上“调试”中的“窗口”|“断点”; ? 选择调试工具栏上最右边的“断点按钮。

采用上述三种方法中的任意一种,在主界面的下方将或出现如图13所示的断点窗口。其中会显示所有已经设置的断点,而短线窗口中工具栏上的“新建“按钮可以帮助设置新的断点,而“列”按钮可以帮助设置要显示的断点信息,例如显示包含断点的源文件名或函数名。

7

图13 设置断点窗口

(3) 设置跟踪点

跟踪点是一种特殊的断点,它具有与之相关联的自定义动作。创建跟踪点的方法是:右击希望设置为跟踪点的代码行,然后从弹出菜单中选择“断点”|“插入跟踪点”,这时将出现如图14所示的“命中断点时”对话框。在该对话框中可以设置跟踪点动作是“打印消息”或者运行某个宏,例如,可以在设置打印消息为:

$FUNCTION, The value of b is { b }

那么,当抵达该跟踪点时,上述设置产生的输出将显示在应用程序窗口中的输出窗格中。

当然,也可以在图14所示的命中断点时对话框中设置程序执行到跟踪点上是暂停执行还是继续执行,红色类型符号表明某行那个源代码中存在不使执行停止的跟踪点。

图14 命中断点时对话框

(4) 启动调试模式

在如图15所示的调试菜单选项上,有在4种调试模式中启动应用程序的方式。

8

1. 函数模板编程练习:数组求最大值

(1) 定义一个函数模板,其功能是从一维数组中找出取值最大元素,并返回。

(2) 请编写测试用的主程序,要求分别测试由char、int类型、double组成的一位数组,

找出取值最大元素值。

2. 类模板的编程练习

(1) 以下是一个安全数组的类模板定义,可避免C语言中数组下标越界的错误,请仔细

阅读此段代码。

1. // 定义用于表述安全数组的类模板 2. template 3. class SafeArray 4. {

5. private:

6. T ary[size]; 7. public: 8. //构造函数 9. SafeArray ( ); 10. //重载运算符[ ] 11. T & operator[ ](int i); 12. //输出数组元素 13. void Print(); 14. };

(2) 请补充完整安全数组类的成员函数的实现。

(3) 请编写测试用的主程序,测试你编写的安全数组的正确性。 3. vector向量的使用(目的:理解STL中的向量),输入以下程序并运行、分析结果。 1. // #include \如果使用预编译的头文件就包含这个头文件 2. #include // STL向量的头文件。这里没有\。 3. #include // 包含cout对象的头文件。 4. using namespace std;

5. //保证在程序中可以使用std命名空间中的成员。

6. char* szHW = \ //这是一个字符数组,以”\\0”结束。 7. int main(int argc, char* argv[]) 8. {

9. vector vec; //声明一个字符向量vector (STL中的数组) 10. //为字符数组定义一个游标iterator。 11. vector ::iterator vi; //初始化字符向量,对整个字符串进行循环, 12. //用来把数据填放到字符向量中,直到遇到”\\0”时结束。 13. char* cptr = szHW; // 将一个指针指向“Hello World”字符串 14. while (*cptr != '\\0') 15. { vec.push_back(*cptr); 16. cptr++; } 17. // push_back函数将数据放在向量的尾部。 18. // 将向量中的字符一个个地显示在控制台 19. for (vi=vec.begin(); vi!=vec.end(); vi++)

20. // 这是STL循环的规范化的开始——通常是 \, 而不是 \21. // 因为\在一些容器中没有定义。 22. // begin()返回向量起始元素的游标(iterator), 23. //end()返回向量末尾元素的游标(iterator)。

24. { cout << *vi; } // 使用运算符 “*” 将数据从游标指针中提取出来。 25. cout << endl; // 换行 26. cin.ignore(); 27. return 0; 28. }

4. 容器和游标的使用,输入以下程序,并调试、分析结果

19

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35.

// #include \如果使用预编译的头文件就包含这个头文件

#pragma warning(disable:4786)

#include

#include

#include

using namespace std;

typedef map > INT2STRING;

int main() {

INT2STRING theMap;

INT2STRING::iterator theIterator;

string theString = \

int index;

theMap.insert(INT2STRING::value_type(0, \

theMap.insert(INT2STRING::value_type(1, \

theMap.insert(INT2STRING::value_type(2, \

theMap.insert(INT2STRING::value_type(3, \

theMap.insert(INT2STRING::value_type(4, \

theMap.insert(INT2STRING::value_type(5, \

theMap.insert(INT2STRING::value_type(6, \

theMap.insert(INT2STRING::value_type(7, \

theMap.insert(INT2STRING::value_type(8, \

theMap.insert(INT2STRING::value_type(9, \

for (;;) {

cout << \

cin >> theString;

if(theString == \ for(index = 0; index < theString.length(); index++)

{ theIterator = theMap.find(theString[index] - '0');

if(theIterator != theMap.end() ) cout << (*theIterator).second << \

else cout << \

} cout << endl; }

cin.ignore();

return 0; }

上机指导6 windows编程之一,可视化编程:MFC的编

程练习

目的和要求:

1. 熟练掌握AppWizard的使用方法。 2. 了解消息映射的概念和操作方法。 3. 学会查看和编辑代码。

4. 开始掌握窗体、控件、事件、视图和文档的使用方法。 内容和步骤:

1. 对话框程序编程练习:简易计算器程序

请用对话框模式编程实现两个数据的加减乘运算。

(1) 请参照“03-C++程序设计课内上机参考资料\\03-简易计算器程序-一个简单的对话框

应用程序-MFC使用入门-上机范例参考.ppt”里面介绍的步骤,完成简易计算器程序的可视化编程的以下步骤:

20

a) 创建简易计算器的对话框MFC应用程序;

b) 修改项目界面——添加输入操作数、运算符、计算按钮、结果显示等相关控件; c) 为上述控件关联变量; d) 为消息事件编程;

(2) 调试你编写的简易计算器程序;

(3) 范例程序代码参见“00-C++课内实验范例程序\\[C++范例-VC2005]范例002 加减运

算-简单的基于对话框的MFC范例程序.rar”(仅供参考)。

2. SDI编程练习之一:文本编辑器 (1) 请参照“03-C++程序设计课内上机参考资料\\04-SDI模式下的文本编辑程序+MFC入

门-创建MFC应用程序.ppt”里面的介绍,不写一行代码,采用SDI模式实现文本编辑器。

(2) 调试运行编辑好的文本编辑器程序。

(3) 请仔细阅读此文档后半部分的说明,为接下来的练习做以下准备:

a) 理解消息机制的基本概念;

b) 开始尝试使用“Windows消息监测工具Spy++”调试你的程序,。 3. 鼠标绘图程序练习

(1) 练习之一,MFC创建:创建鼠标绘图MFC程序

a) 请参考“03-C++程序设计课内上机参考资料\\05-鼠标绘图程序练习之一——SDI模式下的鼠标绘图程序-开始MFC编程之旅.ppt”介绍的步骤,创建SDI模式下的鼠标绘图程序框架。

b) 修改项目界面——修改标题和菜单。 c) 为程序编写源代码。 d) 编译Release版本。

(2) 练习之二,菜单和工具栏:为鼠标绘图程序添加菜单和工具栏

a) 请参考“03-C++程序设计课内上机参考资料\\06-鼠标绘图程序练习之二——处理菜单和工具栏-为鼠标绘图程序添加菜单和工具栏.ppt”介绍的步骤,为刚才创建的鼠标绘图程序添加菜单和工具栏。 b) 为菜单消息添加处理程序。 c) 添加工具栏按钮。

(3) 练习之三,消息事件编程:扩充鼠标绘图程序的功能,为鼠标消息编程

a) 请参考“03-C++程序设计课内上机参考资料\\07-鼠标绘图程序练习之三——在窗口中绘图—对鼠标消息编程-扩充Sketche功能.ppt”介绍的步骤,为鼠标绘图程序的鼠标消息进行绘图编程。

b) 请在上机前预习此讲义内容,以初步理解Windows绘图的基本知识。

(4) 练习之四,文档和视图:扩充鼠标绘图程序的功能,完成图形绘制和数据存储编程

a) 请参考“03-C++程序设计课内上机参考资料\\08-鼠标绘图程序练习之四——创建文档和改进视图-完成绘图编程和数据存储编程.ppt”里面介绍的步骤,完成图形绘制和数据存储的编程。

b) 请在上机前预习此讲义内容,以初步了解集合类等基本概念。

(5) 练习之五,对话框和控件:为鼠标绘图程序添加线宽等图形参数设置对话框,并添

加第五种元素——文本

a) 请参考“03-C++程序设计课内上机参考资料\\09-鼠标绘图程序练习之五——对话框和控件——线宽等图形参数设置,绘图上添加文本等相关编程.ppt”介绍的步骤,实现以下的编程:

21

i. 设置线宽 ii. 设置显示比例 iii. 添加第五种元素:文本 b) 调试你的程序

(6) 练习之六,文档编程:为鼠标绘图程序增加“文档保存和打开、打印及预览、文本

移动”等功能。

a) 请参考“03-C++程序设计课内上机参考资料\\10-鼠标绘图程序练习之六——存储和打印文档——文档保存和打开、打印及预览、文本移动.ppt”介绍的步骤,为鼠标绘图程序添加以下功能: i. 文档保存和打开 ii. 打印及预览 iii. 文本移动 b) 调试你的程序

(7) 以下是鼠标绘图程序的范例代码(仅供参考)

a) 00-C++课内实验范例程序\\[C++范例-VC2005]范例006 最简单的鼠标绘图程序-SDI范例程序.rar

b) 01-范例程序\\[C++范例-VC2005]范例007 鼠标涂鸦-SDI范例带文档保存功能.rar c) 00-C++课内实验范例程序\\[C++范例-VC2005]范例008 绘图程序——MDI版本.rar

4. 编程与程序调试与测试练习:

(1) 请参考“01-范例程序\\[C++范例-VC2005]范例008 绘图程序——MDI版本.rar

,将上面完成的鼠标绘图程序修改为MDI版本。

(2) 请修改文本编辑器项目,为其设置中英文双语菜单。

a) 在其“视图”菜单中添加一个菜单项“英文”;单击此菜单项将显示英文菜单 b) 创建新的菜单资源,该菜单为系统自动创建的IDR_MAINFRAME的英文版本,然

后在其“View”中增加“Chinese”菜单项,单击此菜单项将显示用来的中文菜单 c) 提示1:菜单项ID号相同表示需要处理的事件处理函数相同 d) 提示2:英文菜单中的内容可以从中文菜单中复制过去

e) 提示3:1和2步骤中添加的两个菜单项对应ID号应该相同 f) 提示4:菜单属于主窗口,即CMainFrame类 g) 提示5,需要修改的内容:

h) CMainFrame类定义与中英文两个菜单对应的变量成员(菜单类CMenu),以及目

前正在使用的菜单标志变量

i) 修改CMainFrame类的OnCreate函数,初始化并保存菜单资源

j) 为1和2步骤中添加的两个菜单项(中英文菜单切换)添加事件处理函数:响应不

同的菜单设置要求

k) 在工具栏中为1和2步骤中添加的两个菜单项增加相应工具按钮

(3) 运行上述修改的两个应用程序,并用Spy++程序监测其消息处理情况。

上机指导7 windows编程之二,组件编程:DLL编写

目的和要求:

1. 掌握Win32DLL的编写和调用方法。 2. 掌握MFC常规DLL的编写和调用方法。

22

3. 掌握MFC扩展DLL的编写和调用方法。 4. 掌握COM组件的编写和调用方法。 内容和步骤:

1. Win32DLL编写练习:读取系统时间

(1) 创建Wn32DLL项目,其功能是获取系统时间。创建步骤如下:

a) 新建Win32项目,项目名称“SysTime”; b) 在下图中,选择应用程序类别“DLL”;

选择附加选项“导出符号” ,将产生一个详细的项目,其中包含DllMain()、导出符号和实例代码,通常情况下,选择此选项。

c) 此时,你可以在项目中看到以下的文件: i. SysTime.cpp,这是主DLL 源文件 ii. StdAfx.h,、StdAfx.cpp,这些文件用于生成名为SysTime.pch 的预编译头(PCH)

文件和名为StdAfx.obj 的预编译类型文件。

iii. SysTime.h

d) 在SysTime.h中添加以下的声明(获取系统时间的函数声明):

SYSTIME_API void GetTime(SYSTEMTIME* lpSystemTime); e) 在 SysTime.cpp 源文件中添加GetTime函数的定义代码:

SYSTIME_API void GetTime(SYSTEMTIME* lpSystemTime) { GetLocalTime( lpSystemTime ); }

f) 编译和链接程序,你会在项目的debug文件夹中发现SysTime.dll和SysTime.lib这

两个文件。

(2) 创建使用Win32 DLL的测试项目,创建步骤如下:

a) 创建如下图所示的对话框应用程序SysTimeApplication;

23

b) 为“确定”按钮添加单击事件代码:

void CSysTimeApplicationDlg::OnBnClickedOk( ) { // TODO: 在此添加控件通知处理程序代码 SYSTEMTIME st; GetTime(&st); CString StrMessage; StrMessage.Format (\当前时间是:%d 年%d 月%d 日

%d : %d : %d \ st.wYear, st.wMonth, st.wHour, st.wMinute, st.wSecond); MessageBox( StrMessage ); }

c) 将(1)中创建的三个文件:SysTime.h、SysTime.dll、SysTime.lib复制到本项目的

文件夹中,并将DLL文件在DEBUG文件夹中复制一份,这样直接启动应用程序时,也不会导致找不到动态链接库。 d) 运行并测试你的测试项目程序。

e) 现在请回答问题:动态链接库的隐式链接和显式链接的区别是什么? 2. MFC常规DLL编程练习:闰年判断程序

(1) 创建包含闰年判断函数的MFC常规DLL程序,创建步骤如下:

a) 新建MFC DLL项目,项目名称“RunNian”;

b) 在下图中,选择DLL类型为“使用共享MFC DLL的规则DLL”;

24

c) 现在你可以在项目中看到以下的文件: i. 预编译头 StdAfx.h, StdAfx.cpp ii. 资源文件Resource.h、Resource.cpp iii. 模板定义文件:RunNian.def,主要用来声明DLL的导出函数 iv. 主程序文件RunNian.h、RunNian.cpp d) 在文件RunNian.cpp的最后面添加以下代码:

BOOL IsRunNian(DWORD dwYear = 0)

{ AFX_MANAGE_STATE(AfxGetStaticModuleState());

//模式转化,确保切换到MFC模块mfc42.dll的状态下 BOOL Result = FALSE;

if(((dwYear%4==0)&&(dwYear!=0))||(dwYear@0==0)) { Result = TRUE; } return Result; }

e) 修改文件RunNian.def的代码,如下所示:

; RunNian.def : 声明DLL 的模块参数。 LIBRARY \

EXPORTS

; 此处可以是显式导出

IsRunNian @1

f) 编译并链接,此时将会同时产生RunNian.dll和RunNian.lib文件,后续程序可以使

用它们。

g) 现在请回答以下的问题: i. RunNian.def,模板定义文件的作用是什么? ii. RunNian.def中的“IsRunNian @1”,表示IsRunNian函数有哪几种调用方法?

(2) 创建使用闰年判断函数的MFC常规DLL程序的测试项目

a) 创建Win32控制台应用程序:RunNianCheck b) 修改RunNianCheck.cpp代码如下所示:

// RunNianCheck.cpp : 定义控制台应用程序的入口点。 #include \#include #include #include

int main(int argc, char* argv[]) { DWORD dwYear; //输入待判断的年份 printf(\输入年份!\\n\ scanf_s(\ HINSTANCE hInstance; //存储动态链接库的指针 typedef BOOL (FUN)(DWORD); //定义函数原型 FUN *pFun;

25

//给出具体路径,通过LoadLibrary函数加载 // 可以给出相对路径或者绝对路径 assert(hInstance = LoadLibrary(\

//通过GetProcAddress函数找到DLL中IsRunNian函数的指针 assert( pFun = (FUN*) GetProcAddress(hInstance, \ //通过IsRunNian函数的指针调用IsRunNian判断 if ( (*pFun)(dwYear) ) { printf(\是闰年!\ } else { printf(\不是闰年!\ } //使用FreeLibrary函数释放调用的DLL资源 assert(FreeLibrary(hInstance)); getchar(); getchar(); return 0; }

c) 运行此测试程序。

3. 创建COM组件编程练习:英文单词首字母变为大写 (1) 利用ATL创建COM组件“MyCom”,创建步骤如下:

a) 单击“文件”中的“新建”|“项目”,选择ATL项目,输入名称Mycom; b) 在下图中选择“服务器类型”为“动态连接库(DLL)” ,单击“完成”按钮。

c) 添加类: i. 在Mycom项目的“类视图”中右击Mycom,单击“添加类”。 ii. 在下图中选择“ATL简单对象”,单击“添加”按钮。

26

iii.

在下图中,输入“简称”(short name)为Str,确认类名为CStr,输入“CoClass”为“StrCom”,确认Prog ID为“Mycom.StrCom”,单击“下一步”。

iv.

在下图中选择相应的线程模型等,这里选择默认的选项。

d) 在COM组件中添加方法: i. 在“类视图”中,右击IStr接口,选择“添加方法”。

27

ii. iii.

输入方法名为“FirstToUpper” 添加三个参数分别为: [in] BSTR InData; [out] BSTR* OutData; [out,reval]long* result;

该方法的实现如下: // str.cpp

STDMETHODIMP CStr::FirstToUpper(BSTR InData, BSTR* OutData, long* result) { // TODO: 在此添加实现代码 CComBSTR strSource=InData;

bool bMayUpper=true; //是否可以大写的标志 for(int i=0;i='a'&&strSource[i]<='z'&& bMayUpper ) { strSource[i]=strSource[i]-32; bMayUpper=false; } if(strSource[i]==' ') bMayUpper=true; } *OutData = SysAllocString(strSource); return *result; }

e) 编译IDL并注册COM组件: i. 设置项目属性为“Release”。 ii. 编译并链接ATL项目Mycom,你会在项目的Release目录中发现两个文件:

Mycom.tlb,接口定义文件对应的二进制文件 Mycom.dll

iii. 注册MyCom.dll:

要想让用户能够“透明”地使用创建的组件,首先必须将该组件注册。VC编译

28

iv.

if(type == DRIVE_CDROM) //如果是光驱的话 cout<<\驱动器\是光驱\\n\ } cin.ignore(); return 0; }

(3) 编译运行你的程序。 3. 编程练习:

(1) 请使用WindowsAPI编程,读取主机(本机)的主机名和IP地址。

提示:用于获取本机的API函数是 #include

int PASCAL FAR gethostname(char FAR *name, int namelen);

而gethostbyname()函数返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针

truct hostent FAR* PASCAL FAR gethostbyname(const char FAR * name);

上机指导9 常用设计模式编程练习

目的和要求:

1. 掌握单例模式的基本用法。

2. 掌握简单工厂模式或者工厂模式的基本用法。 3. 初步掌握常用设计模式的基本概念和优缺点。 内容和步骤:

1. 单例模式编程练习:模拟实现简易打印池程序

(1) 要求:打印池(Print Spooler)是一个用于管理打印任务的应用程序,通过打印池

用户可以删除、中止或者改变打印任务的优先级,在一个系统中只允许运行一个打印池对象,如果重复创建打印池则抛出异常。请用单例模式实现一个简易的打印池程序。

(2) 单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。基

本的单例模式C++实现代码如下所示(仅供参考): class CSingleton {

//其他成员 public:

static CSingleton* GetInstance() {

if ( m_pInstance == NULL ) //判断是否第一次调用 m_pInstance = new CSingleton(); return m_pInstance; }

private:

CSingleton(){};

34

static CSingleton * m_pInstance; };

该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。 (3) 现在请你参考上面所示的基本的单例模式,编程实现打印池类的定义和实现。该类

的类图如下所示:

PrintSpoolerSingleton - instance : PrintSpoolerSingleton - PrintSpoolerSingleton () + getInstance () : PrintSpoolerSingleton + manageJobs () : void ... instance PrintSpoolerException + PrintSpoolerException (String message) ... (4) 请编写测试用的主程序,测试是否能够实例化多个打印池对象。 (5) 现在请回答以下问题:

a) 单例模式类中,构造函数为什么需要定义为私有属性?例如,PrintSpoolerSingleton

类的构造函数就是私有的。

b) 仔细阅读单例模式CSingleton,大家可能存在一个疑问:m_pInstance指向的空间

什么时候释放呢?更严重的问题是,这个实例的析构操作什么时候执行? 要解决这个问题,在C++中通常的做法如下: class CSingleton: { // 其它成员 public:

static CSingleton * GetInstance() private:

CSingleton(){};

static CSingleton * m_pInstance;

class CGarbo // 它的唯一工作就是在析构函数中删除CSingleton的实例 {

public:

~CGarbo()

35

{

if (CSingleton::m_pInstance)

delete CSingleton::m_pInstance; } };

static CGarbo Garbo;

// 定义一个静态成员,在程序结束时,系统会调用它的析构函数 };

现在要你回答的问题是:为什么采用定义静态成员的方式释放静态成员空间,而不是在析构函数中进行?

2. 简单工厂模式编程练习:计算器

(1) 要求:请把上机指导1中你实现的计算器(两个操作数的加减乘除运算),增加简单

工厂模式类,以降低你的计算器程序在扩展新的运算符时尽可能降低修改代价。

(2) 在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定

义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。 下面是简单工厂模式的参照代码:

//算法的父类,抽象出返回结果的接口 class Operation {

public:

virtual int GetResult() =0; public:

double m_Num1; double m_Num2; };

//工厂类,用于生产相应的算法子类 class OperationFactry {

public:

OperationFactry(void); ~OperationFactry(void); public:

static Operation* CreateOperate(int n ) {

switch(n) {

case 1:

return new OperationAdd; break; } } };

//算法子类,由工厂类创建,重写父类中的虚函数 class OperationAdd

36

{

public:

int GetResult(); };

(3) 现在请上机重新实现计算器程序,按照以下的类图定义运算基类、运算子类(加法

类、减法类、乘法类、除法类)、简单工厂类:

(4) 请编写测试用的主程序,并运行你的程序。

(5) 下面在上面调试通过的程序基础上,增加第五个运算子类(乘方),并重新调试运行

程序?

现在请思考问题:用或者不用简单工厂模式,增加新的运算子类时,两种方式修改代价有什么不同吗?

37

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

Top