搜档网
当前位置:搜档网 › 第三章 Win32应用程序设计

第三章 Win32应用程序设计

第三章 Win32应用程序设计
第三章 Win32应用程序设计

第三章Win32应用程序设计

在过去,进行Windows程序设计是一件痛苦异常的事情,原因是那时候还没有现在的这些设计精美的应用程序开发工具。在今天,一个对Windows程序运行的内部机制几乎一无所知的初入门者,只需要通过不到一天的学习,也可以使用如Visual Basic之类的程序开发工具创建出功能完整的Windows应用程序。这在几年前还是一件不可思议的事,因为即使是一个熟练掌握C语言的程序员,在当时差不多需要半年的学习才可以较全面的掌握Windows 的编程技术,而且,与在DOS环境下编程相比,急剧膨胀的程度代码大大增加了程序调试的困难,从而使得编写一个出色的Windows应用程序要比编写一个出色的DOS需要考虑多得多的东西。

在Microsoft的另一种易学易用的编程工具Visual Basic中,从某种角度说,Windows程序不是编出来的,而是由程序员画出来的。但是要知道,一个出色的Windows的应用程序并不仅在于在屏幕上绘出程序的各个窗口和在窗口中恰当的安排每一个控件。对于具有一定基础的程序员而言,更重要的内容在于知道Windows和Windows应用程序的运行机制,以及它们之间以何种方式来进行通信,然而,明确自己在编写Windows时所需做的工作是哪一些。换句话说,我们需要透过Windows漂亮的图形用户界面,认清在底层所发生的每一件事情。然而,这并非是一件容易的事。虽然,使用MFC和AppWizard,我们仍可能只需要回答几个简单的问题和添加少数的几条代码就能够生成功能完整的Windows应用程序。但是记住,没有一个成功的商业软件是使用这样的方式生成的。同时,也只有深入的理解了MFC应用程序框架的运行机制,才可能用好和用活这一工具,才能达到熟悉掌握VisualC++的境界。

尽管说MFC应用程序框架提供的是面向对象的Windows编程接口,这和传统的使用C语言和SDK来进行的Windows应用程序设计有着很大的不同,但是从底层来说,其中的大部分功能仍是通过调用最基本的Win32 API 来实现的。其中最重要的一点是,Windows应用程序的运行机制仍然没有改变,它们仍然是通过消息来和操作系统,进而和用户进行交互的事件驱动的应用程序。MFC对这一切进行了比较彻底的封装,它们隐藏在你所看不见的背面。即使你对这一切一无所知,你仍可以在Visual C++中使用MFC来进行程序设计。但是,经验表明,理解这一切的最好的方式是回过头去,看一看这些内容在SDK编写的应用程序是如何实现的,然后,再看一看在MFC中是如何把它们一层一层的与程序员隔离开的。

因此,在本章中介绍相对已“过时”的Win32 SDK编程,并非是说以后也使用SDK来编写应用程序,而在于让你通过它们更深入的从MFC的内部了解MFC,并且,对于某些术语和概念的说明和澄清,也有助于你以后理解很多的东西。如果你一开始对这些东西不感兴趣,那么,你可以先暂时跳过此章,继续阅读本书的其它部分。当你对于MFC中的某些问题感到不解,或者想知道MFC究竟是如何工作的时候,再回过头来补充这些知识,也是完全可以的。

本章包括以下的内容:

W indows应用程序的消息处理

W in32 API和SDK

W inMain函数

窗口和窗口过程

32位编程的特点

第一节事件驱动的应用程序

类似的话已在很多书籍中说过了无数遍,以至于每一个正在或试图进行Windows编程的人都耳熟能详:Windows 应用程序是事件驱动(或称作消息驱动)的应用程序。Windows是一个多任务的操作系统,也就是说,在同一时刻,在Windows中有着多个应用程序的实例正在运行,比如说这时我正在打开字处理软件Word来编写这本书的书稿,同时,还打开了Visual C++的集成开发环境Microsoft Developer Studio来调试书中的示例程序,而且,后台还在放着歌曲。在这样的一个操作系统中,不可能像过去的DOS那样,由一个应用程序来享用所有的系统资源,这些资源是由Windows统一管理的。那么,特定的应用程序如何获得用户输入的信息呢?事实上,Windows时刻监视着用户的一举一动,并分析用户的动作与哪一个应用程序相关,然后,将用户的动作以消息的形式发送给该应用程序,应用程序时刻等待着消息的到来,一但发现它的消息队列中有未处理的消息,就获取并分析该消息,最后,应用程序根据消息所包含的内容采取适当的动作来响应用户所作的操作。举一个例子来说明上面的问题,假设我们编了一个程序,该程序有一个File菜单,那么,在运行该应用程序的时候,如果用户单击了File菜单,这个动作将被Windows (而不是应用程序本身!)所捕获,Windows经过分析得知这个动作应该由上面所说的那个应用程序去处理,既然是这样,Windows就发送了个叫做WM_COMMAND的消息给应用程序,该消息所包含的信息告诉应用程序:“用户单击了File菜单”,应用程序得知这一消息之后,采取相应的动作来响应它,这个过程称为消息处理。Windows为

每一个应用程序(确切地说是每一个线程)维护了相应的消息队列,应用程序的任务就是不停的从它的消息队列中获取消息,分析消息和处理消息,直到一条接到叫做WM_QUIT消息为止,这个过程通常是由一种叫做消息循环的程序结构来实现的。

Windows所能向应用程序发送的消息多达数百种,但是,对于一般的应用程序来说,只是其中的一部分有意义,举一个例子,如果你的应用程序只使用鼠标,那么如WM_KEYUP、WM_KEYDOWN和WM_CHAR等消息就没有任何意义,也就是说,应用程序中事实上不需要处理这些事件,对于这些事件,只需要交给Windows作默认的处理即可。因此,在应用程序中,我们需要处理的事件只是所有事件中的一小部分。

图3.1给出了一般Windows应用程序的执行流程。

图3. 1 Windows应用程序的基本流程

因此,从某种角度上来看,Windows应用程序是由一系列的消息处理代码来实现的。这和传统的过程式编程方法很不一样,编程者只能够预测用户所利用应用程序用户界面对象所进行的操作以及为这些操作编写处理代码,却不可以这些操作在什么时候发生或者是以什么顺序来发生,也就是说,我们不可能知道什么消息会在什么时候以什么顺序来临。

Windows程序在处理消息时使用了一种叫做回调函数(callbackfunction)的特殊函数。回调函数由应用程序定义,但是,在应用程序中并没有调用回调函数的代码,回调函数是供操作系统或者其子系统调用的,这种调用通常发生在某一事件发生,或者在窗口或字体被枚举时。典型的回调函数有窗口过程、对话框过程和钩子函数。其中的窗口过程和对话框过程将在本章后面的内容中讲述。

第二节Win32 API和SDK

说到Windows编程,就不能不谈到Windows API (WindowsApplication Programming Interface,Windows应用程序编程接口),它是所有Windows应用程序的根本之所在。简单的说,API就是一系列的例程,应用程序通过调用这些例程来请求操作系统完成一些低级服务。在Windows这样的图形用户界面中,应用程序的窗口、图标、菜单和对话框等就是由API来管理和维护的。

Windows API具有两种基本类型:Win16 API和Win32 API。两者在很多方面非常相像,但是Win32 API除了几乎包括了Win16 API中的所有内容以外,还包括很多的其它内容。Windows API依靠三个主要的核心组件提供Windows 的大部分函数,在Win16和Win32中,它们具有不同的名称,如表3.1所示。

表3. 1 Win16和Win32的核心组件

虽然Win16 API组件带有.EXE的扩展名,但是它们事实都是动态链接库(.DLL),不能单独运行。其它一些非核心的Windows API由其它组件所提供的DLL来实现,这些组件包括通用对话框、打印、文件压缩、版本控制以及多媒体支持等。

Windows SDK (Windows Software Development Kit,Windows软件开发工具包)和Windows API紧密联系,它是一套帮助C语言程序员创建Windows应用程序的工具,在Windows SDK中包括了以下几个组成部分:

大量的在线帮助,这些帮助描述了Windows编程所可能用到的函数、消息、结构、宏及其它资源

各种编程工具,如对话框编辑器及图象编辑器等

W indows库及头文件

使用C语言编写的示例程序

该工具包的最新版本就是我们正在使用的Win32 SDK,在安装了Visual C++的同时,Win32 SDK也安装到你的计算机上了。尽管MFC提供了对Win32 API的比较完整的封装,但是,在某些情况下,我们更倾向于直接调用Win32 API,因为这有时候可以获得更高的效率,并且有着更大的自由度。而且,使用MFC编写的新风格的Windows应用程序的工作方式基本上与使用SDK编写的同一程序一样,它们往往有着很多的共同之处,只是使用MFC更加的方便,因为它隐藏了大量的复杂性。

前面提到过,面向对象的编程方式是当前最流行的程序设计方法,但是,Win32 API本身却是基于C语言的过程式编程的,SDK和MFC的最主要的不同之处也就是以C与C++之间的差别,使用MFC进行Windows应用程序设计需要面向对象的编程思想和方法,好在我们已经在前面这此进行了大量的铺垫。

第三节使用SDK编写Windows应用程序

传统的DOS程序以main函数作为进入程序的初始入口点,在Windows应用程序中,main函数被WinMain函数取而代之,WinMain函数的原型如下:

int WINAPI WinMain (HINSTANCE hInstance, // 当前实例句柄

HINSTANCE hPrevInstance, // 前一实例句柄

LPSTR lpCmdLine, // 指向命令行参数的指针

int nCmdShow) // 窗口的显示状态

这里出现了一个新的名词“句柄”(handle),所谓的句柄是一个标识对象的变量,或者是一个对操作系统资源的间接引用。

在上面的函数原型中,我们看到了一些“奇怪”的数据类型,如前面的HINSTANCE和LPSTR等,事实上,很多这样的数据类型只是一些基本数据类型的别名,表3.2列出了一些在Windows编程中常用的基本数据类型的别名,表3.3列出了常用的预定义句柄,它们的类型均为void *,即一个32位指针。

注1:

注2: 事实上,WNDPROC被定义为LRESULT (CALLBACK*)(HWND, UINT,WPARAM, LPARAM),这个定义最终被编译器解释为long (__stdcall *)(void*,unsigned int,unsigned int,long)。

表3. 3 Windows

查看Win32 SDK

定义,这些定义往往使用了#define和typedef等关键字。

这里解释什么是应用程序的一个实例(instance)。最简单的理解可以用下面的例子来说明:比如说已经在Windows 中打开了一个“写字板”(可以在“开始”菜单中的“程序|附件”下面找到它的快捷方式),现在你需要从另一篇文章里复制一部分内容到你正在写的这篇文章中,那么,你可以再打开一个“写字板”(注意写字板不是一个多文档应用程序,不能像在Word中那样打开多个不同的文件),然后从该写字板中复制文件的内容到在前一个写字板内打开的文章中。这里,我们多次运行了同一个应用程序,在这个例子中,我们将所打开的两个写字板叫做该应用程序的两个实例。对于实例的更精确(当然也要比上面的例子要更难懂得多)的定义,在Win32 SDK中是这样给出的:实例就是类中一特定对象类型的一个实例化对象(instantiation),如一个特定的进程或线程,在多任务操作系统中,一

个实例指所加载的应用程序或动态链接库的一份拷贝。刚开始时我们也许看不懂这一定义,不过没有关系,慢慢的就理解了。

注意:

尽管在前面给出的WinMain函数的原型中包括了一个名为hPrevInstance的HINSTANCE类型的参数,按照其字面上的意义,它所传递的是应用程序的前一个实例的句柄,但是,在Win32平台下,该参数的值总是为NULL,而不管是否有当前应用程序的实例在运行。在过去的Windows 3.x环境下编程,我们常常使用下面的代码来检查应用程序是否已有一个实例在运行:

i f (!hPrevInstance)

{

// 在此添加没有应用程序实例在运行时的所需执行的代码。

// 对于大多数应用程序,我们常在这里注册窗口类。

}

然而,在Win32操作系统——Windows 95、Windows NT以及其后续版本中,上面的if条件体中的代码总会被执行,因为hPrevInstance总是为NULL,因此!hPrevInstance恒为真。

之所以这样,是因为在Win32环境下,每一个应用程序的实例都有自已独立的地址空间,因此,它们之间互相独立,互不干涉。但是,对于一些应用程序,只需要而且只应该有一个实例在运行。什么情况下会是这样呢?假设我们编写了一个应用程序,在默认情况下,该应用程序将在后台运行,通过按下程序所定义的某一个热键,应用程序将被激活。对于这样的应用程序,在同一时该只应该有一个实例在运行。另外,像Windows NT下在任务管理器,在同一时刻也只可以有一个实例在运行。

使用下面的技巧可以保证在同一时刻只有应用程序的一个实例:

#include "windows.h"

#define VK_X 0x58

i nt WINAPI WinMain (HINSTANCE hInstance,

H INSTANCE hPrevInstance,

L PSTR lpCmdLine,

i nt nCmdShow)

{

i f (!CreateMutex(NULL,TRUE,"No Previous Ins tance!"))

{

M essageBox(NULL,"创建Mutex失败!","NoPrev",MB_OK|MB_SYSTEMMODAL);

r eturn FALSE;

}

i f (GetLastError()==ERROR_ALREADY_EXISTS)

{

M essageBox(NULL,"已有NoPrev的一个实例在运行, 当前实例将被终止!",

"NoPrev",MB_OK|MB_SYSTEMMODAL);

r eturn FALSE;

}

i f(!Re gisterHotKey(NULL,0x0001,MOD_CONTROL|MOD_SHIFT,VK_X))

{

M essageBox(NULL,"注册热键Ctrl+Shift+X失败!",

"NoPrev",MB_OK|MB_SYSTEMMODAL);

r eturn FALSE;

}

M essageBox(NULL,"NoPrev已启动!\n\n按下热键Ctrl+Shift+X将终止NoPrev.",

"NoPrev",MB_OK|MB_SYSTEMMODAL);

M SG m sg;

w hile (GetMessage(&msg,NULL,0,0))

{

s witch (msg.message)

{

c ase WM_HOTKEY:

i f (int(msg.wParam)==0x0001)

i f (MessageBox(NULL,"终止NoPrev?",

"NoPrev",MB_YESNO|MB_SYSTEMMODAL)==IDYES)

r eturn TRUE;

}

}

r eturn TRUE;

}

上面的代码是一个功能完整的Windows应用程序,其中用到了一些到目前为止我们还未讲述到的内容。程序定义了热键Ctrl+Shift+X,当按下该热键时将终止该程序。由于程序中没有包括任何窗口,因此这是唯一的一种正常终止应用程序的方法。当程序NoPrev正在后台运行时,如果用户按下了组合键Ctrl+Shift+X,Windows将向程序主线程的消息队列中发送一条称为WM_HOTKEY的消息,当程序收到这条消息时,即弹出了消息框询问是否终止NoPrev。上面的说明将有助于你理解以上代码,但是我们目前对止并不做要求。这里,只需要注意下面的代码: i f (!CreateMutex(NULL,TRUE,"No Previous Instance!"))

{

M essageBox(NULL,"创建Mutex失败!","NoPrev",MB_OK|MB_SYSTEMMODAL);

r eturn FALSE;

}

i f (GetLastError()==ERROR_ALREADY_EXISTS)

{

M essageBox(NULL,"已有NoPrev的一个实例在运行, 当前实例将被终止!",

"NoPrev",MB_O K|MB_SYSTEMMODAL);

r eturn FALSE;

}

在上面的代码中,我们先调用CreateMutex创建一个名为“NoPrevious Instance”的命名互斥对象(named mutexobject),如果该对象名已存在(注意这时函数CreateMutex仍返回真值TRUE),则随后调用的GetLastError函数将返回ERROR_ALREADY_EXISTS,由此得知已有一个应用程序的实例正在运行。从而弹出消息框提醒用户,然后终止应用程序的当前实例。

在上面的WinMain函数原型中的另一个奇怪的标识符为WINAPI,这是一个在windef.h头文件中定义的宏,在当前版本Win32 SDK中,WINAPI被定义为FAR PASCAL,因此,使用FAR PASCAL同使用WINAPI具有同样的效果,但是,我们强烈建议你使用WINAPI来代替以前常用的FAR PASCAL,因为Microsoft不保证FAR PASCAL能够在将来的Windows版本中正常工作。在目前情况下,和FAR PASCAL等价的标识符还有CALLBACK (用在如窗口过程或对话框过程之类的回调函数前)和APIENTRY等。它们对编译器而言都是一回事,最终将被解释为__stdcall。在Windows环境下编程,会遇到很多这样的情况,注意不要混淆它们。

一般情况下,我们应该在WinMain函数中完成下面的操作:

1. 注册窗口类;

2. 创建应用程序主窗口;

3. 进入应用程序消息循环。

接下来我们将依次讨论这些内容。

在Windows应用程序中,每一个窗口都必须从属于一个窗口类,窗口类定义了窗口所具有的属性,如它的样式、图标、鼠标指针、菜单名称及窗口过程名等。在注册窗口类前,我们先创建一个类型为WNDCLASS的结构,然后在该结构对象中填入窗口类的信息,最后将它传递给函数RegisterClass,整个过程如下面的代码所示:

WNDCLASS wc;

// 填充窗口类信息

wc.style=CS_HREDRAW|CS_VREDRAW;

wc.lpfnWndProc=WndProc;

wc.cbClsExtra=0;

wc.cbWndExtra=0;

wc.hInstance=hInstance;

wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);

wc.hCursor=LoadCursor(NULL,IDC_ARROW);

wc.hbrBackground=GetStockObject(WHITE_BRUSH);

wc.lpszMenuName=NULL;

wc.lpszClassName="SdkDemo1";

// 注册窗口类

RegisterClass(&wc);

下面解释一下结构WNDCLASS中各成员的含义:

style: 指定窗口样式。该样式可以为一系列屏蔽位的按位或,在前面的例子中,CS_HREDRAW表示当窗口用户区宽度改变时重绘整个窗口,而CS_VREDRAW则表则表示当窗口用户区高度改变时重绘整个窗口。对于其它的窗口样式,请参阅SDK中关于WNDCLASS的联机文档。(顺便说一句,请注意该成员的大小写,它是小写的style,而不是Style。)

lpfnWndProc: 指向窗口过程的指针。关于窗口过程我们将以后面的内容中讲述。在前面的例子中,我们使用名为WndProc的窗口过程。

cbClsExtra: 指定在窗口类结构之后分配的附加字节数。操作系统将这些字节初始化为0。

cbWndExtra: 指定在窗口实例之后分配的附加字节数。操作系统将这些字节初始化为0。如果应用程序使用WNDCLASS结构注册一个使用资源文件中的CLASS指令创建的对话框,那么cbWndExtra必须被设置为DLGWINDOWEXTRA。

hInstance : 标识该类的窗口过程所属的实例。hIcon : 标识类图标。该成员必须为一个图标资源的句柄。如果该成员为NULL,则应用程序必须在用户最小化应用程序窗口时绘制图标。

hCursor : 标识类鼠标指针。该成员必须为一个光标资源的句柄,如果该成员为NULL,当鼠标移进应用程序窗口时应用程序必须显式指定指针形状。

hbrBackground: 标识类背景刷子。该成员可以为一个用来绘制背景的画刷句柄,或者为标准系统颜色值之一。lpszMenuName: 指向一個以NULL结尾的字符串的指针,该字符串指定了类菜单的资源名称。如果在资源名称为的菜单为一个整数所标识,则可以使用MAKEINTRESOURCE宏将其转换为一个字符串;如果该成员为NULL,则属于该类的窗口无默认菜单。

lpszClassName: 指向一个以NULL结尾的字符串或为一个原子。如果该参数为一个原子,那么它必须是一个使用GlobalAddAtom函数创建的全局原子;如果为一个字符串,该字符器将成员窗口类名。

注意:这里多次提到窗口类这一名词,但是它和前面常说的C++类没有任何联系。窗口类只表示了窗口的类型,它完全不是面向对象意义上的类,因为它不支持面向对象技术中的继承及多态等。

在使用RegisterClass注册窗口类成功之后,即可以使用该窗口类创建并显示应用程序的窗口。这个过程如下面的代码所示:

// 创建应用程序主窗口

hWnd=CreateWindow ("SdkDemo1", // 窗口类名

"第一个Win32 SDK应用程序", // 窗口标题

WS_OVERLAPPEDWINDOW, // 窗口样式

CW_USEDEFAULT, // 初始化x 坐标

CW_USEDEFAULT, // 初始化y 坐标

CW_USEDEFAULT, // 初始化窗口宽度

CW_USEDEFAULT, // 初始化窗口高度

NULL, // 父窗口句柄

NULL, // 窗口菜单句柄

hInstance, // 程序实例句柄

NULL); // 创建参数

// 显示窗口

ShowWindow(hWnd,SW_SHOW);

// 更新主窗口客户区

UpdateWindow(hWnd);

由于上述代码均加上了详尽的注释,这里仅作一些简单的说明和强调。CreateWindow函数的原型是这样的:HWND CreateWindow(LPCTSTR lpClassName, // 指向已注册的类名

LPCTSTR lpWindowName, // 指向窗口名称

DWORD dwStyle, // 窗口样式

int x, // 窗口的水平位置

int y, // 窗口的垂直位置

int nWidth, // 窗口宽度

int nHeight, // 窗口高度

HWND hWndParent, // 父窗口或所有者窗口句柄

HMENU hMenu, // 菜单句柄或子窗口标识符

HANDLE hInstance, // 应用程序实例句柄

LPVOID lpParam, // 指向窗口创建数据的指针

);

在前面的示例中,我们对x、y、nWidth和nHeight参数都传递了同一个值CW_USEDEFAULT,表示使用系统默认的窗口位置和大小,该常量仅对于重叠式窗口(即在dwStype样式中指定了WS_OVERLAPPEDWINDOW,另一个常量WS_TILEDWINDOW有着相同的值)有效。对于CreateWindows函数的其它内容,比如关于dwStyle参数所用常量的详细参考请自行参阅Win32 SDK中的文档。

注意:尽管Windows 95是一个32位的操作系统,但是,其中也保留了很多16位的特征,比如说,在Windows 95环境下,系统最多只可以有16384个窗口句柄。而在Windows NT下则无此限。然而,事实上,对于一般的桌面个人机系统来说,我们几乎不可能超过这个限制。

创建窗口完成之后,ShowWindows显示该窗口,第二个参数SW_SHOW表示在当前位置以当前大小激活并显示由第一个参数标识的窗口。然后,函数UpdateWindows向窗口发送一条WM_PAINT消息,以通知窗口更新其客户区。需要注意的是,由UpdateWindows发送的WM_PAINT消息将直接发送到窗口过程(在上面的例子中是WndProc函数),而不是发送到进程的消息队列,因此,尽管这时应用程序的主消息循环尚未启动,但是窗口过程仍可接收到该WM_PAINT消息并更新其用户区。在完成上面的步骤之后,进入应用程序的主消息循环。一般情况下,主消息循环具有下面的格式:

while (GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

主消息循环由对三个API函数的调用和一个while结构组成。其中GetMessage从调用线程的消息队列中获取消息,并将消息放到由第一个参数指定的消息结构中。如果指定了第二个参数,则GetMessage获取属于该参数指定的窗口句柄所标识的窗口的消息,如果该参数为NULL,则GetMessage获取属于调用线程及属于该线程的所有窗口的消息。最后两个参数指定了GetMessage所获取消息的范围,如果两个参数均为0,则GetMessage检索并获取所有可以得到的消息。

在上面的代码中,变量msg是一个类型为MSG的结构对象,该结构体的定义如下:

typedef struct tagMSG { // msg

HWND hwnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

DWORD time;

POINT pt; } MSG;

下面解释各成员的含义:

hwnd: 标识获得该消息的窗口进程的窗口句柄。

message: 指定消息值。

wParam: 其含义特定于具体的消息类型。

lParam: 其含义特定于具体的消息类型。

time: 指定消息发送时的时间。

pt: 以屏幕坐标表示的消息发送时的鼠标指针的位置。

在while循环体中的TranslateMessage函数将虚拟按键消息翻译为字符消息,然后将消息发送到调用线程的消息队列,在下一次调用GetMessage函数或PeekMessage函数时,该字符消息将被获取。TranslateMessage函数将WM_KEYDOWN和WM_KEYUP虚拟按键组合翻译为WM_CHAR和WM_DEADCHAR,将WM_SYSKEYDOWN 和WM_SYSKEYUP虚拟按键组合翻译为WM_SYSCHAR和WM_SYSREADCHAR。需要注意的一点是,仅当相应的虚拟按键组合能够被翻译为所对应的ASCII字符时,TranslateMessage才发送相应的WM_CHAR消息。

如果一个字符消息被发送到调用线程的消息队列,则TranlateMessage返回非零值,否则返回零值。

注意:与在Windows 95操作系统下不同,在Windows NT下,TranslateMessage对于功能键和光标箭头键也返回一个非零值。然后,函数DispatchMessage将属于某一窗口的消息发送该窗口的窗口过程。这个窗口由MSG结构中的hwnd成员所标识的。函数的返回值为窗口过程的返回值,但是,我们一般不使用这个返回值。这里要注意的是,并不一定是所有属于某一个窗口的消息都发送给窗口的窗口过程,比如对于WM_TIMER消息,如果其lParam 参数不为NULL的话,由该参数所指定的函数将被调用,而不是窗口过程。

如果GetMessage从消息队列中得到一个WM_QUIT消息,则它将返回一个假值,从而退出消息循环,WM_QUIT 消息的wParam参数指定了由PostQuitMessage函数给出的退出码,一般情况下,WinMain函数返回同一值。

下面我们来看一下程序主窗口的窗口过程WndProc。窗口过程名是可以由用户自行定义,然后在注册窗口类时在WNDCLASS结构中指定。但是,一般来说,程序都把窗口过程命令为WndProc来类似的名称,如MainWndProc 等,并不是一定要这样做,但是这样明显的有利于阅读,因此也是我们推荐的做法。窗口过程具有如下的原型:LRESULT WINAPI WndProc(HWND,UINT,WPARAM,LPARAM);

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

对于编译器而言,两种书写形式都是一样的,它们都等价于

long __stdcall WndProc(void *,unsigned int,unsigned int,long)

窗口过程使用了四个参数,在它被调用时(再强调一点,一般情况下,窗口过程是由操作系统调用,而不是由应用程序调用的,这就是我们为什么将它们称为回调函数的道理),这四个参数对应于所发送消息结构的前四个成员。下面给出了一个窗口过程的例子:

// WndProc 主窗口过程

LRESULT WINAPI WndProc (HWND hWnd,

UINT msg,

WPARAM wParam,

LPARAM lParam)

{

HDC hdc;

RECT rc;

HPEN hPen,hPenOld;

HBRUSH hBrush,hBrushOld;

switch (msg)

{

case WM_PAINT:

hdc=GetDC(hWnd);

GetClientRect(hWnd,&rc);

hPen=CreatePen(PS_SOLID,0,RGB(0,0,0));

hBrush=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,0));

hPenOld=SelectObject(hdc,hPen);

hBrushOld=SelectObject(hdc,hBrush);

Ellipse(hdc,rc.left,rc.top,rc.right,rc.bottom);

SelectObject(hdc,hPenOld);

SelectObject(hdc,hBrushOld);

ReleaseDC(hWnd,hdc);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

break;

}

return DefWindowProc(hWnd,msg,wParam,lParam);

}

在该窗口过程中,我们处理了最基本两条消息。

第一条消息是WM_PAINT,当窗口客户区的全部或一部分需要重绘时,系统向该窗口发送该消息。在前面的过程中我们已经提到过,在使用ShowWindow函数显示窗口之后,通常随即调用函数UpdateWindow,该函数直接向窗口过程发送一个WM_PAINT消息,以通知窗口绘制其客户区。在该消息的处理函数中,我们先使用GetDC获得窗口的设备句柄,关于设备句柄本书后面将要专门涉及,这里我们只需知道它是用来调用各种绘图方法的。然后调用GetClientRect获得当前窗口的客户区矩形。接着调用CreatePen创建一个黑色画笔,调用CreateHatchBrush创建一个45度交叉线的填充画刷,并且SelectObject函数将它们选入设备描述表中,原有的画笔和画刷被保存到hPenOld 和hBrushOld中,以便以后恢复。完成以上步骤之后,调用Ellipse函数以当前客户区大小绘制一个椭圆。最后,再一次调用SelectObject函数恢复原有的画笔和画刷,并调用ReleaseDC释放设备描述表句柄。在这个消息的处理代码中,我们涉及到了一些新的概念、数据类型和API函数,然而本章并不着意于讲述这些内容,读者也不必深究它们,这些代码只是为了完整该示例程序才使用的。对于窗口来说,除了客户区以外的其它内容将由系统进行重绘,这些内容包括窗口标题条、边框、菜单条、工具条以及其它控件,如果包含了它们的话。这种重绘往往发生在覆盖于窗口上方的其它窗口被移走,或者是窗口被移动或改变大小时。因此,对于大多数窗口过程来说,WM_PAINT 消息是必须处理的。

另一个对于绝大多数窗口过程都必须处理的消息是WM_DESTROY,当窗口被撤消时(比如用户从窗口的系统菜单中选择了“关闭”,或者单击了右边的小叉,对于这些事件,Windows的默认处理是调用DestroyWindow函数撤销相应的窗口),将会接收到该消息。由于本程序仅在一个窗口,因此在这种情况下应该终止应用程序的执行,因此我们调用了PostQuitMessage函数,该函数向线程的消息队列中放入一个WM_QUIT消息,传递给PostQuitMessage 函数的参数将成为WM_QUIT消息的wParam参数,在上面的例子中,该值为0。

对于其它情况,在上面的示例程序中我们没有必要进行处理,Windows专门为此提供了一个默认的窗口过程,称为DefWindowProc,我们只需要以WndProc的参数原封不动的调用默认窗口过程DefWindowProc,并将其返回值作为WndProc的返回值即可。

将上面讲述的所有内容综合起来,我们就已经使用Win32 SDK完成了一个功能简单,但是结构完整的Win32应用程序了。对于使用Win32SDK编写的实用的Win32应用程序,它们的结构与此相比要复杂得多,在这些情况下,应用程序也许不仅仅包括一个窗口,而对应的窗口过程中的switch结构一般也会是一个异常膨胀的嵌套式switch 结构。如此庞大的消息处理过程大大增加了程序调试和维护的难度,使用MFC则有可能在很多程度上减轻这种负担,这便是MFC为广大程序员所乐于接受,以至今天成为实际上的工业标准的原因。但是,不管它如何复杂,归根到底,一般情况下,它仍然具有和我们的这个功能简单的Win32应用程序一样或类似的结构。为了读者阅读和分析方便,我们把这个程序的完整代码给出如下:

#include

// 函数原型

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int); LRESULT WINAPI WndProc(HWND,UINT,WPARAM,LPARAM); // WinMain 函数

int WINAPI WinMain (HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

HWND hWnd; // 主窗口句柄

MSG msg; // 窗口消息

WNDCLASS wc; // 窗口类

if (!hPrevInstance)

{

// 填充窗口类信息

wc.style=CS_HREDRAW|CS_VREDRAW;

wc.lpfnWndProc=WndProc;

wc.cbClsExtra=0;

wc.cbWndExtra=0;

wc.hInstance=hInstance;

wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);

wc.hCursor=LoadCursor(NULL,IDC_ARROW);

wc.hbrBackground=GetStockObject(WHITE_BRUSH);

wc.lpszMenuName=NULL;

wc.lpszClassName="SdkDemo1";

// 注册窗口类

RegisterClass(&wc);

}

// 创建应用程序主窗口

hWnd=CreateWindow ("SdkDemo1", // 窗口类名

"第一个Win32 SDK应用程序", // 窗口标题

WS_OVERLAPPEDWINDOW, // 窗口样式

CW_USEDEFAULT, // 初始化x 坐标

CW_USEDEFAULT, // 初始化y 坐标

CW_USEDEFAULT, // 初始化窗口宽度

CW_USEDEFAULT, // 初始化窗口高度

NULL, // 父窗口句柄

NULL, // 窗口菜单句柄

hInstance, // 程序实例句柄

NULL); // 创建参数

// 显示窗口

ShowWindow(hWnd,SW_SHOW);

// 更新主窗口客户区

UpdateWindow(hWnd);

// 开始消息循环

while (GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

// WndProc 主窗口过程

LRESULT WINAPI WndProc (HWND hWnd,

UINT msg,

WPARAM wParam,

LPARAM lParam)

{

HDC hdc;

RECT rc;

HPEN hPen,hPenOld;

HBRUSH hBrush,hBrushOld;

switch (msg)

{

case WM_PAINT:

hdc=GetDC(hWnd);

GetClientRect(hWnd,&rc);

hPen=CreatePen(PS_SOLID,0,RGB(0,0,0));

hBrush=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,0));

hPenOld=SelectObject(hdc,hPen);

hBrushOld=SelectObject(hdc,hBrush);

Ellipse(hdc,rc.left,rc.top,rc.right,rc.bottom);

SelectObject(hdc,hPenOld);

SelectObject(hdc,hBrushOld);

ReleaseDC(hWnd,hdc);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

break;

}

return DefWindowProc(hWnd,msg,wParam,lParam);

}

该示例代码中的所有内容都已在前面做了完整的讲解,这里我们简单的说一下如何在Microsoft Developer Studio中编译该示例程序。请按下面的步骤进行:

1. 选择File菜单下的New命令,新建一个Win32 Application工程,这里我们假设对该工程命名为SdkDemo1,而事实上这完全取决于你的意愿。这个过程已经在本书的第一章中作为介绍,这里就不再重复说明了。

2. 选择Project菜单下的Add To Project|New...命令,向工程中添加一个C++ Source File (C++源文件),可以将该文件命名为winmain.cpp,不需要键入扩展名,Microsoft Developer Studio在创建文件时会自动加上.cpp的后缀名。这个过程也已经在第一章中作过介绍。阅读过该章内容的读者不应感到陌生。然后在Wordspace窗口的FileView中双击文件名winmain.cpp (在依赖于你在前面过程中的设定),输入下面的源代码即可。如果已将源代码输入为C++源文件(以.cpp为后缀名的文件),则可以使用Project|Add To Project|Files...将其添加到工程中。

3. 单击Build菜单下的Build SdkDemo1.exe或Build All或按下快捷键F7 (如果未对该快捷键做过自定义操作的话)或单击Build或BuildMinibar工具条上的按钮,编译并创建可执行文件SdkDemo1.exe,运行该可执行文件(从Developer Studio中或资源管理器均可),将得到如图3.2所示的结果。

图3.2 示例程序SdkDemo1的运行结果

前面已经不只一次说到过,使用这种方式编写的应用程序使用调试和维护的难度很大。这个问题是使用直接使用SDK编程的固有总是。但是,我们还是有办法可以使得该程序的结构更紧凑和更集中一些,从而改善代码的可读性,也使得它更接近于使用SDK编写的真正的Win32应用程序。

通过分析应用程序,我们发现,在上面的程序代码中,WinMain函数的代码显得有些过分臃肿,解决总是的办法就是将这些代码分离为单个的函数,这样,我们就可以得到更实用的基本SDK应用程序框架,当然,相对于MFC所提供的应用程序框架来说,我们的这个应用程序框架几乎不值一提,但是,它的确是要比前面的示例程序好多了。经过修改的代码如下:

#include

// 函数原型

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int);

LRESULT WINAPI WndProc(HWND,UINT,WPARAM,LPARAM);

BOOL InitApplication(HINSTANCE);

BOOL InitInstance(HINSTANCE,int);

// WinMain 函数

int WINAPI WinMain (HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

if (!hPrevInstance)

if (!InitApplication(hInstance))

return FALSE;

if (!InitInstance(hInstance,SW_SHOW))

return FALSE;

MSG msg; // 窗口消息

// 开始消息循环

while (GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

// WndProc 主窗口过程

LRESULT WINAPI WndProc (HWND hWnd,

UINT msg,

WPARAM wParam,

LPARAM lParam)

{

HDC hdc;

RECT rc;

HPEN hPen,hPenOld;

HBRUSH hBrush,hBrushOld;

switch (msg)

{

case WM_PAINT:

hdc=GetDC(hWnd);

GetClientRect(hWnd,&rc);

hPen=CreatePen(PS_SOLID,0,RGB(0,0,0));

hBrush=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,0)); hPenOld=SelectObject(hdc,hPen);

hBrushOld=SelectObject(hdc,hBrush);

Ellipse(hdc,rc.left,rc.top,rc.right,rc.bottom); SelectObject(hdc,hPenOld);

SelectObject(hdc,hBrushOld);

ReleaseDC(hWnd,hdc);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

break;

}

return DefWindowProc(hWnd,msg,wParam,lParam);

}

BOOL InitApplication(HINSTANCE hInstance)

{

WNDCLASS wc; // 窗口类

// 填充窗口类信息

wc.style=CS_HREDRAW|CS_VREDRAW;

wc.lpfnWndProc=WndProc;

wc.cbClsExtra=0;

wc.cbWndExtra=0;

wc.hInstance=hInstance;

wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);

wc.hCursor=LoadCursor(NULL,IDC_ARROW);

wc.hbrBackground=GetStockObject(WHITE_BRUSH); wc.lpszMenuName=NULL;

wc.lpszClassName="SdkDemo2";

// 注册窗口类

return RegisterClass(&wc);

}

BOOL InitInstance(HINSTANCE hInstance,int nCmdShow) {

HWND hWnd; // 主窗口句柄

// 创建应用程序主窗口

hWnd=CreateWindow ("SdkDemo2", // 窗口类名

"经过修改的第一个Win32 SDK应用程序", // 窗口标题

WS_OVERLAPPEDWINDOW, // 窗口样式

CW_USEDEFAULT, // 初始化x 坐标

CW_USEDEFAULT, // 初始化y 坐标

CW_USEDEFAULT, // 初始化窗口宽度

CW_USEDEFAULT, // 初始化窗口高度

NULL, // 父窗口句柄

NULL, // 窗口菜单句柄

hInstance, // 程序实例句柄

NULL); // 创建参数

if (!hWnd)

return FALSE;

// 显示窗口

ShowWindow(hWnd,SW_SHOW);

// 更新主窗口客户区

UpdateWindow(hWnd);

return TRUE;

}

由于上面的代码只是将前面的代码的结构作了一下调整,并没有引入新的API函数和其它编程内容,因此为了节省篇幅,我们这里对该代码不再进行讲解,至于其与SdkDemo1的代码相比的优越性,由读者自己将两段代码对比后得出。

第四节32位编程的特点

本节假定用户是刚接触32位Windows编程的新手,那么,有必要将一些相关的概念术语弄清楚,同时,也要把Windows 95、Windows NT和16位的Windows 3.x相区别开来。这些最重要的概念包括进程和线程的管理以及新的32位平坦内存模式。

在介绍32位内存管理之前,我们有必要介绍一下进程和线程这两个术语。

进程是装入内存中正在执行的应用程序,进程包括私有的虚拟地址空间、代码、数据及其它操作系统资源,如文件、管道以及对该进程可见的同步对象等。进程包括了一个或多个在进程上下文内运行的线程。

线程是操作系统分配CPU时间的基本实体。线程可以执行应用程序代码的任何部分,包括当前正在被其它线程执行的那些。同一进程的所有线程共享同样的虚拟地址空间、全局变量和操作系统资源。

在一个应用程序中,可以包括一个或多个进程,每个进程由一个或多个线程构成。

线程通过“休眠”(sleeping,暂停所有执行并等待)的方法,来做到与进程中的其它线程所同步。在线程休眠前,必须告诉Windows,该线程将等待某一事件的发生。当该事件发生时,Windows发给线程一个唤醒调用,线程继续执行。也就是说,线程与事件一起被同步,除此之外,也可以由特殊的同步对象来进行线程的同步。这些同步对象包括:

互斥不受控制的或随意的线程访问在多线程应用程序中可能会引起很大的问题。这里所说的互斥是一小须代码,它时刻采取对共享数据的独占控制以执行代码。互斥常被应用于多进程的同步数据存取。

信号量信号量与互斥相似,但是互斥只允许在同一时刻一个线程访问它的数据,而信号量允许多个线程在同一时刻访问它的数据。Win32不知道哪一个线程拥有信号量,它只保证信号量使用的资源量。

临界区临界区对象也和互斥相似,但它仅被属于单个进程的线程使用。临界区对象提供非常有效的同步模式,同互斥一样,每次在同一时间内只有一个线程可以访问临界区对象。

事件事件对象用于许多实例中去通知休眠的线程所等待的事件已经发生,事件告诉线程何时去执行某一个给定的任务,并可以使多线程流平滑。

将所有的这些同步对象应用于控制数据访问使得线程同步成为可能,否则,如果一个线程改变了另一个线程正在读的数据,将有可能导致很大的麻烦。

在Win32环境下,每个运行的在进程内的线程还可以为它自己的特定线程数据分配内存,通过Win32提供的线程本地存储(TLS)API,应用程序可以建立动态的特定线程数据,在运行时这些数据联系在一起。本书将在专门的章节

中讨论线程和进程的问题。

下面我们来看在32位应用程序地址空间中的内存分配和内存管理。

常见的内存分配可以划分为两类:帧分配(frame allocation)和堆分配(heap allocation)。两者的主要区别在于帧分配通常和实际的内存块打交道,而堆分配在一般情况下则使用指向内存块的指针,并且,帧对象在超过其作用域时会被自动的删除,而程序员必须显式的删除在堆上分配的对象。

在帧上分配内存的这种说法来源于“堆栈帧”(stack frame)这个名词,堆栈帧在每当函数被调用时创建,它是一块用来暂时保存函数参数以及在函数中定义的局部变量的内存区域。帧变量通常被称作自动变量,这是因为编译器自动为它们分配所需的内存。

帧分配有两个主要特征,首先,当我们定义一个局部变量是,编译器将在堆栈帧上分配足够的空间来保存整个变量,对于很大的数组和其它数据结构也是这样;其次,当超过其作用域时,帧变量将被自动的删除。下面举一个帧分配的例子:

int Func(int Argu1,int Argu2) // 编译器将在堆栈帧上为函数参数变量分配空间

{

// 在堆栈上创建局部对象

char szDatum[256][256];

...

// 超过作用域时将自动删除在堆栈上分配的对象

}

对于局部函数变量,其作用域转变在函数退出时发生,但如果使用了嵌套的花括号,则帧变量的作用域将有可能比函数作用域小。自动删除这些帧变量非常之重要。对于简单的基本数据类型(如整型或字节变量)、数组或数据结构,自动删除只是简单的回收被这些这是所占用的内存。由于这些变量已超出其作用域,它们将再也不可以被访问。对于C++对象,自动删除的过程要稍稍复杂一些。当一个对象被定义为一个帧变量时,其构造函数在定义对象变量时被自动的调用,当对象超出其作用域时,在对象所占用的内存被释放前,其析构函数先被自动的调用。这种对构造函数和析构函数的调用看起来非常的简便,但我们必须对它们倍加小心,尤其是对析构函数,不正确的构造和释放对象将可能对内存管理带来严重的问题。在本书的第二章中我们曾经历过其中的一种——指针挂起。

在帧上分配对象的最大的优越性在于这些对象将会被自动的删除,也就是说,当你在帧上分配对象之后,不必担心它们会导致内存漏损(memory leak)。但是,帧分配也有其不方便之处,首先,在帧上分配的变量不可以超出其作用域,其中,帧空间往往是有限的,因此,在很多情况下,我们更倾向于使用下面接着要讲述的堆分配来代替这里所讲述的帧分配来为那些庞大的数据结构或对象分配内存。

堆是为程序所保留的用于内存分配的区域,它与程序代码和堆栈相隔离。在通常情况下,C程序使用函数malloc 和free来分配和释放堆内存。调试版本(Debug version)的MFC提供了改良版本的C++内建运算符new和delete用于在堆内存中分配和释放对象。

使用new和delete代替malloc和free可以从类库提供的增强的内存管理调试支持中得到好处,这在检测内存漏损时非常之有用。而当你使用MFC的发行版本(Release version)来创建应用程序时,MFC的发行版本并没有使用这种改良版本的new和delete操作符,取而代之的是一种更为有效的分配和释放内存的方法。

与帧分配不同,在堆上可分配的对象所占用的内存的总量只受限于系统可有的所有虚拟内存空间。

以下的示例代码对比了上面讨论的内存分配方法在为数组、数据结构和对象分配内存时的用法:

使用帧分配为数组分配内存:

{

const int BUFF_SIZE = 128;

// 在帧上分配数组空间

char myCharArray[BUFF_SIZE];

int myIntArray[BUFF_SIZE];

// 所分配的空间在超出作用域时自动回收

}

使用堆分配为数组分配内存:

const int BUFF_SIZE = 128;

// 在堆上分配数组空间

char* myCharArray = new char[BUFF_SIZE];

int* myIntArray = new int[BUFF_SIZE];

...

delete [] myCharArray;

delete [] myIntArray;

使用帧分配为结构分配内存:

struct MyStructType { int topScore;};

void SomeFunc(void)

{

// 帧分配

MyStructType myStruct;

// 使用该结构

myStruct.topScore = 297;

// 在超出作用域时结构所占用的内存被自动回收

}

使用堆分配为结构分配内存:

// 堆分配

MyStructType* myStruct = new MyStructType;

// 通过指针使用该结构

myStruct->topScore = 297;

delete myStruct;

使用帧分配为对象分配内存:

{

CMyClass myClass; // 构造函数被自动调用

myClass.SomeMemberFunction(); // 使用该对象

}

使用堆分配为对象分配内存:

// 自动调用构造函数

CMyClass *myClass=new CMyClass;

myClass->SomeMemberFunction(); // 使用该对象

delete myClass; // 在使用delete的过程中调用析构函数

注意:一定要记住一个事实,在堆上分配的内存一定要记得释放,对于使用运算符new分配的内存,应当使用delete运算符来释放;而使用malloc函数分配的内存应当使用free函数来释放。不应当对同一内存块交叉使用运算符new、delete和函数malloc、free (即使用delete运算符释放由malloc函数分配的内存,或使用free函数释放由new 运算符根本的内存),否则在MFC的调试版本下将会导致内存冲突。

对固定大小的内存块,使用运算符new和delete要比使用标准C库函数malloc和free方便。但有时候我们需要使用可变大小的内存块,这时,我们必须使用标准的C库函数malloc、realloc和free。下面的示例代码创建了一个可变大小的数组:

#include

#include

#define UPPER_BOUND 128

void main()

{

int *iArray=(int *)malloc(sizeof(int));

for (int i=0;i

{

iArray=(int *)realloc(iArray,(i+1)*sizeof(int));

printf(" %08x",(int)iArray);

iArray[i]=i+1;

}

for (i=0;i

{

printf("%5d",iArray[i]);

}

free(iArray);

return;

}

观察下面的运行结果:

00410770 00410780 00410790 004107a0 004107a0 004107c0 004107e0 00410800

00410800 00410830 00410860 00410890 00410890 004108d0 00410910 00410950

00410950 004109a0 004109f0 00410a40 00410a40 00410aa0 00410b00 00410b60

00410b60 00410bd0 00410c40 00410cb0 00410cb0 00410d30 00410db0 00410e30

00410e30 00410ec0 00410f50 00410760 00410760 00410800 004108a0 00410940

00410940 004109f0 00410aa0 00410b50 00410b50 00410c10 00410cd0 00410d90

00410d90 00410e60 00410f30 00410760 00410760 00410840 00410920 00410a00

00410a00 00410af0 00410be0 00410cd0 00410cd0 00410dd0 00410ed0 00410760

00410760 00410870 00410980 00410a90 00410a90 00410bb0 00410cd0 00410df0

00410df0 00410760 00410890 004109c0 004109c0 00410b00 00410c40 00410d80

00410d80 00410760 004108b0 00410a00 00410a00 00410b60 00410cc0 00410e20

00410e20 00410760 004108d0 00410a40 00410a40 00410bc0 00410d40 00410760

00410760 004108f0 00410a80 00410c10 00410c10 00410db0 00410760 00410900

00410900 00410ab0 00410c60 00410e10 00410e10 00410760 00410920 00410ae0

00410ae0 00410cb0 00410760 00410930 00351d98 00351d98 00351d98 00351d98

00351d98 00351d98 00351d98 00351d98 00351d98 00351d98 00351d98 00351d98

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 36 37 38 39 40 41 42 43 44 45 46 47 48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128

上面的结果说明两个问题:一是在使用realloc改变所分配内存单元的大小时,realloc可能会移动该内存块到新的位置。二是在realloc改变和移动内存块时,该内存块中的数据也同时会被保留。所以上面的模式可以满足一些特殊场合的需要。但是,请记住一点,随着内存块的增大,realloc的运行效率会极大的下降,当上面的,因此,除非必要,我们不要使用这种方式,虽然它灵活方便,但这往往是以牺牲效率为代价的。

最后重新强调一点,即不要使用realloc改变由new运算符分配的内存块的大小。前面已经说过,这将会在MFC的调试版本中导致内存冲突。

下面我们简单的介绍Win32内存管理模式。

在Microsoft Win32应用程序编程接口中,每一个进程都有自己多达4GB的虚拟地址空间。内存中低位的2GB (从0x00到0x7FFFFFFF)可以为用户所用,高位的2GB (从0x80000000到0xFFFFFFFFF)为内核所保留。进程所使用的虚拟地址并不代码对象在内存中实际的物理地址。事实上,内核为每一个进程维护了一个页映射,页映射是一个用于将虚拟地址转换为对应的物理地址的内部数据结构。

每一个进程的虚拟地址空间都要比所有进程可用的物理内存RAM (随机存取存储器)的总合大得多。为了增加物理存储的大小,内核使用磁盘作为额外的存储空间。对于所有正在执行的进程来说,总的存储空间的量是物理内存RAM和磁盘上可以为页面文件所用的自由空间的总合,这里页面文件指用来增加物理存储空间的磁盘文件。每个

进程物理存储空间和虚拟地址(也称为逻辑地址)空间以页的形式来组织,页是一种内存单元,其大小依赖于宿主计算机的类型。对于x86计算机来说,宿主页大小为4KB,但我们不能假定对有所有运行Windows操作系统的计算机,其页大小均为4KB。

为了使内存管理具有最大的灵活性,内核可以将物理内存中的页移入或移出磁盘上的页面文件。当一个页被移入物理内存时,内核更新受到影响的进程的页映射。将内核需要物理内存空间时,它将物理内存中最近最少使用的页移入页面文件。对于应用程序来说,内核对物理内存的管理是完全透明的,应用程序只对它自己的虚拟地址空间进行操作。

在进程虚拟地址空间中的页可以具在表所列的状态之一:

进程可以使用函数GlobalAlloc和LocalAlloc来分配内存。在Win32API的32位线性环境中,本地堆和全局堆并没有区别,因此,使用这两个函数来分配内存对象也没有任何区别。

由GlobalAlloc和LocalAlloc函数分配的内存对象位于私有的占用页中,这些页允许进行读写存取。私有内存不可以为其它进程所访问。与在Windows 3.x中不同,使用带有GMEM_DDESHARE标志的GlobalAlloc函数分配的内存事实上并没有被全局共享。保留该标志位仅是为了向前兼容和为一些应用程序增强动态数据交换(DDE,dynamic data exchange)的性能而使用。应用程序如果因其它目的需要共享内存,那么必须使用文件映射对象。多个进程可以通过映射同一个文件映射对象的视来提供命名共享内存。我们在这里将不讨论文件映射和共享内存的问题。

通过使用函数GlobalAlloc和LocalAlloc,可以分配能够表示为32位的任意大小的内存块,所受的唯一限制是可用的物理内存,包括在磁盘上的页面文件中的存储空间。这些函数,和其它操作全局和本局内存对象的全局和本地函数一起被包含在Win32 API中,以和Windows的16位版本相兼容。但是,从16位分段内存模式到32位虚拟内存模式的转变将使得一些函数和一些选项变得不必要甚至没有意义。比如说,现在不再和近指针和远指针的区别,因为无论在本地还是在全局进行分配都将返回32位虚拟地址。

函数GlobalAlloc和LocalAlloc都可以分配固定或可移动的内存对象。可移动对象也可以被标记为可丢弃的(discardable)。在早期的Windows版本中,可移动的内存对象对于内存管理非常之重要,它们允许系统在必要时压缩堆以为其它内存分配提供可用空间。通过使用虚拟内存,系统能够通过移动物理内存页来管理内存,而不影响使用这些页的进程的虚拟地址。当系统移动一个物理内存页时,它简单的将进程的虚拟页映射到新的物理页的位置。可移动内存在分配可丢弃内存仍然有用。当系统需要额外的物理存储时,它使用一种称作“最近最少使用”的算法来释放非锁定的可丢弃内存。可丢弃内存可以用于那些不是经常需要和易于重新创建的数据。

当分配固定内存对象时,GlobalAlloc和LocalAlloc返回32位指针,调用线程可以立即使用该指针来进行内存存取。对于可移动内存,返回值为一个句柄。为了得到一个指向可移动内存的指针,调用线程可以使用GlobalLock和LocalLock函数。这些函数锁定内存使得它不能够被移动或丢弃,除非使用函数GlobalReAlloc或LocalReAlloc对内存对象进行重新分配。已锁定内存对象的内存块保持锁定状态,直至锁定计数减到0,这时该内存块可以被移动或丢弃。

由GlobalAlloc和LocalAlloc所分配的内存的实际大小可能大于所要求的大小。为了得到已分配的实际内存数,可以使用函数GlobalSize和LocalSize。如果总分配量大于所要求的量,进程则可以使用所有的这些量。

函数GlobalReAlloc和LocalReAlloc以字节为单位改变由GlobalAlloc和LocalAlloc函数分配内存对象的大小或其属性。内存对象的大小可以增大,也可以减小。

函数GlobalFree和LocalFree用于释放由GlobalAlloc、LocalAlloc、GlobalReAlloc或LocalReAlloc分配的内存。

其它的全局和本地函数包括GlobalDiscard、LocalDiscard、GlobalFlags、LocalFlags、GlobalHandle和LocalHandle。GlobalDiscard和LocalDiscard用于丢弃指定的可丢弃内存对象,但不使其句柄无效。该句柄可能通过函数GlobalReAlloc或LocalReAlloc与新分配的内存块相关联。函数GlobalFlags或LocalFlags返回关于指定内存对象的信息。这些住处包括对象的锁定计数以及对象是否可丢弃或是否已被丢弃。函数GlobalHandle或LocalHandle返回与指定指针相关联的内存对象的句柄。

Win32进程可以完全的使用标准的C库函数malloc、free等来操作内存。在Windows的早期版本中使用这些函数,将可能带来问题隐患,但是使用Win32 API的应用程序中则不会。举例来说,使用malloc分配固定指针将不能使用可移动内存的优点。由于系统可以通过移动物理内存页来自由的管理内存,而不影响虚拟地址,因此内存管理将不再成为问题。类似的,远指针和近指针之间不再有差别。因此,除非你希望使用可丢弃内存,否则完全可以将标准的C库函数用于内存管理。

Win32 API提供了一系列的虚拟内存函数来操作或决定虚拟地址空间中的页的状态。许多应用程序使用标准的分配函数GlobalAlloc、LocalAlloc、malloc等就可以满足其需要。然而,虚拟内存函数提供了一些这些标准分配函数所不具有的功能,它们可以进行下面的这些操作:

保留进程虚拟地址空间中的一段保留地址空间并不为它们分配物理存储,而只是防止其它分配操作使用这段空间。它并不影响其它进程的虚拟地址空间。保留页防止了对物理存储的不必要的浪费,然而它允许进程为可能增长的动态数据结构保留一段地址空间,进程可以在需要的时候为这些空间分配物理存储。

占用进程虚拟地址空间中的保留页的一部分,以使得物理存储(无论是RAM还是磁盘空间)只对正在进行分配的进程可用。

指定允许读写存取、只读存取或不允许存取的占用页区域。这和标准的分配函数总是分配允许读写存取的页不同。 释放一段保留页,使得调用线程在随后的分配操作中可以使用这段虚拟地址。

取消对一段页的占用,释放它们的物理存储,使它们可以其它进程在随后的分配中使用。

在物理内存RAM中锁定一个或多个占用页,以免系统将这些页交换到页面文件中。

获得关于调用线程或指定线程的虚拟地址空间中的一段页的信息。

改变调用线程或指定线程的虚拟地址空间中指定占用页段的存取保护。

虚拟内存函数对内存页进行操作。函数使用当前计算机的页大小来对指定的大小和地址进行舍入。

可以使用函数GetSystemInfo来获得当前计算机的页大小。

函数VirtualAlloc完成以下操作:

保留一个或多个自由页。

占用一个或多个保留页。

保留并占用一个或多个自由页。

你可以指针所保留或占用的页的起始地址,或者让系统来决定。函数将指定的地址舍入到合适的页边界。保留页是不可访问的,但占用页可以使用标志位PAGE_READWRITE、PAGE_READONL Y和PAGE_NOACCESS来分配。当页被占用时,从页面文件中分配存储空间,每个页仅在第一次试图对其进行读写操作时被初始化并加载到物理内存中。可以用一般的指针引用来访问由VirtualAlloc函数占用的页。

函数VirtualFree完成下面的操作:

解除对一个或多个页的占用,改变其状态为保留。解除对页的战胜释放与之相关的物理存储,使其为其它进程可用。任何占用页块都可以被解除占用。

释放一个或多个保留页块,改变其状态为自由。释放页块使这段保留空间可以为进程分配空间使用。保留页只能通过释放由函数VirtualAlloc最初保留的整个块来释放。

同时解除对一个或多个占用页的占用并释放它们,将其状态改变为自由。指定的块必须包括由VirtualAlloc最初保留的整个块,而且这些页的当前状态必须为占用。

函数VirtualLock允许进程将一个或多个占用页锁定在物理内存RAM中,防止系统将它们交换到页面文件中。这保证了一些要求苛刻的数据可以不通过磁盘访问来存取。将一个页锁定入内存是很危险的,因为它限制了系统管理内存的能力。由于可能会将可执行代码交换到页面文件中,可执行程序使用VirtualLock将有可能降低系统性能。函数VirtualUnlock解除VirtualLock对内存的锁定。

函数VirtualQuery和VirtualQueryEx返回关于以进程地址空间中某一指定地址开始的一段连续内存区域的信息。VirtualQuery返回关于调用线程内存的信息。VirtualQueryEx返回指定进程内存的信息,这通常用来支持调试程序,

第2章 程序设计基础习题

程序设计基础 一、判断题 1.整型变量有Byte、Integer、Long类型3种。 2.Byte类型的数据,其数值范围在-255~255之间。 3.Visual Basic的Double类型数据可以精确表示其数值范围内的所有实数。 4.在逻辑运算符Not、Or、And中,运算优先级由高到低依次为Not、Or、And。 5.关系表达式是用来比较两个数据的大小关系的,结果为逻辑值。 6.一个表达式中若有多种运算,在同一层括号内,计算机按函数运算→逻辑运算→关系运算→算术运算的顺序对表达式求值。 7.赋值语句的功能是计算表达式值并转换为相同类型数据后为变量或控件属性赋值。 8.用DIM定义数值变量时,该数值变量自动赋初值为0。 9.函数InputBox的前3个参数分别是输入对话框的提示信息、标题以及默认值。 10.函数MsgBox的前3个参数分别表示默认按钮、按钮样式以及图标样式。 二、选择题 1.Integer类型数据能够表示的最大整数为________ 。 A、275 B、215-1 C、216 D、216-1 2.货币类型数据小数点后面的有效位数最多只有________ 。 A、1位 B、6位 C、16位 D、4位 3.输入对话框InputBox的返回值的类型是________ 。 A、字符串 B、整数 C、浮点数 D、长整数 4.运算符“\”两边的操作数若类型不同,则先________ 再运算。 A、取整为Byte类型 B、取整为Integer类型 C、四舍五入为整型 D、四舍五入为Byte类型 5.Int( Rnd * 100 ) 表示的是________ 范围内的整数。 A、[0,100] B、[1,99] C、[0,99] D、[1,100] 6.下列程序段的输出结果是________ 。 a=10: b=10000: x=log(b)/log(a): Print "lg(10000)=";x A、lg(10000)=5 B、lg(10000)=4 C、4 D、5 7.返回删除字符串前导和尾随空格符后的字符串,用函数________ 。 A、Trim B、Ltrim C、Rtrim D、mid 8.Print语句的一个输出表达式为________ ,则输出包括日期、时间信息。 A、Date B、Month C、Time D、Now 9. 语句Print "5*5" 的显示结果是________ 。 A、25 B、"5*5" C、5*5 D、出现错误提示 10.语句“Form1.Print Tab(10);"#"”的作用是在窗体当前输出行________ 。 A、第10列输出字符“#” B、第9列输出字符“#” C、第11列输出字符“#” D、输出10个字符“#” 三、填空题 1.语句“Dim C As________”定义的变量C,可用于存放控件的Caption的值。 2.长整型变量(Long类型)占用________ 个字节。 3.表达式Right(String(65, Asc("abc")), 3)的值是________。 1

第三章机械设计编程基础

第三章 机械设计编程基础 2.1 编程和图表处理的基本方法 一、编制机械设计计算程序的基本方法 (1) 设计数据 (2) 表格、线图及标准规范 (3) 算法设计 [] p p dlh T σσ≤= 4 式中,T 为转矩; h 为键高度; l 为键的工作长度; [σp ]为轮毂的许用挤压应力。 表1 平键(摘自GB1096-90) 轴径 mm d mm b mm h 自6~8 2 2 >8 ~10 3 3 >10~12 4 4 >12~17 5 5 >17~22 6 6 >22~30 8 7 >30~38 10 8 >38~44 12 8 >44~50 14 9

二、设计图表处理的基本方法 1.表格(手册中的)分为两类:? ?? ..:;:着某种联系表格中的数据之间存在列表函数任何联系表格中的数据之间没有数表 2.表格处理的基本方法: (1) 表格的程序化:将数表中的数据以数组形式存储和检索,直接编 在解题的程序中。 (2) 表格的公式化:对于列表函数,可用曲线拟合的方法形成数学表 达式并直接编于程序中。 2-2 设计数表的处理 一、表格的程序化 1. 数表 一维(元)数表:所查取的数据只与一个变量有关的数表; 二维(元)数表:所查取的数据与两个变量有关的数表; 它们均可用一维和二维数组的形式存入计算机,以备程序使用。 一维(元)数表程序化

示例1 : 示例2 : int I; float GAMA[ ] ={ 7.87,7.85,8.30,7.75}; printf( “1. 工业纯铁\ n”); printf( “1. 钢材\ n”); printf( “2. 高速钢\ n”); printf( “3. 不锈钢\ n”); printf( “选择材料类型:”); scanf( “ % d”,&I); printf( “3. 不锈钢\ n”); printf( “材料的密度:% f\ n”,GAMA[I -1]); 表2 材料的密度 材 料 密度 / (g.。cm -3) 工业纯铁 7。87 钢 材 7。85 高 速 钢 8。30 不 锈 钢 7。75

第三章 最简单的c程序设计

第三章最简单的c程序设计 实践教学: 属性:实训 时间:学时。 实践教学内容: 实验目的:熟悉运算符、表达式,掌握标准输入输出函数的适用方法和顺序结构程序设计的一般方法。 实验内容和步骤: 1.编程,要求从键盘按规定的格式输入时间(时:分:秒), 并将输入的时间在屏幕上显示出来,存入ex3_1.c文件, 并编译、调试、运行。 #include main() { int a ,b,c; scanf("%d%d%d",&a,&b,&c); if (a>24) printf("请重新输入"); if (b>60) printf("请重新输入"); if (c>60) printf("请重新输入"); printf("%d:%d:%d\n",a,b,c); return 0; }

2.编程,要求从键盘输入数据,使整型变量a=10,b=8;字符 型c1=’A’,c2=’a’;实型变量x=3.1,y=64.54。并按规定格式输出变量的值。格式如下。存入ex3-2.c文件,并编译、调试、运行。 __int___a=__10___b=____8 char_c1=__A,___c2=____a float___x=_3.1,___y=64.54 #include main() { int a=10,b=8; char c1='A',c2='a'; float x=3.1,y=64.54; return 0; } 3.c语言编程求ax2+bx+c=0并分析其结果。存入ex3_2.c文 件,并编译、调试、运行。 #include

第二章 程序设计基础

1.结构化程序包括的基本控制结构只有三种,即顺序结构、选择结构与循环结构。 对象之间进行通信的构造叫做消息,A正确。多态性是指同一个操作可以是不同对象的行为,D 错误。对象不一定必须有继承性,C错误。封装性是指从外面看只能看到对象的外部特征,而不知道也无须知道数据的具体结构以及实现操作,B错误。 2.对象之间进行通信的构造叫做消息。多态性是指同一个操作可以是不同对象的行为。对象不一定必须有继承性。封装性是指从外面看只能看到对象的外部特征,而不知道也无须知道数据的具体结构以及实现操作。 继承是面向对象的方法的一个主要特征,是使用已有的类的定义作为基础建立新类的定义技术。广义的说,继承是指能够直接获得已有的性质和特征,而不必重复定义它们,所以说继承是指类之间共享属性和操作的机制。 3.整数类实例包括: 十进制常量用0~9表示,不能以0开头; 八进制常量用0~7表示,必须用0开头; 十六进制常量用0~9和A~F(a~f)表示,必须以0x或0X开头。0x518。 0.518是浮点数实例,518E-2为科学计数法表示的浮点数实例。 "-518"是字符串实例, 字符实例的一般形式是用一对单引号括起来的一个字符。另外ASCII码中还有一些控制字符,C 语言中用转义字符的形式来书写这些常,转义字符一反斜杠(\)开始,后面跟1个字符或字符序列。'518'单引号中有三个字符,错误。"5"双引号为字符串,错误。'nm'单引号中有两个字符,错误。'\n'为换行符,属于字符类实例,正确。 4.数据流图从数据传递和加工的角度,来刻画数据流从输入到输出的移动变换过程。数据流图中的主要图形元素有:加工(转换)、数据流、存储文件(数据源)等。

第三章 汇编程序设计

第3-1讲 第3章 单片机汇编语言程序设计 【课 题】MCS-51汇编语言编程 【授课方法】在专业教室讲授,举例说明汇编语言编程应用。 【目的要求】了解本课程汇编语言编程的基础知识; 理解MCS-51汇编语言编程方法; 掌握MCS-51汇编语言编程相关规定和硬件知识。 【重点难点】灵活运用与编程有关的规定。 【教学过程】1、复习 2、程序设计的重要性 3、程序设计方法 汇编语言程序设计 微型机应用离不开应用程序的设计。单片机程序设计多采用汇编语言编写。本章介绍 MCS51系统汇编语言程序编写的一般知识:有关规定、习惯用法、常见程序结构和编程方法。要学会编写程序应掌握编程的一般知识,还要分析一些经典程序,从修改现有程序入手,先简后难,循序渐进,最后达到自已设计应用系统和编写程序的目的。 3.1 单片机汇编程序设计方法与流程 3.1.1 汇编语言程序设计步骤 用汇编语言编制程序的过程,称为汇编语言程序设计。通常,汇编语言程序设计的步 骤如下:整个汇编程序设计的流程图见图3.1,当然、短小程序可能不要这么复杂。 1、设计规划,建立数学模型 设计前对项目作评估和规划,程序功能、运算精度、执行速度、各硬件特点、掌握设计的重点和难点。 2、选择适当的算法 对于同一个任务,往往可用不同的程序实现。此时应结合所用机器的指令系统,对不同的算法进行分析比较,经各方面综合考虑选择一种最佳算法,使程序精简,且执行速度快。 3、程序结构的设计 程序结构设计是把所采用的算法转化为汇编语言程序的准备阶段,特别是对于情况复杂的大型课题,必须进行程序结构设计。它可以分为模块化程序设计、结构程序设计及自顶向下设计等。 设计 课 题 划 设 计规算法模型绘流程程图编 制序汇编 调试试运行完成 修改程序 修改修改规划算法 流程 仿真图3.1汇编语言程序设计流程图 .

第三章-简单程序设计word版本

第三章简单程序设计 3.1 流程结构和语句 1 . 三种流程结构 顺序结构,选择结构,循环结构——程序在逻辑上执行的流程。 ●顺序结构:按语句在源程序中出现的次序依次执行; ●选择结构:根据一定的条件有选择地执行或不执行某些语句。 ●循环结构:在一定条件下重复执行相同的语句。 所有的流程控制都是由语句实现的,且任何一个表达式都可作为一个语句使用,成之为表达式语句 2. 表达式语句 任何表达式通过在其末尾加一个“;”,可使表达式成为一个语句,形式为: 表达式; 其中“;”是C语句的组成部分,表示一个语句结束。表达式语句能够独立出现在程序中,而表达式则不能独立出现。 例如:x=y+1 是表达式 x=y+1;是语句 习惯上把赋值表达式语句如:x=y+1;新为赋值语句。函数调用也是表达式,因此: printf(“hellow”)是表达式; printf(“hellow”);是语句,习惯上称为输出语句。 scanf(“%d%d”,&x,&y);函数调用表达式语句(输入语句) 3. C的语句概述 C的一个“说明”也必须以分号结束,也称为语句,因此C的语句分说明语句和执行语句两类。 说明语句可以出现在程序中任何块(函数或复合语句)的外面——称为外部说明或块内——称为局部说明(在执行语句的前面)。 外部说明必须放在一个源程序文件中所有函数定义的外面;局部说明包括类型定义、变量和函数说明,其作用是描述程序中被处理数据(变量或函数)的名称和类型供解释程序使用。 执行语句只能出现在函数体内且处于局部说明的后面,执行语句完成对数据的处理和对程序流程的控制。 常用的程序结构为: 常量说明 类型说明 变量说明/*外部说明*/ 返回类型函数名(参数表) { 变量说明/*局部说明*/ 执行语句 }

matlab程序设计第三章课后习题答案

1. p138 第6题在同一坐标轴中绘制下列两条曲线并标注两曲线交叉点。 >> t=0:0.01:pi; >> x1=t; >> y1=2*x1-0.5; >> x2=sin(3*t).*cos(t); >> y2=sin(3*t).*sin(t); >> plot(x1,y1,'r-',x2,y2,'g-') >> axis([-1,2,-1.5,1]) >> hold on >> s=solve('y=2*x-0.5','x=sin(3*t)*cos(t)','y=sin(3*t)*sin(t)'); >> plot(double(s.x),double(s.y),'*'); 截图:

p366 第4题绘制极坐标曲线,并分析对曲线形状的影响。 function [ output_args ] = Untitled2( input_args ) %UNTITLED2 Summary of this function goes here % Detailed explanation goes here theta=0:0.01:2*pi; a=input('请输入a的值:'); b=input('请输入b的值:'); n=input('请输入n的值:'); rho=a*sin(b+n*theta); polar(theta,rho,'k'); end 下面以a=1,b=1,n=1的极坐标图形为基础来分析a、b、n的影响。

对a的值进行改变:对比发现a只影响半径值的整倍变化 对b的值进行改变:对比发现b的值使这个圆转换了一定的角度

对n的值进行改变:对比发现当n>=2时有如下规律 1、当n为整数时,图形变为2n个花瓣状的图形 2、当n为奇数时,图形变为n个花瓣状的图形 分别让n为2、3、4、5

第二章 简单的VB程序设计习题

第二章简单的VB程序设计习题 一、选择题 1. 假定已在窗体上画了多个控件,并有一个控件是活动的,为了在属性窗口中设置窗体的属性,预先执行的操作是 ()。 (A)单击窗体上没有控件的地方(B)单击任一个控件 (C)不执行任何操作(D)双击窗体的标题栏 2. 在VB中最基本的对象是(),它是应用程序的基石,是其它控件的容器。 (A)文本框(B)命令按钮 (C)窗体(D)标签 3. 有程序代码如下:text1.text=“visual basic”则:text1、text和“visual basic”分别代表()。 (A)对象,值,属性(B) 对象,方法,属性 (C)对象,属性,值(D) 属性,对象,值 4. VB是一种面向对象的程序设计语言,()不是面向对象系统所包含的三要素。 (A)变量(B)事件(C)属性(D)方法 5. VB的一个应用程序至少包括一个()文件,该文件存储窗体上使用的所有控件对象和有关的()、 对象相应的()过程和()代码。 (A)模块、方法、事件、程序(B)窗体、属性、事件、程序 (C)窗体、程序、属性、事件(D)窗体、属性、事件、方法 6. 在VB6.0环境已有应用程序的情况下,要重新建立一个工程的操作是()。 (A)单击工具栏上的“添加工程”按钮 (B)在工程资源管理器窗口上单击右键,在弹出的快捷菜单中选择“新建工程”选项 (C)选择“文件”菜单中的“新建工程” (D)选择“视图”菜单中的“新建工程” 7. 为了把窗体上的某个控件变为活动,应执行的操作是()。 (A)单击窗体的边框(B)单击该控件的内部 (C)双击该控件(D)双击窗体 8. 确定一个控件在窗体上的位置的属性是()。 (A)Width和Height (B)Width或Height (C)Top和Left (D)Top或Left 9. 确定一个窗体或控件的大小的属性是()。 (A)Width和Height (B)Width或Height (C)Top和Left (D)Top或Left 10. 对于窗体,下面()属性可以在程序运行时进行设置。 (A)MaxButton (B)BorderStyle (C)Name (D)Left 11. 要使Print方法在Form_Load事件中起作用,要对窗体的()属性进行设置。 (A)BackColor (B)BorderStyle (C)AutoRedraw (D)Caption 12. 要使标签控件显示时不覆盖其背景内容,要对()属性进行设置。 (A)BackColor (B)BorderStyle (C)ForeColor (D)BackStyle 13. 要使对象看得见而不可操作,要对()属性设置。 (A)Enabled (B)Visible (C)BackColor (D)Caption 14. 文本框没有()属性。

第三章java程序设计教案

课堂教学教案 教师姓名:课程名称:Java程序设计授课时数:2 第3次课

附录3 3.1 Java程序的构成 3.2 数据类型、变量与常量 ●语言成分 1. 关键字: 由Java语言定义的,具有特定含义的单词 2. 标识符: 以字母开头的字母数字序列 标识符命名规则①②③④⑤ 3.分隔符 3.2.1 基本数据类型 1. 什么是数据类型 2. 数据类型分类 1)基本数据类型 2)引用数据类型 3. 基本数据类型 3.2.2 变量与常量 ?Java 标识符 ?类名(接口名)—名词 第一字母大写,每一单词首字母大写。 例:AccountBook ?方法名—动词 第一字母小写,每一单词首字母大写。 例:balanceAccount() ?变量名—名词 第一字母小写,每一单词首字母大写。 ?常量名 全部大写,单词间用下划线分开 . 例:HEAD_COUNT ?Java标识符要区分大小写

标识符中的合法字符:字母、数字、_ 、$ (非数字开头) ?数据类型与说明语句 ?Java程序中所处理的数据是将各种数据类型实例化后的数据。 ?数据类型实例化的方法:说明语句 ?实例化数据的两种形式:变量与常量 ?数据类型与说明语句 ?基本数据类型说明语句例:(同时给变量赋初值) ?Java几乎是将字符串视为基本数据类型 ?常数表示法(字面值) ?布尔常数true false ?整型常数 一般常量:32bit 长整型常量:64bit (88L) 十进制数:非0开头的正负整数105,-23 八进制数:0开头的正负整数017(15), -023(-19) 十六进制:0x开头的正负整数0x2F(47),-0xa8(-168) ?浮点常数 一般浮点常量(32bit) 3.14F 6.18E3F 双精度浮点数(64bit ) 7.56D 6.02E23 (D可省) 3.3 表达式 ?数据类型转换 ?同种数据类型转换: 短类型→ 长类型:默认(系统自动转换) 长类型→短类型:强制 ?布尔型与其他数据类型之间不能强制类型转换 ?Wrapper类 ?Wrapper类例-Integer ?属性 static int MAX_VALUE 返回int型数据的最大值 static int MIN_VALUE 返回int型数据的最小值 ?构造方法 Integer(int value) Integer(String s) ?方法 int intValue() double doubleValue() String toString() static String toString(int i) static Int parseInt(String s) static Integer valueOf(String s) ?类型转换-利用类/对象方法 ?利用类方法(静态方法) Integer.parseInt("23") //返回整数23 Integer.toString(23) //返回字符串"23" Double.parseDouble("12.3")

C语言 第三章 顺序结构程序设计期末测试习题与答案

C语言第三章顺序结构程序设计期末测试习题与答案 1、关于算法的描述,下列正确的是 ( )。 A.一个算法可以没有输入,但必须有输出 B.一个算法必须要有输入,但可以没有输出 C.一个算法有几个输入就必须有几个输出 D.一个算法如果没有输入,则此算法是错误的 参考答案:A 2、算法的每一个步骤都应是确切定义的,不能有二义性,相同的输入应该得到相同的输出,这是算法的 ( )。 A.确定性 B.可行性 C.正当性 D.有穷性 参考答案:A 3、以下程序片段: int x=2,y=3; printf(); 的运行结果是 ( )。 A.输出为:x=2 B.输出为:x=2,y=3 C.输出为:y=3 D.什么都不输出 参考答案:D

4、已知a,b,c为int型变量,若从键盘输入:2,3,4<回车>,使a的值为2,b的值为3,c的值为4,以下选项中合法的输入语句是 ( )。 A.scanf(“a=%d,b=%d,c=%d”,&a,&b,&c); B.scanf(“%dV%dV%d”,&a,&b,&c); C.scanf(“%d,%d,%d”,&a,&b,&c); D.scanf(“%2d%3d%4d”,a,b,c); 参考答案:C 5、若int a,b; double x; 以下不合法的scanf函数调用语句是 ( )。 A.scanf(“%3d%*3d%lf”,&a,&b,&x); B.scanf(“%ld%lo%o”,&a,&b,&x); C.scanf(“%o%f%lo”,&a,&b); D.scanf(“%d%o%f”, &a,&b,&x); 参考答案:C 6、有输入语句:scanf(“a=%db=%dc=%d”,&a,&b,&c);为使变量 a的值为1,b的值为3,c的值为5,则正确的数据输入方式是 ( )。 A.a=1b=3c=5↙ B.1,3,5↙ C.135↙ D.a=1 b=3 c=5↙ 参考答案:A 7、putchar ( )函数可以向终端输出一个 ( )。 A.整型变量表达式值 B.字符或字符型变量值 C.字符串 D.实型变量值

第三章 程序设计基础

3.1编程序:用getchar函数读入两个字符给c1,c2,然后分别用putchar和printf函数输出这两个字符。并思考以下问题:(1)变量c1,c2应定义为字符型或整型?或两者皆可?(2)要求输出C1和C2值的ASCII 码,应如何处理?用putchar函数还是printf函数?(3)整型变量与字符型变量是否在任何情况下都可以互相替代? 1、#include main() {char c1,c2; c1=getchar(); c2=getchar(); putchar(c1); putchar(c2); p rintf(“\n”); p rintf(“%c,%c\n”,c1,c2);} 运行结果: 请输入两个字符C1,C2: AB 用putchar语句输出结果为: AB 用printf语句输出结果为: A,B 回答思考问题: C1和C2定义为字符型。 在printf函数中用%d格式符输出。即:printf(“%d,%d\n”,C1,C2); 字符变量在计算机内占一个字节,而整型变量占两个字节,因此整型变量在可输出字符的范围内(ASCII 码为0-255之间的字符)是可以与字符数据互相转换的。如果整数在此范围外,则不能代替。 3.2 #include "stdio.h" main() { int hh,mm,ss; int result; printf("input the time(hh:ss:mm):"); scanf("%d:%d:%d",&hh,&mm,&ss); result=hh*3600+mm*60+ss; printf("%d",result); } 3.3设某职工应发工资x元,试求各种票额钞票总张数最少的付款方案。 分析:可以从最大的票额(100元)开始,算出所需的张数,然后在剩下的部分算出较小票额的张数,直到最小票额(1元)。

第2章 简单的C#程序设计 习题

第2章简单的C#程序设计 一.选择题 1.C#中程序的入口方法是【】方法。 A) main B) Main C) begin D) Program 2.假设变量x的值为25,要输出x的值,下列正确的语句是【】。 A) System.Console.writeline(“x”);B) System.Console.WriteLine(“x”); C) System.Console.WriteLine(“x={0}”,x);D) System.Console.WriteLine(“x={x}”); 3.要退出应用程序的执行,应执行下列的【】语句。 A) Application.Exit(); B) Application.Exit; C) Application.Close(); D) Application.Close; 4.关于C#程序的书写,下列不正确的说法是【】。 A) 区分大小写 B) 一行可以写多条语句 C) 一条语句可以写成多行 D) 一个类中只能有一个Main()方法,因此多个类中可以有多个Main()方法。 5.公共语言运行库即【】。 A) CRL B) CLR C) CRR D) CLS 6..NET平台是一个新的开发框架,【】是.NET的核心部分。 A) C# B) .NET Framework C) https://www.sodocs.net/doc/9417610708.html, D) 公共语言运行库 7.C#应用程序项目文件的扩展名是【】。 A) csproj B) cs C) sln D) suo 8.C#应用程序解决方案文件的扩展名是【】。 A) csproj B) cs C) sln D) suo 9.运行C#程序可以通过按【】键实现。 A) F5B) Alt+F5 C) Ctrl+F5 D) Alt+Ctrl+F5 10.C#语言中类模块代码文件的扩展名是【】。 A) csproj B) cs C) sln D) suo 11.构建桌面应用程序需要.NET提供的类库是【】。 A) https://www.sodocs.net/doc/9417610708.html, B) Windows Form C) XML D) https://www.sodocs.net/doc/9417610708.html, 12.C#中导入某一命名空间的关键字是【】。 A) use B) using C) import D) include 13..NET Framework将【】定义为一组规则,所有.NET语言都应遵守这个规则,才能创建可以与其他语言互操作的应用程序。 A) JIT B) CLR C) MSIL D) https://www.sodocs.net/doc/9417610708.html, 14.在https://www.sodocs.net/doc/9417610708.html,中,在【】中可以查看当前项目的类和类的层次信息。 A) 解决方案资源管理器B) 类视图窗口 C) 对象浏览器窗口D) 属性窗口 15.在.NET Framewor中,MSIL是指【】 A) 接口限制B)中间语言C) 核心代码D) 类库16.【】是独立于CPU的指令集,它可以被高效地转换为本机机器语言。

jsp编程基础第三章习题

第三章JSP语法基础习题 一、选择题 1.JSP的编译指令标记通常是指:() A)Page指令、Include指令和Taglib指令 B)Page指令、Include指令和Plugin指令 C)Forward指令、Include指令和Taglib指令 D)Page指令、Param指令和Taglib指令 2.可以在以下哪个()标记之间插入Java程序片?() A)<% 和%> B)<% 和/> C) D)<% 和!> 3.下列哪一项不属于JSP动作指令标记?() A) B) C) D) 4.JSP的Page编译指令的属性Language的默认值是:() A)Java B)C C)C#D)SQL 5.JSP的哪个指令允许页面使用者自定义标签库?() A)Include指令B)Taglib指令 C)Include指令D)Plugin指令 6.可以在以下哪个()标记之间插入变量与方法声明?() A)<% 和%> B)<%!和%> C) D)<% 和!> 7.能够替代<字符的替代字符是()?() A)< B)> C)< D)  8.动作标记中,scope的值不可以是()。 A)page B)request C)session D)response 9.下列()注释为隐藏型注释。() A) B) C)<%-- 注释内容--%> D)] --> 10.下列变量声明在()范围内有效。() <%! Date dateTime; int countNum; %> A)从定义开始处有效,客户之间不共享 B)在整个页面内有效,客户之间不共享 C)在整个页面内有效,被多个客户共享

智慧树知到程序设计基础(C语言)测试第三章单元测试参考答案

智慧树知到程序设计基础(C语言)测试第三章单元测试参考答案 ?总题数: 10 1 【判断题】 (10分) 表达式25/3%3的值为2. A.错 B.对 正确 本题总得分10分 2 【判断题】 (10分) 若有定义:int y=2; 则计算表达式y+=y后的y值是2() A.对 B.错 正确 本题总得分10分 3 【单选题】 (10分) 下列不正确的叙述是()。 A.在C语言程序中,SUM和sum是两个不同的变量。 B.若a和b类型相同,在计算了赋值表达式a=b后b中的值将复制到a 中,而b中的值不变。 C.在C语言程序中,%运算符的优先级高于/ 运算符。 D.在C语言程序中,进行赋值运算时,先将右侧表达式的值转化左侧变 量的类型再赋给变量。 正确 本题总得分10分 4 【单选题】 (10分) 在C语言中,要求运算对象必须是整型的运算符是()。

A.- B.% C.* D./ 正确 本题总得分10分 5 【单选题】 (10分) 下列选项中正确的定义语句是()。 A.double a ; b; B.double a=7,b=7; C.double , a , b; D.double a=b=7; 正确 本题总得分10分 6 【单选题】 (10分) 输入一个3位正整数n,分别输出n的个位a、十位b、百位c。下面程序的语句填空应为:() #include int main( ) { int n,a,b,c; scanf("%d",&n); a=____;; b=n/10%10; c=n/100; printf("a=%d b=%d c=%d",a,b,c); return 0; }

C++程序设计教程 课后答案第三章 李秉璋

1.概念填空题 1.1一个C++程序是由一个或多个函数所组成,即使是最简单的程序,也必须有一个main函 数。该函数是程序执行的起点和终点。C++中,函数不允许嵌套定义,允许嵌套调用。 1.2 函数执行过程中通过return 语句将函数值返回,当一个函数不需要返回值,需要使用 void 作为类型名。 1.3 在C++中,如果函数定义在后,调用在先,需要原型声明。其格式和定义函数时的函 数头的形式基本相同,但参数表中形参不是必须的,同时必须以; 结尾。 1.4 递归程序分两个阶段执行递推,回归。 1.5 函数名相同,但对应形参表不同的一组函数称为重载函数,参数表不同是指类型不 同或参数个数不同。 1.6 内联函数的展开、重载函数的确定均在编译阶段进行。 1.7 静态局部变量存储在全局数据区,在程序运行时候建立,生命期为整个程序 ,如定义 时未显式地初始化,则其初值为0。局部变量存储在栈区,在块或函数开始运行时候建立,生命期为块或函数,如定义时未显式地初始化,则其初值为随机数。 2.简答题 2.1 函数的作用是什么?如何定义函数?什么叫函数原型? 2.2 什么叫形式参数?什么叫实际参数?C++函数参数有什么不同的传递方式?请写一个验 证程序说明。 2.3 C++函数通过什么方式传递返回值?若返回引用类型时,是否可以返回一个算术表达式? 为什么? 2.4 变量的生存期和变量作用域有什么区别?请举例说明。 2.5 静态局部变量有什么特点?编写一个应用程序,说明静态局部变量的作用。 3.选择题 3.1正确的函数定义形式为(A )。 A.void fun(void) B.double fun(int x;int y) C.int fun(int=0,int); D.double fun(int x,y) 3.2 C++语言中规定函数的返回值的类型是由(D)。 A.return语句中的表达式类型决定 B.调用该函数时的主调函数类型决定 C.调用该函数时系统临时决定 D.定义该函数时所指定的函数类型决定 3.3 若有函数调用语句:fun(a+b,(x,y),(x,y,z));此调用语句中的实参个数为(A)。 A.3 B.4 C.5 D.6 3.4 C++中,关于默认形参值,正确的描述是(C)。 A.设置默认形参值时,形参名不能缺省 B.只能在函数定义时设置默认形参值 C.应该先从右边的形参开始向左边依次设置 D.应该全部设置 3.5 若同时定义了如下函数,fun(8,3.1)调用的是下列哪个函数(D)。 A.void fun(float,int) B.void fun(double,int)

《C语言程序设计》教案第三章程序的控制结构—顺序结构

《C语言程序设计》课程教案表

printf(“%d,%o”,a,a); 输出结果为:-1,177777 这是因为-1在内存中以补码形式存放(见图3-2)。八进制数为从低位开始,以三位一组划分为一个八进制数。 3)x格式符。以十六进制数无符号形式输出整数。 例如: int a=-1; printf(“%x,%o,%d”,a,a,a); 输出结果为:ffff,177777,-1 十六进制数为从低位开始,见图3-2,以四位一组划分为一个数。 4)u格式符。以十进制数无符号形式输出整数。一个有符号的(int)型数据可以用%d格式输出,也可以用%u格式输出。要注意两类数据的取值范围大小。 例如:无符号数据的输出。 main() { unsigned int x=65535; int y=-1; printf(“x=%d,%o,%x,%u\n”,x,x,x,x); printf(“y=%d,%o,%x,%u\n”,y,y,y,y); } 运行结果为: x=-1,177777,ffff,65535 y=-1,177777,ffff,65535 即-1的二进制形式以无符号形式输出时为整数65535。 5)c格式符。用来输出一个字符。 例如:char x=’A’; printf(“%c,%d\n”,x,x); 运行结果为:A,65 可以看出,一个范围在0~255的整数,既可以用%d格式输出,也可以用%c格式输出。输出该整数或者整数对应ASCII的字符。 6)s格式符。用来输出一个字符串,该格式有以下用法: ①%s例如:printf(“%s”,”HELLO”);运行结果为:HELLO ②%±ms,如果%ms字符串的实际宽度小于m,右对齐,左端补空格,%-ms,字符串左对齐,右端补空格;否则,不受m限制,输出实际宽度。 ③%±m.ns,若%m.ns取字符串左端n个字符,输出在m列的右端,左端补空格;%-m.ns,取字符串左端n个字符,输出在m列的左侧,右侧补空格;若m

ASP。net程序设计基础教程第2版03-第三章-课后习题答案.doc

1 / 1 第3章 https://www.sodocs.net/doc/9417610708.html, 的内置对象 3.8.1 作业题 1.使用Response 对象,在Default.aspx 上输出系统当前日期和时间。如图1所示: 图1 作业题3-1 2. 创建一个网页Default.aspx, 用户输入姓名、年龄, 如图2所示。单击“确定”按钮后,页面跳转到Welcome.aspx,并显示用户刚才输入的信息,如图3所示。要求只能采用Response 和Request 对象,页面跳转采用GET 请求。 图2 Default.aspx 图3 Welcome.aspx 3. 实现不同身份的用户,登录后进入不同的页面。在Default.aspx 的下拉列表中只有 admin 和user 选项 ,如图4所示。根据登录的用户名,分别进入Admin.aspx 和 User.aspx,并且显示如图5、图6所示的欢迎信息。要求采用Session 对象来实现。 图 4 Default.aspx 图 5 Admin.aspx 图 6 User.aspx 4.在作业题3的基础上分别统计admin 和user 的访问量,要求用Application 对象来实现。如图7——图9所示 图7 Default.aspx 图8 Admin.aspx 图9 User.aspx 5. 如图所示 ,在默认主页输入昵称,进入网站中的另一个页面NewPage, 显示欢迎信息和客户端IP 地址。若是第一次访问,用cookie 存储本次访问的时间。下次再访问时,显示上次访问的时间。要求采用server 对象进行页面跳转并传递参数。如图10——图12所示。 图10 输入昵称 图11 第一次访问时的欢迎信息 图12 非第一次访问时的欢迎信息 见“课后习题源代码”文件夹下的“homework3-1——homework3-5”

第三章:程序设计

且要求m>n。 P=m!/(n!(m-n)!),例如,m=12,n=8时,运行结果为495.000000。 注意:部分源程序给出如下。 请勿改动main函数和其他函数中的任何内容,仅在函数fun的注释语句之间填入所编写的若干语句。 试题程序:*/ #include #include float fun (int m, int n) { /************Begin**********/ int i; int a=1,b=1,c=1; double p; for(i=1;i<=m;i++) a=a*i; for(i=1;i<=n;i++) b=b*i; for(i=1;i<=(m-n);i++) c=c*i; p=a/(b*c); return p; /************End************/ } int main () { FILE *wf,*in; int m,n; printf ("p=%f\n",fun (12,8) ) ; /******************************/ in=fopen("in72.dat","r"); wf=fopen("out72.dat","w"); while(!feof(in)) { fscanf(in,"%d %d\n",&m,&n); fprintf (wf,"%f\n",fun(m,n)); } fclose(wf); fclose(in); /*****************************/ return 0; }

入。S=1+1/(1+2)+1/(1+2+3)+…+1/(1+2+3+…+n) 例如,若n的值为11时,函数的值为1.833333。 注意:部分源程序给出如下。 请勿改动main函数和其他函数中的任何内容,仅在函数fun的花括号中填入所编写的若干语句。 试题程序: */ #include #include #include float fun(int n) { /***********Begin*************/ int i; double a=0; double s=0; for(i=1;i<=n;i++) { a=a+i; s=s+(1/a); } return s; /***********End***************/ } int main() { FILE *wf,*in; int n; float s; printf("\nPlease enter N: "); scanf("%d",&n); s=fun(n); printf("The result is:%f\n " , s); /******************************/ in=fopen("in001.dat","r"); fscanf(in,"%d",&n); wf=fopen("out.dat","w"); fprintf (wf,"%f",fun(n)); fclose(wf); /*****************************/ return 0; }

第三章 顺序结构程序设计

第三章顺序结构程序设计 一、结构化程序设计: 1.程序是命令的有序集合,命令执行的顺序即程序的结构。 2.在结构化程序设计中,把所有程序的逻辑结构归纳为三种:顺序结构、选择结构和循环 结构。 3.结构化程序设计概述: 结构化程序设计的原则:自顶向下→逐步细化→模块化设计(所谓模块化设计,就是按模块组装的方法编程。把一个待开发的软件分解成若干个小的简单部分,称为模块。)→结构化编码(编码俗称编程序)。 二、算法: 1. 算法就是一种在有限的步骤内解决问题或完成任务的方法。 2. 计算机程序就是告诉计算机如何去解决问题或完成任务的一组详细的、逐步执行的指令 集合。 3. 数据时操作的对象,操作的目的是对数据进行加工处理,以得到期望的结果。 4. 算法的流程成图表示法: 起止框:输入/输出操作:判断框:流程线: 5. 基本算法: 累加,累乘,求最大或最小值。 穷举:穷举就是一种重复型算法。它的基本思想是对问题的所有可能状态一一测试,直到找到解或将全部可能状态都测试过为止。 迭代:是一个不断用新值取代变量的旧值,由旧值递推出变量的新值过程。 递归:算法自我调用的过程,一种算法是否称为递归,关键取决于该算法定义中是否有它本身。 排序:是在待排序记录中按照一定次序排列数据的过程。 查找:是在数据列表中确定目标所在位置的过程。(顺序查找:可在任何列表中查找,对半查找:要求列表是有序的) 三、C语句概述: 1. 表达式语句:在各种表达式后加一个分号构成的语句。 2. 函数调用语句:在函数调用的一半形式后加一个分号构成的语句,其作用是完成特定的 功能。一般形式:函数名(实参表); 3. 空语句:只有一个分号的语句称为空语句,其形式如:;空语句在语法上占据一个语 句的位置,但是它不具备任何操作功能。 4. 复合语句:用一对花括号“{}”将多条语句括起来构成的语句体。形式{语句组} 5. 控制语句:C程序设计中用来构成分支结构和循环结构并完成一定控制功能的语句。 ●条件语句:if …else。 ●多分支选择语句:switch ●当型循环语句:while,for ●直到循环型循环语句:do…while ●终止本次循环语句:continue ●终止整个循环或跳出switch语句:break ●无条件转移语句:goto ●函数返回语句:return 四、输入/输出语句:

相关主题