Visual C++ 程序设计 张 淼 哈尔滨工业大学(威海)计算机学院.

Similar presentations


Presentation on theme: "Visual C++ 程序设计 张 淼 哈尔滨工业大学(威海)计算机学院."— Presentation transcript:

1 Visual C++ 程序设计 张 淼 哈尔滨工业大学(威海)计算机学院

2 课程安排 授课24学时 实验16学时 考试: 平时成绩(作业+出勤) 15% 实验成绩 15% 考试 70% 计算机科学与技术学院 张淼

3 Visual C++到底是什么? 计算机科学与技术学院 张淼

4 C语言自产生以来,出现了许多C语言的集成开发环境,如Turbo C、Borland C等,在这些集成开发环境中,程序员可以将代码的编辑、编译、连接、执行和调试过程全部完成。
回顾 计算机科学与技术学院 张淼

5 面向对象的程序设计语言C++产生之后,又出现了Borland C++、C++ Builder和Visual C++等针对C++语言的集成开发环境。虽然这些C++集成开发环境是针对C++语言的,而且增加了许多其它特性,然而单就C程序来讲,如果符合ANSI C的标准,在这些集成环境中都能够很好地编译和连接。 计算机科学与技术学院 张淼

6 VC++主要是用来开发Windows应用程序的。Windows程序设计不同于DOS下的程序设计,它是一种事件驱动的程序设计模式,主要是基于消息的。
计算机科学与技术学院 张淼

7 Visual C++ Windows环境下最主要的应用开发系统之一。 C++语言的可视化集成开发环境。
强大的调试功能为大型复杂软件的开发提供了有效的排错手段。 计算机科学与技术学院 张淼

8 接使用Microsoft提供的MFC类库。
使用VC++进行Windows应用程序 设计时,有两种方法:一是使用 Platform SDK,另一种方法是直 接使用Microsoft提供的MFC类库。 计算机科学与技术学院 张淼

9 Platform SDK(software develop kit ):
使用C语言和Win32 API (Application Programming Interface,应用程序编程接口)函数进行编程。 MFC: 使用C++语言和MFC(微软基础类库)进行编程. 计算机科学与技术学院 张淼

10 Visual C++ Programming
MFC Programming 计算机科学与技术学院 张淼

11 SDK MFC 简单、易于理解 复杂,代码可读性差 工作量大、效率低 工作量小,效率高 计算机科学与技术学院 张淼

12 通过了解相对已“过时”的Win32 SDK编程,有助于理解以后的很多东西!
SDK编程方式易于理解 Windows 工作原理。 通过了解相对已“过时”的Win32 SDK编程,有助于理解以后的很多东西! 计算机科学与技术学院 张淼

13 C++/MFC程序设计必须跨越的四大技术障碍
Windows程序基本观念 Microsoft Foundation Classes(MFC)本身 Visual C++集成开发环境与各种开发工具(难度不高,但需熟练) 计算机科学与技术学院 张淼

14 课程安排 学时安排 课程内容 24学时 Windows编程基础 面向对象程序设计和C++语言基础(书1-3章)
3学时 面向对象程序设计和C++语言基础(书1-3章) 13学时 MFC(Microsoft Foundation Classes)(书4-9章) 8学时 计算机科学与技术学院 张淼

15 推荐书籍 C++ 编程思想(第2版) C++ Primer (第3版)中文版 Windows 程序设计(第5版)
Bruce Eckel,机械工业出版社 C++ Primer (第3版)中文版 Stanley B.Lippman,Josee Lajoie,中国电力出版社 Windows 程序设计(第5版) Charles Petzold,北京大学出版社 MFC Windows 程序设计(第2版) Jeff Prosise ,清华大学出版社 深入浅出MFC(第二版) 侯俊杰,华中科技大学出版社 Visual C++ 技术内幕 George Shepherd,David Kruglinski ,清华大学出版社 深入解析MFC (MFC Internals ) George Shepherd ,中国电力出版社 计算机科学与技术学院 张淼

16 第一部分 Windows编程基础 计算机科学与技术学院 张淼

17 Windows程序内部运行原理 SDK方式编写windows应用程序 Windows应用程序特性 计算机科学与技术学院 张淼

18 CH1.1Windows程序内部运行原理 目标:理解Windows程序的运行机制 掌握以下基本概念: 窗口 API 消息与消息机制 消息响应
句柄 计算机科学与技术学院 张淼

19 Windows应用程序,操作系统,计算机硬件输入输出设备之间的相互关系
消息队列 计算机科学与技术学院 张淼

20 API 操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应,也就是说,操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用(System call),这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API。 计算机科学与技术学院 张淼

21 API 什么是API? API的作用 Application Programming Interface
是Windows系统与Windows应用程序间的标准程序接口 Windows应用程序可以利用标准大量API函数调用系统功能 窗口管理函数:实现窗口的创建、移动和修改功能 系统服务函数:实现与操作系统有关的多种功能 图形设备(GDI)函数:实现与设备无关的图形操作功能 计算机科学与技术学院 张淼

22 Windows应用程序,操作系统,计算机硬件输入输出设备之间的相互关系
消息队列 操作系统 输入输出设备 计算机科学与技术学院 张淼

23 消息响应 操作系统能够感知输入设备的变化(事件),并且能够知道具体的事件情况,但操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。对事件作出反应的过程就是消息响应。 计算机科学与技术学院 张淼

24 消息 操作系统是怎样将感知到的事件传递给应用程序的呢?这是通过消息机制(Message)来实现的。操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序。 计算机科学与技术学院 张淼

25 消息 MSG结构定义如下: typedef struct tagMSG { HWND hwnd; UINT message;
WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; 计算机科学与技术学院 张淼

26 消息 消息结构体 - MSG 主消息:由事先定义好的消息名标识 附加消息:与主消息有关 wParam lParam
typedef struct tagMSG{ HWND hwnd; //窗口句柄 UINT message; //主消息 WPARAM wParam; //附加消息 LPARAM lParam; DWORD time; //送至队列的时间 POINT pt; //消息发送时屏幕光标的位置 } MSG, *PMSG; 计算机科学与技术学院 张淼

27 Windows窗口是Windows系统的一个内部数据结构的实例,由一个“窗口句柄”标识,Windows系统创建它并给它分配系统资源。
Windows Object是Windows系统的内部结构,通过一个句柄来引用; 计算机科学与技术学院 张淼

28 句柄(Handle) 句柄(HANDLE),资源的标识。
操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源。按资源的类型,又可将句柄细分成图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等等各种类型的句柄。操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄。 计算机科学与技术学院 张淼

29 从变量的类型区分变量的用途 int x,y; x=50; y=30;
typedef int WIDTH typedef int HEIGHT WIDTH x; HEIGHT y; //好处:我们从变量的类型上就可以知道x和y是用来表示宽度和高度。 计算机科学与技术学院 张淼

30 typedef struct tagPOINT { LONG x; LONG y; } POINT;
计算机科学与技术学院 张淼

31 对于MSG这个结构体来说,它里面包含的信息是相当丰富的,它包含了对于一个消息来说,它和哪一个窗口相关,这个消息本身是什么,这个消息的附加参数是什么,消息发生投递的时间是什么。我们拿到这样一个消息就像当全面了。那么我们就知道应该做什么样的处理。 计算机科学与技术学院 张淼

32 Windows应用程序,操作系统,计算机硬件输入输出设备之间的相互关系
消息队列 操作系统 输入输出设备 计算机科学与技术学院 张淼

33 消息队列 对于每一个应用程序,操作系统会给它建立一个消息队列。这个队列实际上是一个先进先出的缓冲区,通常是一个某种变量类型的数组。消息队列中的每个元素都是一条消息。 操作系统将生成的每个消息按先后顺序放进消息队列中。应用程序总是依次取走队列里的第一条消息。应用程序取走消息后便能够知道用户的操作和程序状态的变化,从而对特定的消息进行处理(编写代码)——消息响应。 计算机科学与技术学院 张淼

34 计算机科学与技术学院 张淼

35 CH1.2 SDK方式编写windows应用程序
目标:能够用SDK(Windows API+C)编写一个简单的windows应用程序,通过编写程序掌握Windows程序的运行原理及编写方式。为MFC的学习打下基础。 计算机科学与技术学院 张淼

36 程序执行的起点(入口函数)? main() WinMain() 计算机科学与技术学院 张淼

37 WinMain函数 Windows程序的入口函数 int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state ); 计算机科学与技术学院 张淼

38 匈牙利表示法 一种命名约定 匈牙利命名法是微软推广的一种关于变量、函数、对象、前缀、宏定义等各种类型的符号的命名规范。匈牙利命名法的主要思想是:在变量和函数名中加入前缀以增进人们对程序的理解。 为纪念Microsoft程序员Charles Simonyi所使用的命名约定 计算机科学与技术学院 张淼

39 窗口的创建 创建一个完整的窗口需要经过下面四个操作步骤: 设计一个窗口类; 注册窗口类; 创建窗口; 显示及更新窗口。
计算机科学与技术学院 张淼

40 创建窗口 - 第一步 窗口类的定义- WNDCLASS typedef struct tagWNDCLASS{
UINT style; //窗口类风格 WNDPROC lpfnWndProc; //窗口过程函数 int cbClsExtra; //在类结构中预留的空间 int cbWndExtra; //在Windows内部预留空间 HINSTANCE hInstance; //程序实例句柄 HICON hIcon; //窗口的图标 HCURSOR hCursor; //光标句柄 HBRUSH hbrBackground; //背景色 LPCTSTR lpszMenuName; //窗口类菜单 LPCTSTR lpszClassName; //窗口类名 } WNDCLASS, *PWNDCLASS; 计算机科学与技术学院 张淼

41 窗口类的类型 在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。当变量中的某几位同时为1时,就表示同时具有几种特性的组合。一个变量中的哪一位代表哪种意义,不容易记忆,所以我们经常根据特征的英文拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0。 计算机科学与技术学院 张淼

42 如果我们希望某一变量的数值既有CS_VREDRAW特性,又有CS_HREDRAW特性,我们只需使用二进制OR(|)操作符将他们进行或运算相组合,如style=CS_VREDRAW | CS_HREDRAW | CS_NOCLOSE。如果我们希望在某一变量原有的几个特征上去掉其中一个特征,用取反(~)之后再进行与(&)运算,就能够实现,如在刚才的style的基础上去掉CS_NOCLOSE特征,可以用style & ~CS_NOCLOSE实现。 计算机科学与技术学院 张淼

43 窗口过程函数 窗口过程函数用来对消息进行处理 第二个成员变量lpfnWndProc指定了这一类型窗口的过程函数,也称回调函数。
计算机科学与技术学院 张淼

44 回调函数 当应用程序收到给某一窗口的消息时(还记得前面讲过的消息通常与窗口相关的吗?),就应该调用某一函数来处理这条消息。这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。 计算机科学与技术学院 张淼

45 对于一条消息,操作系统到底调用应用程序中的哪个函数(回调函数)来处理呢?操作系统调用的就是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。 计算机科学与技术学院 张淼

46 LoadIcon LoadIcon的作用是在应用程序中加载一个窗口图标, 其原型为: HICON LoadIcon(
HINSTANCE hInstance, LPCTSTR lpIconName ); 图标资源所在的模块句柄, NULL则使用系统预定义图标 图标资源名或系统 预定义图标标识名 计算机科学与技术学院 张淼

47 LoadCursor LoadCursor的作用是在应用程序中加载一个窗口光标, 其原型为: HCURSOR LoadCursor(
HINSTANCE hInstance, LPCTSTR lpCursorName ); 光标资源所在的模 块句柄,NULL则使 用系统预定义光标 光标资源名或系统 预定义光标标识名 计算机科学与技术学院 张淼

48 GetStockObject GetStockObject的作用是获取笔、画刷、调色板、字体的句柄。应用程序调用函数GetStockObject获取系统提供的背景刷。其原型为: HGDIOBJ GetStockObject( int fnObject // type of stock object ); 计算机科学与技术学院 张淼

49 创建窗口 - 第二步 注册窗口类 - RegisterClass
ATOM RegisterClass( CONST WNDCLASS *lpWndClass ); 计算机科学与技术学院 张淼

50 创建窗口 - 第三步 创建窗口实例- CreateWindow HWND CreateWindow (
LPCTSTR lpszClassName,//窗口类名 LPCTSTR lpszTitle, //窗口标题名 DWORD dwStyle, //创建窗口的样式 int x,y, //窗口左上角坐标 int nWidth,nHeight, //窗口宽度和度高 HWND hwndParent, //该窗口的父窗口句柄 HWENU hMenu, //窗口主菜单句柄 HINSTANCE hInstance, //创建窗口的应用程序当前句柄 LPVOID lpParam //指向一个传递给窗口的参数值的指针 ) 计算机科学与技术学院 张淼

51 窗口样式 计算机科学与技术学院 张淼

52 创建窗口 - 第四步 显示窗口 - ShowWindow
BOOL ShowWindow( HWND hWnd,     int nCmdShow ); 计算机科学与技术学院 张淼

53 创建窗口 - 第五步 刷新窗口 - UpdateWindow 显示窗口后,应用程序调用UpdateWindow更新并绘制用户区,
BOOL UpdateWindow( HWND hWnd ); 显示窗口后,应用程序调用UpdateWindow更新并绘制用户区, 并发出WM_PAINT消息。 UpdateWindow(hwnd); 计算机科学与技术学院 张淼

54 GetMessage 其中函数GetMessage形式为: GetMessage( lpMSG, //指向MSG结构的指针 hwnd,
从消息队列中读取 一条消息,并将消 息放在MSG结构中 返回零值,即检索 到WM_QUIT消息,程 序结束循环并退出 其中函数GetMessage形式为: GetMessage( lpMSG, //指向MSG结构的指针 hwnd, nMsgFilteMin, //用于消息过滤的最小消息号值 nMsgFilterMax //用于消息过滤的最大消息号值 计算机科学与技术学院 张淼

55 从消息队列中获取消息 翻译消息和派遣消息 GetMessage TranslateMessage DispatchMessage
将消息传递给窗口过程进行处理 Windows 产生的消息 计算机科学与技术学院 张淼

56 计算机科学与技术学院 张淼

57 排队消息 非排队消息 Windows系统 计算机科学与技术学院 张淼 应用程序 WinMain ( ) WndProc case 1
Default 应用程序 WinMain ( ) 消息 循环 WndProc DispatchMessage DefWindowProc 检索到 的消息 GetMessage 排队消息 WM_KEYDOWN WM_KEYUP WM_MOUSEMOVE WM_LBUTTONDOWN WM_QUIT ... 非排队消息 WM_CREATE WM_DESTROY WM_SIZE WM_PAINT 应用程序的 消息队列 回调 Windows系统 计算机科学与技术学院 张淼

58 窗口过程 窗口过程函数用来对消息进行处理 LRESULT CALLBACK WndProc ( HWND hWnd,
UINT message, WPARAM wParam, LPARAM lParam ); 计算机科学与技术学院 张淼

59 LRESULT CALLBACK WndProc( HWND hwnd, UINT messgae,
窗口函数的一般形式如下: LRESULT CALLBACK WndProc( HWND hwnd, UINT messgae, WPARAM wParam,LPARAM lParam ) { … switch(message) ∥ message为标识的消息 { case … break; case WM_DESTROY: PostQuitMessage(0); default: return DefWindowProc(hwnd,message,wParam,lParam); } return(0); 在消息处理程序段中一般都有对WM_DESTROY的处理,该消息是关闭窗口时发出的。它向应用程序发出WM_QUIT消息,请求退出处理函数: void PostQuitMessage(int nExitCode) //nExitCode为应用程序的退出代码 为未定义处理过程的消息提供缺省处理 计算机科学与技术学院 张淼

60 程序执行流程 否 进行程序初始化 创建窗口注册窗口函数 用GetMessage取消息 是 处理消息 退出消息 程序从WinMain开始
退出程序 拦截消息 默认处理 计算机科学与技术学院 张淼

61 格式化字符串函数 一个常用的格式化字符串的函数 printf stdio.h char szChar[20];
sprintf(szChar,"char is %d",wParam); 计算机科学与技术学院 张淼

62 MessageBox函数 用于显示信息 int //返回用户所选按钮代表的数值 MessageBox ( HWND hWnd, //窗口句柄
LPCTSTR lpText, //消息框主体显示的文本 LPCTSTR lpCaption, //消息框标题栏显示文本 UINT uType //代表消息框风格的常数组合 ) ; 计算机科学与技术学院 张淼

63 消息框风格 以MB_开始的常数组合代表消息框的风格 多种风格之间用“|”分隔 #include <windows.h>
int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) { if(IDOK == MessageBox( NULL, "是否退出本程序?", "退出", MB_OKCANCEL|MB_ICONQUESTION)) MessageBox(NULL,"再见!","退出",MB_OK); return 0; } 计算机科学与技术学院 张淼

64 设备描述表( Device context )
设备描述表(设备上下文):系统内部维护的一个数据结构,该结构包含应用程序向输出设备输出时所需要的信息。是应用程序和输出设备之间的桥梁。 不允许 外设 应用 程序 Windows系统 提供 统一的设备环境DC 使应用程序和设备相连 计算机科学与技术学院 张淼

65 应用程序每一次文字图形操作均参照设备描述表中的属性进行。设备描述表描述了特定输出设备状态、文本和图形的绘图参数等;包括设备上可使用的输出区域、逻辑坐标系、选定何种绘图工具绘图、绘图前景色、填充色、字体、字体颜色、字的磅数等属性。 计算机科学与技术学院 张淼

66 GDI简介 GDI - 图形设备接口 要在窗口的客户区绘图,可以使用Windows的图形设备接口(GDI)函数
GDI的重要部分:设备描述表(DC) GDI内部保存的数据结构 设备无关性 每个GDI函数都需要将设备描述表句柄作为函数的第一个参数,例如 DrawText( HDC, LPCTSTR, int, LPRECT, UINT ); TextOut( HDC, int, int, LPCTSTR, int ); 计算机科学与技术学院 张淼

67 获取设备描述表:方法一 该方法适合于处理非WM_PAINT消息 这种获取设备描述表的一般方式如下 用GetDC获取设备描述表句柄
用ReleaseDC释放设备描述表句柄 这种获取设备描述表的一般方式如下 hdc = GetDC(hwnd) ; …… ReleaseDC(hwnd, hdc) ; 计算机科学与技术学院 张淼

68 WM_PAINT消息 什么时候向窗口过程发送WM_PAINT消息(窗口发生重绘) - “无效”的时候
调用UpdateWindow函数刷新窗口 窗口的全部或者部分失效的时候 窗口从无到有 手动刷新窗口 调用InvalidateRect函数 计算机科学与技术学院 张淼

69 Windows系统为每一个窗口建立了一个PAINTSTRUCT结构。该结构中包含了包围无效区域的一个最小矩形的结构RECT,应用程序可以根据这个无效区域执行更新的操作。 PAINTSTRUCT结构体不需要我们维护,是系统内部维护的结构体。 计算机科学与技术学院 张淼

70 获取设备描述表:方法二 该方法只适合于处理WM_PAINT消息 一般处理WM_PAINT的形式如下:
用BeginPaint“使无效窗口生效”并“返回设备描述表句柄” 用EndPaint释放设备描述表句柄 一般处理WM_PAINT的形式如下: Case WM_PAINT; hdc = BeginPaint(hwnd, &ps) ; …… EndPaint(hwnd, &ps) ; return 0 ; 计算机科学与技术学院 张淼

71 与输出文本相关的几个函数 设置文本颜色 设置文本背景色 设置文本显示模式 SetTextColor(HDC,COLORREF);
SetBkColor(HDC,COLORREF); 设置文本显示模式 SetBkMode(HDC,int iBkMode); iBkMode OPAQUE //不透明 TRANSPARENT //透明 计算机科学与技术学院 张淼

72 画线 画线 移动画笔 LineTo MoveToEx LineTo(HDC,int,int);
默认情况下从(0,0)开始画 移动画笔 MoveToEx MoveToEx(HDC,int,int,LPPOINT); 计算机科学与技术学院 张淼

73 画其它图形 画椭圆 画圆角矩形 画弧 画饼 画矩形 Ellipse(hdc,100,100,200,200);
RoundRect(hdc,……); 画弧 Arc(hdc,……); 画饼 Pie(hdc,……); 画矩形 Rectangle(HDC,int,int,int,int); 计算机科学与技术学院 张淼

74 画笔 - HPEN 创建画笔 将画笔选入DC 删除画笔资源 HPEN hPen;
hPen = CreatePen(PS_SOLID,2,RGB(255,0,0)); // 线形,线宽,颜色 SelectObject(hdc,hPen); DeleteObject(hPen); 计算机科学与技术学院 张淼

75 画刷 - HBRUSH 创建画刷 将画刷选入DC 删除画刷资源 HBRUSH hBrush;
hBrush = CreateSolidBrush(RGB(255,0,0)); SelectObject(hdc,hBrush); DeleteObject(hBrush); 计算机科学与技术学院 张淼

76 画刷 取得系统自带画刷 HBRUSH hBrush;
hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); //WHITE_BRUSH //GRAY_BRUSH //DKGRAY_BRUSH //…… 计算机科学与技术学院 张淼

77 获取设备描述表:方法三 获取整个窗口的设备描述表 用GetWindowDC获取整个窗口的设备描述表句柄
用ReleaseDC释放设备描述表句柄 HDC hdc; Hdc = GetWindowDC(hWnd); //使用设备描述表 …… ReleaseDC(hWnd,hdc); 参见工程0302 计算机科学与技术学院 张淼

78 获取设备描述表:方法四 获取整个屏幕的设备描述表 用CreateDC获取整个屏幕的设备描述表句柄 用DeleteDC释放设备描述表句柄
HDC hdc; Hdc = CreateDC("DISPLAY",NULL,NULL,NULL); //使用设备描述表 …… DeleteDC(hdc); 参见工程0302 计算机科学与技术学院 张淼

79 常用消息 WM_CREATE WM_PAINT WM_CLOSE WM_DESTROY WM_QUIT
由CreateWindow函数发出的消息 WM_PAINT 窗口客户区的全部或者部分“无效”时触发 WM_CLOSE 关闭窗口时产生的消息 WM_DESTROY 由DestroyWiodow函数发出的消息 WM_QUIT 由PostQuitMessage函数发出的消息 计算机科学与技术学院 张淼

80 进队消息与不进队消息 进队消息 不进队消息 窗口过程 进队消息是由Windows放入程序的消息队列中的 不进队消息直接发送给窗口过程
窗口过程是窗口的“消息中心” 计算机科学与技术学院 张淼

81 函数调用约定 函数调用是通过栈来实现的。在调用时,将参数值和调用后的返回地址压入所分配的栈空间中。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。在参数传递中,有两个很重要的问题必须得到明确说明: 当参数个数多于一个时,按照什么顺序把参数压入堆栈 函数调用后,由谁来把堆栈恢复原装(堆栈清除) 计算机科学与技术学院 张淼

82 函数调用约定 在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有: stdcall cdecl fastcall
thiscall naked call 计算机科学与技术学院 张淼

83 头文件 windows.h(所有windows程序都必须载入windows.h)
其中包含如下头文件 windef.h 基本类型定义 winnt.h 支持Unicode的类型定义 winbase.h 内核函数 winuser.h 用户接口函数 wingdi.h 图形设备接口函数 …… 计算机科学与技术学院 张淼

84 Windows程序分为“程序代码”和“用户接口(UI)资源”两大部分。程序代码使用编译器编译,用户接口资源使用资源编译器编译,最后两者使用连接器加上库文件可以生成可执行文件。
计算机科学与技术学院 张淼

85 动态链接库 自从微软推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是Windows操作系统的基础。
计算机科学与技术学院 张淼

86 动态链接库通常都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。
Windows API中的所有函数都包含在DLL中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。 计算机科学与技术学院 张淼

87 静态库和动态库 静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。 在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。 计算机科学与技术学院 张淼

88 这些资源可以使用VC++6.0提供的资源编辑器来实现创建和编辑。
用户接口(UI)资源是指功能菜单、对话框、程序图标、光标等资源,它是Windows应用程序界面的重要组成部分。资源的使用极大方便了应用程序界面的设计,也大大方便了应用程序与用户的交互。 这些用户资源的实际内容(二进制代码)是借助各种工具产生的。并以各种扩展名的文件存在,如.ico,.bmp,.cur等。程序员必须在一个所谓的资源描述文档(.rc)中描述它们。RC编译器读取RC文件的描述后,将所有用户接口资源文件集中制作一个.RES文件。 这些资源可以使用VC++6.0提供的资源编辑器来实现创建和编辑。 计算机科学与技术学院 张淼

89 资源 资源分类 资源ID 菜单 对话框 …… 资源的唯一标识 WM_COMMAND消息 wParam低16位传递菜单项资源ID
计算机科学与技术学院 张淼

90 Windows应用程序加载菜单的方法: 在窗口类的定义中加载菜单资源
wndcls.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1); 在创建窗口时加载菜单 HMENU hMenu; hMenu=LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)) 动态加载菜单:应用程序通过调用函数LoadMenu获取菜单句柄后,还可通过SetMenu动态加载菜单。 计算机科学与技术学院 张淼

91 CH1.3 windows应用程序特性 消息驱动机制 图形设备接口(GDI) 基于资源的程序设计 动态链接库 计算机科学与技术学院 张淼

92 总结: API 消息机制(MSG) 句柄 GDI 设备上下文(DC) 动态链接库 资源与资源ID 计算机科学与技术学院 张淼

93 作业: 1、理解Windows程序设计的基本概念 2、书后习题P118 (7) 计算机科学与技术学院 张淼

94 第二部分 面向对象程序设计和C++语言基础
计算机科学与技术学院 张淼

95 C++语言的产生 C++是从C语言发展演变而来的,首先是一个更好的C。 引入了类的机制,最初的C++被称为"带类的C"。
1983年正式取名为C++。C++语言的标准化工作从1989年开始,于1994年制定了ANSI C++标准草案。以后又经过不断完善,成为目前的C++。 计算机科学与技术学院 张淼

96 ◆ C++语言与C语言的本质区别——C++语言支持面向对象的程序设计
对象的程序设计语言 ※C++语言对C语言作了很 多改进 支持面向过程的程序设计语言 ◆ C++语言与C语言的本质区别——C++语言支持面向对象的程序设计 C语言仅支持面向过程的程序设计 学好C++语言的关键 掌握好面向对象的程序设计思想 计算机科学与技术学院 张淼

97 打着面向对象的幌子干着面向过程的勾当! C++ 编译器对 C 程序的兼容性造成了最底层的“过程勾当”。
计算机科学与技术学院 张淼

98 C++语言基础 C/C++语言概述(结构化程序设计部分) C++面向对象程序设计基础 C++面向对象程序设计进阶 计算机科学与技术学院 张淼

99 控制台(console)应用程序 DOS-like程序。
仍然可以调用部分的、不牵扯到图形使用者接口(GUI)的Win32 API(尤其是KERNEL.DLL模块所提供的那一部分),所以它可以使用Windows提供的各种高级功能,它可以产生进程,产生线程。甚至还可以在其中使用部分的MFC类(同样是与GUI没有关联的),例如处理数组、链表等数据结构的collection classes(CArray,CList)、与文件有关的CFile、CStdioFile。 Console窗口将成为其标准输入和输出装置。 以main函数为入口点。 计算机科学与技术学院 张淼

100 第一个C++程序 - HelloWorld 该程序在控制台输出一个字符串 #include <iostream.h>
void main() { cout<<"Hello World"<<endl; } 计算机科学与技术学院 张淼

101 第二个C++程序 - 还是HelloWorld
该程序在控制台输出一个字符串 #include <iostream> using namespace std; void main() { cout<<"Hello World"<<endl; } 计算机科学与技术学院 张淼

102 分析我们的Hello World #include <iostream> using namespace std;
预处理命令“#” using namespace std; 引用std名字空间 计算机科学与技术学院 张淼

103 C++标准库包含了C语言和针对C++提供的新功能。
C标准库提供的头文件,在C++标准库中也提供了相应的头文件,文件名的约定是:把C标准库头文件名的.h去掉,然后在文件名前加一个字母c。 现在很多的C++实现为了保证与C语言的兼容性,往往同时提供了两套头文件。值得注意的是:C++标准库的程序实体是在名空间std中定义的,在C++程序,如果包含的是相应的C++的头文件,则应通过名空间std来使用这些功能。但注意:在VC++6.0的C++标准中,从C标准包含进来的功能并没有在名空间std中定义。 计算机科学与技术学院 张淼

104 cout<<"Hello World"<<endl;
void main() 程序入口点 cout<<"Hello World"<<endl; cout:标准输出流对象,作用相当于printf << :插入符,将后面的内容插入到cout中,即输出到屏幕上。 endl:end of line 计算机科学与技术学院 张淼

105 输出 cout流输出 格式 功能 cout<<输出数据; 可以连续输出 cout可以输出字符、整数、浮点数和字符串
cout<<a<<b<<c<<endl; 功能 cout可以输出字符、整数、浮点数和字符串 类似printf函数 计算机科学与技术学院 张淼

106 输入 cin流输入 cin:标准输入流对象,作用相当于scanf >>:提取符,将用户输入的内容保存到后面的变量中。 格式 功能
可以连续输入(空白符:空格、横向制表符或回车符作为输入数据之间的分隔符) cin>>i>>j>>k; 功能 输入字符、整数、浮点数和字符串 类似scanf函数 计算机科学与技术学院 张淼

107 CH C++语言概述 基本C++语法格式 数据类型 运算符和表达式 基本语句 函数 指针和引用 计算机科学与技术学院 张淼

108 CH2.1.1 C++基本语法 C++是严格区分大小写的 C++是一种自由格式的语言
书写基本原则如下: 一行一般写一条语句。长语句一行写不下时可以一条写多行。分行原则是不能将一个单词分开。用双引号引用的字符串和宏定义一行写不下需要续行时需用续行符\。 在书写C++程序时,一般采用比较美观的“缩进”格式来书写。 计算机科学与技术学院 张淼

109 C++程序的注释 作用:提高程序的可读性;调试程序
分类 C++型注释——双斜线“//”注释(从“//”开始,本行中所有字符都被 作为注释处理。)可以嵌在C型注释中。 C型注释——“/*”表示注释的开始,“*/”表示注释的结束。在“/*”、 “*/”之间的字符均被视作注释。主要用于大块注释。 软件编码规范中说:“可读性第一,效率第二”。在程序中必须包含适量的注释,以提高程序的可读性和易维护性,程序注释一般占程序代码总量的20%-50%。 计算机科学与技术学院 张淼

110 C++中的标识符 标识符是用来表示变量名、函数名、数组名、类名等的有效字符序列。标识符命名要遵守合法性、有效性和易读性原则。
标识符的命名规则 由字母、数字和下划线组成 以字母或下划线作为第一个字符, 其后跟零个或多个字母、数字、下划线。 大写字母与小写字母分别代表不同的标识符 不能与关键字相同 举例 正确的 错误的 sum Dram_ rectangl e_myfri end 1_peo //起始字符非法 operator //是关键字 my $ //含有非法字符  标识符的命名规则 计算机科学与技术学院 张淼

111 C++的关键字(参见书P2) 计算机科学与技术学院 张淼

112 CH2.1.2 C++数据类型 数据是程序处理的对象,在C++语言中,所有的对象都属于某种数据类型。 基本数据类型 指针 数据类型的分类
派生类型 复合类型(数组、类、结构 体、共用体、枚 举) 指针 引用 计算机科学与技术学院 张淼

113 基本数据类型 整型(int) 字符型(char) 浮点型(实型)(float,double) 逻辑型(布尔型)(bool)
空值类型(void):用于函数和指针 计算机科学与技术学院 张淼

114 常量 在程序中,有些数据在运行期间是不允许改变的,我们称之为常量。 常量的两种表示形式 字面常量 符号常量 程序中直接写出常量值的常量。
计算机科学与技术学院 张淼

115 以0X或0x开头,由若干0~9的数字及A~F(大小写均可)的字母组成
1. 整型常量 整型常量就是以文字形式出现的整数,包括三种形式。各种表示形式前均可加上正或负号以表示它们值的正负,正号可以省略。 形式 组成 举例 备注 十进制 由若干个0~9的数字组成,但不能以0开头 1980,-50 L(或l)表示长整型,U(或u)表示无符号型,如果后缀包括L(或l)和U(或u)则表示无符号长整型。 八进制 以0开头,由若干0~7的数字组成 010,-0276 十六进制 以0X或0x开头,由若干0~9的数字及A~F(大小写均可)的字母组成 0x1Fa -0X4Ab 计算机科学与技术学院 张淼

116 默认数据类型为double型,如果加上后缀F(或f)则为float型,加上L(或l)则为long double型。
2. 实型常量 实型常量只能用十进制表示,共有两种表示形式 一般表示形式 指数表示形式 形式 组成 举例 备注 一般表示 又称小数表示形式。使用这种表示形式时,实型常量由整数和小数两部分组成。其中的一部分在实际使用时可省略,但不允许两部分同时省去。 10.2,10.,.2 默认数据类型为double型,如果加上后缀F(或f)则为float型,加上L(或l)则为long double型。 指数表示 表示很大或很小的实数,由尾数部分、字母E(或e)、指数部分三部分组成。尾数部分的表示和一般表示形式相同,指数部分必须是整数,但可正可负,当指数大于零时,正号可省。 1.2E20,.24e100, 计算机科学与技术学院 张淼

117 3. 字符常量 字符常量通常是指用单引号括起来的一个字符,其数据类型是char。其中单引号只是用来说明被它括起来的字符是字符常量,它本身不是字符常量的内容。如:′a′,′#′,′G′。 C++语言中,还有一种转义序列的表示方法可用来表示字符常量。 是用转义符号“\”后跟一个字符或一个ASCII码来表示一个单一字符。若“\”后跟一个ASCII码,则表示的是该ASCII码所代表的字符。 在这里ASCII码用八进制或十六进制表示,这里八进制和十六进制的表示与前面表示整型常量的方式不同,应无第一个“0”。例如′\X62′就表示字符′b′。 计算机科学与技术学院 张淼

118 3. 字符常量 在书写字符常量时,可显示字符通常用字符本身来书写,而不可显示字符(控制字符)和有专门用途的字符用转义序列表示。另外,对于下面的字符要特别注意: 反斜杠(\)应写成‘\\’ 单引号(‘)应写成‘\ ‘’ 双引号(“)应写成‘\”’或‘”’ 计算机科学与技术学院 张淼

119 由于双引号在字符串中用做定界符,所以,若字符串中需要出现双引号时,则必须采用转义序列。 注
4. 字符串常量 字符串常量又称字符串或串常量,是用一对双引号括起来的字符序列。例如:″xyz″, ″I am a student″,″This is a string″都是字符串。 由于双引号在字符串中用做定界符,所以,若字符串中需要出现双引号时,则必须采用转义序列。 I \0 C++语言中字符串的存储与字符不同,它在内存中的存放并不是简单地按串中字符的顺序排放,而是在末尾加上一个′\0′,表示字符串的结束。 字符串、字符及与其对应的存储形式 ″I″ ′I′ I 计算机科学与技术学院 张淼

120 字符串和宏定义一行写不下需要续行时需用续行符\。
“this is a string!\ mnp” #define CONTAINING_RECORD(address, type, field) ((type *)( \ (PCHAR)(address) - \ (UINT_PTR)(&((type *)0)->field))) 计算机科学与技术学院 张淼

121 符号常量 符号常量是指有名字的变量。在程序中使用常量时,除了采用字面常量形式外,还可以首先通过常量定义给常量取一个名字,并指定一个类型,然后,在程序中通过常量名来使用这些常量。 计算机科学与技术学院 张淼

122 两种声明符号常量的方法 用const声明符号常量 用#define声明符号常量 解释 C++语言中广泛采用的声明符号常量的方法
C语言中声明符号常量的方法。其中#define是预处理指令。缺点是不能显式声明常量的类型。 形式 const 数据类型 常量名=常量值; 或: 数据类型 const 常量名=常量值; 正确声明 const double pi= ; #define pi 错误声明 const double pi; //错误 pi= ; //错误 最后不允许加 “;” 计算机科学与技术学院 张淼

123 const定义的符号常量可以看作是一个只读变量,不可在程序中修改它的值。定义时必须初始化。
注意 const定义的符号常量可以看作是一个只读变量,不可在程序中修改它的值。定义时必须初始化。 bool类型的值true和false可以看成是C++语言预定义的两个符号常量,它们的值分别为1和0。 计算机科学与技术学院 张淼

124 变量 有些数据在程序运行过程中是可以改变的,我们称之为变量。
一个变量对应着计算机中的一组内存单元,这组内存单元在C++语言中用一个标识符来标识,即变量名。 计算机科学与技术学院 张淼

125 任何一个变量在被引用之前必须定义。与C语言不同的是C++可以在程序中随时定义变量,不必集中在执行语句之前。
一、变量的定义 任何一个变量在被引用之前必须定义。与C语言不同的是C++可以在程序中随时定义变量,不必集中在执行语句之前。 定义格式:<类型> <变量名列表>; 数据类型是指C++语言中的任一合法类型,每个变量都必须属于一种类型。变量名的命名应遵照标识符的命名规则。 在定义变量时,必须注意变量类型的选择。应该保证该变量中将要存储的值不突破该变量类型所能表示的最大值。 计算机科学与技术学院 张淼

126 程序中常需要对一些变量预先设置初值,这一过程称为初始化。在C/C++中,可以在定义变量时同时使变量初始化。
二、变量的初始化 程序中常需要对一些变量预先设置初值,这一过程称为初始化。在C/C++中,可以在定义变量时同时使变量初始化。 C++变量的初始化还有另外一种形式,它与C语言不同。例如: int nX(1), nY(3); 表示nX和nY是整型变量,它们的初值分别为1和3。 计算机科学与技术学院 张淼

127 数组 注意字符数组存储字符串 数组的越界 计算机科学与技术学院 张淼

128 结构体 结构体是一种复合类型,结构体中可包含多个类型不同的数据成员 C++的结构体中允许有成员函数 struct student {
int number; char name[10]; int age; }; C++的结构体中允许有成员函数 计算机科学与技术学院 张淼

129 共用体 共用体是将多个数据成员组织为一个整体,它们在内存中占用同一段存储单元 C++的公用体中允许有成员函数 union ab {
int a; doublc b; }; C++的公用体中允许有成员函数 计算机科学与技术学院 张淼

130 枚举类型 枚举类型是将变量的值一一列举出来,变量的值只限于列举出来的值的范围内 enum weekday {
sun,mon,tue,wed,thu,fri,sat }; 作用:增强程序代码的可读性。 计算机科学与技术学院 张淼

131 用typedef定义类型 使用关键字typedef可以将已有的类型名用新的类型名来代替。 格式:
计算机科学与技术学院 张淼

132 CH2.1.3 运算符和表达式 计算机科学与技术学院 张淼

133 … 运算符 运算是指对数据的求值计算,如:加、减运算等。运算符给出了计算的类型,而参与运算的数据叫操作数(变量、常量)。 运算符的分类
按所要求的操作数的多少 一元运算符 二元运算符 三元运算符 需要1个操作数 需要2个操作数 需要3个操作数 运算性质分 算术运算符 关系运算符 逻辑运算符 运算符具有优先级与结合性。 计算机科学与技术学院 张淼

134 结合性是指当一个操作数左右两边的运算符优先级相同时,按什么顺序进行运算,是自左向右,还是自右向左。
当一个表达式中含有多个运算符时,先进行优先级高的运算,后作优先级低的运算。如果表达式中出现了多个相同优先级的运算时,运算的顺序就要看运算符的结合性了。 结合性是指当一个操作数左右两边的运算符优先级相同时,按什么顺序进行运算,是自左向右,还是自右向左。 计算机科学与技术学院 张淼

135 1、算术运算符 C++语言中的算术运算符包括基本算术运算符和增1、减1运算符。 (1)基本算术运算符 一元运算符: +(取正)、-(取负)
二元运算符: +(或加)、-(或减)、*(乘)、/(除)、%(取余) 注意 1) 两个整数相除,将保留整数部分,不是四舍五入;进行浮点数除法,结果是浮点型。如7/5.0、7.0/5、7.0/5.0的结果都是1.4。 2) 求余运算要求参与运算的两个操作数都是整型,其结果是两个数相除的余数。例如40%5的结果是0,40%11的结果是7。要理解负值的求余运算,例如40%-11结果是7,-40%11结果是-7,-40%-11结果也是-7。 计算机科学与技术学院 张淼

136 注意:只能操作变量,不能操作常量。 前置和后置的区别。 (2)增1、减1运算符
增1、减1运算符都是一元运算符,这两个运算符都有前置和后置两种形式。 前置形式是指运算符在操作数的前面,后置是指运算符在操作数的后面。例如: I++; //++后置 --J; //--前置 注意 注意:只能操作变量,不能操作常量。 前置和后置的区别。 计算机科学与技术学院 张淼

137 操作数的类型转换 隐含转换 类型转换方式 强制转换 1). 隐含转换
如果两个操作数的类型不同,系统将自动地把其中操作数类型低的转换成和另一个相同。 各种类型的高低顺序如下: int unsigned long unsigned long double char、short float ● char、short类型自动转换成int,float类型自动转换成double型。 ● 两个操作数中有一个为long double类型,另一个将也转换成long double类型;否则,如果其中有一个为double类型,另一个将也转换成double型;…,如上顺序从低向高转换。 计算机科学与技术学院 张淼

138 隐式转换的方向就是将一个取值范围较小的类型向取值范围较大的类型转换。它能确保在转换过程中数据不受损失。
类型的转换并不实际改变操作数的数据类型。它只是在计算表达式值的时候,将操作数的值临时做了转换。计算后,操作数仍保持原有的数据类型。 计算机科学与技术学院 张淼

139 强制类型转换的作用是将某种类型强制地转换成指定的类型。
2). 强制类型转换 强制类型转换的作用是将某种类型强制地转换成指定的类型。 强制类型转换是通过强制转换运算符来实现的,具体表示形式如下: 类型说明符(操作数) (类型说明符)操作数 意义为把表达式的数据类型强制转换成类型说明符所指定的类型。 计算机科学与技术学院 张淼

140 2、关系运算符 C++语言中,关系运算符都是二元运算符,共有6个:<(小于)、 >(大于)、<=(小于或等于)、>= (大于或等于)、== (等于)、!=(不等于)。 其比较的结果是一个bool型的值。当两个操作数满足关系运算符指定的关系时,表达式的值为true,否则为false。true等于1,false等于0。在C++编译系统中,任何不为0的数被认为是“真”,0被认为是“假”。 应避免对浮点数进行==和!=比较运算,如果需要,可采用判断两者的 差的绝对值是否小于某个很小的数来实现。 x==y fabs(x-y)<1e-6 x!=y fabs(x-y)>1e-6 int i=-10; unsigned int j=1; i<j ? 计算机科学与技术学院 张淼

141 短路求值: true || x false && x 3、逻辑运算符 一元运算符: !(逻辑求反)
若其操作数为0(false),运算结果为true(1),否则为false(0)。 &&(逻辑与) 只要两个操作数中有一个为0(false),运算结果就为false(0),否则为true(1)。 二元运算符 | |(逻辑或) 只要两个操作数中有一个不为0(false),运算结果为ture(1),否则为false(0)。 短路求值: true || x false && x 计算机科学与技术学院 张淼

142 4、位运算符 一元运算符: 二元运算符 ~(按位求反) &(按位与) | (按位或) ^(按位异或) <<(左移位)
>>(右移位) 位运算符 位运算符是对其操作数按其二进制形式逐位进行运算,参与运算的操作数应为整数。 位运算的结果就是位运算表达式的值,参与运算的操作数的值并没有变化。 计算机科学与技术学院 张淼

143 5、sizeof运算符 sizeof运算符用于计算某类型的数据占用的内存大小(字节数)。它是一个一元运算符,操作数可以是C++语言中任一合法的数据类型。 该运算符的使用形式如下: sizeof(数据类型); sizeof(表达式); 计算机科学与技术学院 张淼

144 当赋值操作的两个操作数类型不同时,编译程序将按赋值转换规则进行隐式类型转换,即右边操作数转换成左边的操作数类型。
6、赋值运算符 复合赋值 10种复合赋值运算符:+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>= 注意: (1) 在复合赋值运算符之间不能有空格。 (2) 复合运算符的优先级和赋值符的优先级一样,在C/C++的所有运算符中只高于逗号运算符,而且复合赋值运算符的结合性也是从右至左的。 多重赋值 多重赋值是指在一个赋值表达式中出现两个或更多的赋值符(“=”) 例如: nNum1 = nNum2 = nNum3 = 100; 当赋值操作的两个操作数类型不同时,编译程序将按赋值转换规则进行隐式类型转换,即右边操作数转换成左边的操作数类型。 计算机科学与技术学院 张淼

145 格式:<条件表达式> ? <表达式1> :<表达式2>
7、条件运算符 唯一的三目运算符是条件运算符。 格式:<条件表达式> ? <表达式1> :<表达式2> 计算机科学与技术学院 张淼

146 8、逗号运算符 逗号运算符是优先级最低的运算符,它可以使多个表达式放在一行上。计算时,从左至右逐个计算每个表达式,最终整个表达式的结果是最后计算的那个表达式的类型和值。例如: j = ( i = 12 , i + 8); 式中,i = 12 ,i + 8 是含逗号运算符的表达式,计算次序是先计算表达式i = 12,然后再计算i + 8,整个表达式的值是最后一个表达式的值,即i + 8的值20, 从而j的结果是20。 计算机科学与技术学院 张淼

147 CH2.1.4 基本语句 计算机科学与技术学院 张淼

148 语句可用于计算表达式的值、控制程序执行的顺序,有时语句也可能不作任何操作(空语句)。
分类 简单语句 结构语句 表达式语句 转移语句 空语句 goto语句 break语句 continue语句 return语句 复合语句 选择语句 循环语句 if语句 switch语句 while语句 do-while语句 for语句 计算机科学与技术学院 张淼

149 CH2.1.5 函数 在面向对象的程序设计中,一个C++程序是由类的实例(对象)构成。函数主要用于定义对象的操作接口。
函数是一个能完成某一独立功能的子程序,也就是程序模块。每个C++程序至少包含一个函数,即main函数(主函数)。在面向过程的程序设计中,一个较为复杂的程序一般通过模块化,分解成主模块与若干子模块的组合,即一个主函数与若干子函数。程序是以函数为单位,由一个或多个函数组成的。 在面向对象的程序设计中,一个C++程序是由类的实例(对象)构成。函数主要用于定义对象的操作接口。 本部分介绍有关函数的一些基本知识,如:函数的定义及调用等。这些知识虽然大部分是建立在面向过程的程序设计方法的基础上,但它们也是我们学习面向对象程序设计的基础。 计算机科学与技术学院 张淼

150 调用其他函数的函数称为主调函数,被其他函数调用的函数称为被调函数。
在面向过程的程序设计中,一个C++程序由一个或多个函数组成。当程序开始运行时,系统自动调用主函数。主函数可以调用子函数,子函数还可以调用其他子函数。 调用其他函数的函数称为主调函数,被其他函数调用的函数称为被调函数。 计算机科学与技术学院 张淼

151 计算机科学与技术学院 张淼

152 函数的定义 在C++程序中,定义一个函数的格式如下: { 语句序列 } 类型 函数名(形式参数表) 该函数的类型,即该函数返回值的类型。
类型 函数名(形式参数表) { 语句序列 } 该函数的类型,即该函数返回值的类型。 函数体:可以有0条、1条或多条语句。当是0条语句时,该函数称作空函数。 计算机科学与技术学院 张淼

153 每个函数都有类型,如果没有明确指定,则类型为int。
函数的返回值是需要返回给主调函数处理的结果,由return语句给出。当该函数没有返回值时,函数的类型为void,可不写return语句。 return语句的一般格式如下: return 表达式; 对于没有返回值的函数,return语句可有可无。 情况分类 没有return语句:函数在被调用时,程序执行完函数体的最后 一条语句后,自动返回主调函数。 有return语句: 这时的return语句应表示为: return; 一个函数中允许出现多个return语句,分别用于不同条件下的函数返回。 计算机科学与技术学院 张淼

154 形式参数表由0个、1个或多个参数组成,内容如下: 类型1 形式参数名1,类型2 形式参数名2,…,类型n 形式参数名n
说明了对应形式参数的数据类型。 是一个标识符。 形式参数又称形参,将函数需要处理的数据、影响函数功能的因素和函数处理的结果作为形参,实现主调函数与被调函数之间的联系。没有形参的函数,可以在形参表的位置填上void或保留空白,但形参表两边的圆括号 不可省略。 计算机科学与技术学院 张淼

155 double rectanglearea(double a,double b) { double s; s=a*b; return s; }
函数的定义 double rectanglearea(double a,double b) { double s; s=a*b; return s; } 计算机科学与技术学院 张淼

156 每个函数都是一个功能独立的模块,绝对不允许在一个函数体内定义另一个函数。
计算机科学与技术学院 张淼

157 函数的声明 声明函数,一般采用声明函数原型。 形式如下: 类型 函数名(形式参数表); 必须以分号结尾。
类型 函数名(形式参数表); 必须以分号结尾。 函数原型中的类型、函数名和形参表必须与定义该函数时完全一致,但函数原型中可以不包含参数名,而只包含形参的类型。 例如: double rectanglearea(double a,double b); double rectanglearea(double,double); 计算机科学与技术学院 张淼

158 函数的调用 除主函数main由系统自动调用外,其他函数都由主函数直接或间接调用的。 函数的调用的一般形式如下: 函数名(实际参数表) 程序中调用的所有函数都要有定义,如果函数定义在其他文件中(如C++的标准库)或定义在本源文件使用点之后,则在调用前需要对被调用的函数进行声明。 计算机科学与技术学院 张淼

159 main( ) 调fun( ) 结束 ③ ② ① ④ ⑦ ⑥ ⑤ 保存: 返回地址 fun( ) 当前现场 返回 恢复: 主调程序现场
计算机科学与技术学院 张淼

160 函数调用时,如果被调用函数带有形参,系统需要首先给它的形参分配内存空间,并用调用表达式中的实参初始化形参。
函数调用时的参数传递指的就是实参与形参结合过程。 传值调用: 简称值调用(默认)。 实参与形参结合方法 传址调用 引用调用 使用值调用时,系统首先为形参分配内存空间,并将实参的值按位置一一对应赋给形参。在被调用函数中形参值的任何改变都不会影响到实参。 计算机科学与技术学院 张淼

161 double power(double X, int N)
主调函数: D = power(A,3) A 2.5 3 X 2.5 N 3 被调函数: double power(double X, int N) 计算机科学与技术学院 张淼

162 例:输入两 整数交换后输出 #include<iostream.h> void Swap(int a, int b);
int main( ) { int x(5), y(10); cout<<"x="<<x<<" y="<<y<<endl; Swap(x,y); return 0; } 计算机科学与技术学院 张淼

163 运行结果: x=5 y=10 void Swap(int a, int b) { int t; t=a; a=b; b=t; }
计算机科学与技术学院 张淼

164 数组作为函数参数 数组元素作实参,与单个变量一样。
数组名作参数,形、实参数都应是数组名,类型要一样,传送的是数组首地址。对形参数组的改变会直接影响到实参数组。 计算机科学与技术学院 张淼

165 嵌套调用 main{} 调fun1() 结束 fun1( ) 调fun2() 返回 fun2( ) 返回 ① ② ③ ④ ⑤ ⑨ ⑦ ⑧ ⑥
计算机科学与技术学院 张淼

166 #include <iostream.h> void main(void) { int a,b;
例 输入两个整数,求平方和。 #include <iostream.h> void main(void) { int a,b; int fun1(int x,int y); cin>>a>>b; cout<<"a、b的平方和:" <<fun1(a,b)<<endl; } 计算机科学与技术学院 张淼

167 return (fun2(x)+fun2(y)); } int fun2(int m) return (m*m); 运行结果: 3 4
int fun1(int x,int y) { int fun2(int m); return (fun2(x)+fun2(y)); } int fun2(int m) return (m*m); 运行结果: 3 4 a、b的平方和:25 计算机科学与技术学院 张淼

168 递归调用 函数直接或间接地调用自身,称为递归调用。 递归过程的两个阶段: 递推: 未知 已知 回归:
4!=4×3! → 3!=3×2! → 2!=2×1! → 1!=1×0! → 0!=1 未知 已知 回归: 4!=4×3!=24←3!=3×2!=6←2!=2×1!=2←1!=1×0!=1←0!=1 未知 已知 计算机科学与技术学院 张淼

169 例 求n! 分析:计算n!的公式如下: 这是一个递归形式的公式,应该用递归函数实现。 计算机科学与技术学院 张淼

170 #include <iostream.h> long fac(int n) { long f; if (n<0)
cout<<"n<0,data error!"<<endl; else if (n==0) f=1; else f=fac(n-1)*n; return(f); } 计算机科学与技术学院 张淼

171 cout<<"Enter a positive integer:"; cin>>n; y=fac(n);
void main( ) { long fac(int n); int n; long y; cout<<"Enter a positive integer:"; cin>>n; y=fac(n); cout<<n<<"!="<<y<<endl; } 运行结果: Enter a positive integer:8 8!=40320 计算机科学与技术学院 张淼

172 程序的执行过程中,调用函数时先要保存主调函数的现场和返回地址,然后程序转移到被调函数的起始地址继续执行。被调函数执行结束后,先恢复主调函数的现场,取出返回地址并将返回值赋给函数调用本身,最后在返回地址处开始继续执行。当函数体比较小时,且执行的功能比较简单时,这种函数调用方式的系统开销相对较大。 计算机科学与技术学院 张淼

173 内联函数 在程序编译时,编译系统将程序中出现内联函数调用的地方用函数体进行替换。由于在编译时将函数体中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间开销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。 内联函数的使用会增加程序的代码量。 内联函数一般来说仅适用于只有一、两条语句的小函数。 计算机科学与技术学院 张淼

174 内联函数的定义:在函数定义时,在函数的类型前增加关键字inline。
形式如下: inline 类型 函数名(形参表) { ......//函数体 } 说明:仅在声明函数原型时加上关键字inline,并不能达到内联效果。 计算机科学与技术学院 张淼

175 #include<iostream.h> inline double CalArea(double radius) {
return 3.14*radius*radius; } int main( ) double r(3.0); double area; area=CalArea(r); cout<<area<<endl; return 0; 计算机科学与技术学院 张淼

176 (1) 内联函数的定义必须出现在内联函数第一次被调用之前。内联函数只能先定义后使用,不存在先声明再使用。
(1)  内联函数的定义必须出现在内联函数第一次被调用之前。内联函数只能先定义后使用,不存在先声明再使用。 (2) 内联函数名具有文件作用域 ,在一个文件中定义的内联函数对于另一个文件是不可见的,因此调用内联函数时一定要见到内联函数的定义。对于一个多文件结构的程序,如果在一个源文件中定义了一个内联函数,而在另一个源文件中对其声明并使用,这将导致程序连接时刻的错误。 计算机科学与技术学院 张淼

177 在一个函数中,要求通过函数来实现一种不太复杂的功能,并且要求加快执行速度,选用( )合适。
在一个函数中,要求通过函数来实现一种不太复杂的功能,并且要求加快执行速度,选用( )合适。 A. 内联函数                B. 重载函数       C. 递归调用          D. 嵌套调用 计算机科学与技术学院 张淼

178 (3) 需要定义成的内联函数不能含有循环、switch和复杂嵌套的if语句。 (4) 递归函数是不能被用来做内联函数的。
(3)  需要定义成的内联函数不能含有循环、switch和复杂嵌套的if语句。 (4)  递归函数是不能被用来做内联函数的。 (5) 把成员函数的定义放在类定义中是建议编译程序按内联函数处理它们。 编译器是否将用户定义成的内联函数作为真正的内联函数处理由它自行决定。 计算机科学与技术学院 张淼

179 全局变量与局部变量 在C++中,根据变量的定义位置,把变量分为全局变量和局部变量。全局变量是指定义在函数外部的变量,它能被所有函数使用。局部变量是指在复合语句(函数)中定义的变量,它们只能在定义它们的复合语句(包括内层的复合语句)中使用。 计算机科学与技术学院 张淼

180 计算机科学与技术学院 张淼

181 全局变量可以定义在函数外的任何地方,如果在使用一个全局变量时未见到它的定义,则在使用前需要对该全局变量进行声明。
变量声明格式: extern <类型名> <变量名>; 变量定义要给变量分配空间,变量声明则否。 在整个程序中,一个变量的定义只有一个,而该变量的声明可以有多个。 计算机科学与技术学院 张淼

182 局部变量也可以放在复合语句的任何位置,但是,在复合语句中局部变量定义 之前不能使用他们。
int main() { …x… //error int x; …x… //ok } 计算机科学与技术学院 张淼

183 函数的形参也可看成局部变量,它们只能在相应的函数中使用。
计算机科学与技术学院 张淼

184 变量的存储分配(生存期) 变量是内存空间的一种抽象,程序中定义的每个变量在程序运行时刻都有与之对应的内存空间。
但是,何时给变量分配空间以及相应空间分配在哪里?这要视变量的性质而定。通常把程序运行时一个变量占有内存空间的时间段称为该变量的生存期。 计算机科学与技术学院 张淼

185 内存空间从程序开始执行就进行分配,直到程序结束才收回。全局变量具有静态生存期。
变量的三种生存期 静态 内存空间从程序开始执行就进行分配,直到程序结束才收回。全局变量具有静态生存期。 自动 内存空间在程序执行到它们所在的复合语句(函数)时才分配,当定义它们的复合语句执行结束时,它们的空间将会收回。局部变量和函数的参数一般具有自动生存期。 动态 内存空间用new操作分配、用delete操作收回。 计算机科学与技术学院 张淼

186 另外,在定义局部变量时,可以为它们加上存储类型修饰符auto、static或register来指定它们的生存期。
局部变量的默认存储类型为auto,即定义局部变量时,如果未指定存储类,则其存储类为auto。定义为auto存储类的局部变量具有自动生存期;定义为static存储类的局部变量具有静态生存期;定义为register存储类的局部变量也具有自动生存期。它与auto存储类的局部变量的区别是:register存储类是建议编译程序把相应的局部变量的空间分配在CPU的寄存器中,目的是为了提高对局部变量的访问效率。 static存储类的局部变量的作用是它能在函数调用时获得上次调用结束时该局部变量的值。 计算机科学与技术学院 张淼

187 #include <iostream.h> int z=0; void f() {int x=0;
static int y=0; x++; y++; z++; cout<<“x=“<<x<<“,y=”<<y<<“,z=”<<z<<endl; } void main() { f(); z++; } 计算机科学与技术学院 张淼

188 当一个程序准备运行时,操作系统将为其分配一块内存空间,其中包括四个部分:静态数据区(static code)、代码区(code)、栈区(stack)和堆区(heap ,或称自由存储区,free store)。 静态数据区 代码区 栈区 堆区 程序内存分配示意图 计算机科学与技术学院 张淼

189 静态数据区用于全局变量、static存储类的局部变量以及常量的内存分配。
栈区用于auto存储类的局部变量、函数的形式参数以及函数调用时的有关信息(如函数返回地址等)的内存分配; 堆区用于动态变量的内存分配。 静态数据区 代码区 栈区 堆区 计算机科学与技术学院 张淼

190 全局变量、static存储类的局部变量(存放在静态数据区的变量)在变量定义时有默认值。int型为0,浮点型为0.0,char型为空。
auto存储类的局部变量在定义时没有值,此时是一个不可预料的值,因此我们必须在使用前对它进行赋值或初始化。 计算机科学与技术学院 张淼

191 全局变量,是在main()函数执行前就分配好了的。其实,在main()函数中的显示代码执行之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局变量的构造及初始化工作。而在main()函数结束之前,会调用由编译器生成的exit函数,来释放所有的全局变量。比如下面的代码: 计算机科学与技术学院 张淼

192 void main(void) { … …// 显式代码 }
void main(void) {  _main(); //隐式代码,由编译器产生,用以构造所有全局变量  … … // 显式代码  … …  exit() ; // 隐式代码,由编译器产生,用以释放所有全局变量} 计算机科学与技术学院 张淼

193 C++程序的多模块结构 逻辑上,一个C++程序由一些全局函数(区别于类定义中的成员函数)、全局常量、全局变量/对象以及类的定义构成,其中必须有且仅有一个main的全局函数。函数内部还可以包含形参、局部常量、局部变量/对象的定义以及语句。 C++函数内部不能再定义函数。 计算机科学与技术学院 张淼

194 为了便于组织、管理和理解C++程序,在物理上我们通常可以按照某种规则对构成C++程序的若干逻辑单位(全局函数、全局常量、全局变量/对象、类等)进行分组,分别把它们放到若干源文件中。编译程序对每个源文件分别进行编译。 分组的原则是按照模块进行,一个模块一般包含两部分:接口和实现。接口给出在本模块中定义的、提供给其他模块使用的一些程序实体(如函数、全局变量等)的声明;模块的实现给出了模块中实体的定义。 在C++中,一个模块通常由两个源文件构成,一个是.h文件,用于存放模块接口的定义;另一个是.cpp文件用于存放模块的实现。在一个模块中要用到另一个模块中定义的程序实体时,需要在前者的.cpp文件中用文件包含命令把后者的.h文件包含进来。 计算机科学与技术学院 张淼

195 在编译前,用命令中的文件名所指定的文件内容替换该命令。
编译预处理命令——文件包含 在编译前,用命令中的文件名所指定的文件内容替换该命令。 #include <文件名> #include “文件名” <>在系统指定的目录下寻找指定文件; “”先在包含#include命令的源文件所在目录下查找指定文件,然后再在系统指定的目录下寻找指定的文件。 计算机科学与技术学院 张淼

196 VC++程序编译链接原理与过程 Animal.h Fish.h #include #include Animal.cpp Fish.cpp
main.cpp 预处理 翻译单元1 翻译单元2 翻译单元3 编译 (Compile) main.obj Animal.obj Fish.obj .lib C++的标准库函数 标准类库 链接 (Link) .exe 可执行文件 计算机科学与技术学院 张淼

197 标识符的作用域 当标识符的作用域完全相同时,不允许出现相同的标识符名。而当标识符具有不同的作用域时,允许标识符同名。
C++根据程序的结构和标识符定义的位置,为每一个定义了的标识符规定了一个作用域。标识符的作用域是指一个定义了标识符的有效范围,即该标识符所标识的程序实体能被访问的程序段。 C++把标识符的作用域分成若干类,其中包括:局部作用域、全局作用域、文件作用域、函数作用域、函数原型作用域、类作用域和名空间作用域。 当标识符的作用域完全相同时,不允许出现相同的标识符名。而当标识符具有不同的作用域时,允许标识符同名。 计算机科学与技术学院 张淼

198 局部作用域(块作用域) C++中的局部常量名、局部变量名/对象名以及函数的形参名具有局部作用域。
局部作用域指在函数定义或复合语句中,从标识符的定义点开始到函数定义或复合语句结束之间的程序段。 C++中的局部常量名、局部变量名/对象名以及函数的形参名具有局部作用域。 计算机科学与技术学院 张淼

199 局部实体定义前可以有语句,但这些语句不在相应标识符的作用域内,从而不能使用后面定义的标识符。
如果一个标识符的局部作用域中包含其它复合语句(内层复合语句),并且在该内层复合语句中定义了一个同名的不同实体,则外层定义的标识符的作用域应该是从其作用域中扣除内层同名标识符的作用域之后得到的作用域。 计算机科学与技术学院 张淼

200 全局变量名/对象名、全局函数名和全局类名的作用域一般具有全局作用域。它们在整个程序中可用。
全局作用域指构成C++程序的所有源文件,具有全局作用域的标识符称为全局标识符。 全局变量名/对象名、全局函数名和全局类名的作用域一般具有全局作用域。它们在整个程序中可用。 计算机科学与技术学院 张淼

201 如果某个局部作用域中定义了与某个全局标识符同名的标识符,则该全局标识符的作用域应扣掉与之同名的局部标识符作用域。
在局部标识符的作用域中若要使用与其同名的全局标识符,则需要用全局选择符(::)对全局标识符进行修饰(受限)。 计算机科学与技术学院 张淼

202 使用全局标识符时,若该标识符的定义点在其他源文件中或在本源文件中使用点之后,则在使用前需要声明它。
通常,把全局标识符的声明放在某个.h文件中,在需要使用这些全局标识符的源文件中用#include编译预处理命令把声明文件包含进来。 计算机科学与技术学院 张淼

203 文件作用域 在全局标识符的定义中加上static修饰符,则该全局标识符就成了具有文件作用域的标识符,它们只能在定义它们的源文件中使用。
文件作用域指单独的源文件。 在全局标识符的定义中加上static修饰符,则该全局标识符就成了具有文件作用域的标识符,它们只能在定义它们的源文件中使用。 在函数外用const定义的常量名具有文件作用域。 用编译预处理命令#define定义的标识符不属于作用域要考虑的范畴,因为它们在编译前将被替换成所定义的内容。 计算机科学与技术学院 张淼

204 C++中的关键词static有两个不同的含义。在局部变量的定义中,static修饰符用于指定局部变量采用静态存储分配;而在全局标识符的定义中,static修饰符用于把全局标识符的作用域改为文件作用域。
计算机科学与技术学院 张淼

205 函数作用域 函数作用域是指由整个函数定义所构成的程序段。 语句标号是唯一具有函数作用域的标识符,可以在定义它们的函数体的任何地方访问它们。
计算机科学与技术学院 张淼

206 函数作用域包括整个函数,而局部作用域是从定义点开始到函数定义或复合语句结束。
函数作用域与局部作用域的区别 函数作用域包括整个函数,而局部作用域是从定义点开始到函数定义或复合语句结束。 在函数体中一个语句标号只能定义一次,即使在内层的复合语句中,也不能再定义与外层相同的语句标号。 C++把语句标号作为一种特殊的标识符看待,它与其他种类的标识符属于不同的范畴,因此,语句标号的作用域可以和同名的其它标识符的作用域重叠。 计算机科学与技术学院 张淼

207 函数原型作用域 在函数原型中声明的标识符可以与函数定义中说明的标识符名称不同。
函数原型作用域是指用于函数声明的函数原型,其中的形参名(如果给出)的作用域从函数原型开始到函数原型结束,即在函数原型声明中的左、右括号之间。 在函数原型中声明的标识符可以与函数定义中说明的标识符名称不同。 计算机科学与技术学院 张淼

208 下列的标识符中,( )是函数作用域的。 A. 函数形参 B. 语句标号 C. 外部静态类标识符 D. 自动类标识符
下列的标识符中,( )是函数作用域的。 A. 函数形参 B. 语句标号 C. 外部静态类标识符 D. 自动类标识符 计算机科学与技术学院 张淼

209 file1.cpp void f() {… } file2.cpp void f() {… } 实现打印 实现屏幕输出 Main.cpp
int main() { f(); } 实现打印 实现屏幕输出 计算机科学与技术学院 张淼

210 名空间(namespace) 名空间是给一组程序实体的定义取一个名字使之构成一个作用域——名空间作用域。 namespace A
{int x=1; void f() {… } void g() 计算机科学与技术学院 张淼

211 在一个名空间中定义的全局标识符,其作用域为该名空间。当在一个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受限。
如:A::f(); 当使用某个名空间上的程序实体时,如果这些程序实体的名与其它全局程序实体的名不冲突,则可在使用前写一个using <名空间>指示项,使得今后使用相应名空间中的程序实体时不必用空间名受限。 计算机科学与技术学院 张淼

212 void print(int value,int base);
题目:以某种进制形式输出整数。 void print(int value,int base); print(x,10); print(x,2); 计算机科学与技术学院 张淼

213 带默认形参值的函数 在C++中,允许在定义或声明函数时,为函数的某些参数指定默认值。当调用这些函数时没有提供相应的实参时,则相应的形参采用指定的默认值,否则相应的形参采用调用者提供的实参值。 void print(int value,int base=10); print(x); print(x,2); 计算机科学与技术学院 张淼

214 int add(int x=5,int y=6) { return x+y; } void main(void)
计算机科学与技术学院 张淼

215 一个函数中有多个默认参数时,则形参分布中,默认参数应从右到左逐渐定义(所有默认参数必须放在参数表的最后,即在一个指定了默认值的参数的右边,不能出现没有指定默认值的参数)。
计算机科学与技术学院 张淼

216 int add(int x,int y=5,int z=6); //正确
计算机科学与技术学院 张淼

217 在相同的作用域内,缺省形参值的声明应保持唯一,但如果在不同的作用域内,允许说明不同的缺省形参。
计算机科学与技术学院 张淼

218 { int add(int x=3,int y=4); add( ); //使用局部缺省形参值(实现3+4) }
void main(void) { int add(int x=3,int y=4); add( ); //使用局部缺省形参值(实现3+4) } void fun(void) { ... add( ); //使用全局缺省形参值(实现1+2) 计算机科学与技术学院 张淼

219 void f(int a,int b,int c) //函数f的定义 {…}
//file.cpp void f(int a,int b,int c) //函数f的定义 {…} //file1.cpp void f(int a,int b,int c=2); //file2.cpp void f(int a,int b=1,int c=0) ; 计算机科学与技术学院 张淼

220 函数重载 C++允许功能相近的函数在相同的作用域内以相同函数名声明,从而形成重载。方便使用,便于记忆。
重载的要求:形参的个数不同或类型不同。 形参类型不同 int add(int x, int y); float add(float x, float y); 形参个数不同 int add(int x, int y); int add(int x, int y, int z); 计算机科学与技术学院 张淼

221 重载函数的返回类型,即函数类型可以相同,也可以不同。如果仅仅是返回类型不同,而函数名相同、形参表也相同,则是非法的。
例如: int fun(int a,int b); long fun(int a,int b); 不合法的 说明:合法的。但不是重载。它只是对同一函数原型的多次声明。 例如: int fun(int a,int b); int fun(int x,int y); 函数重载又称函数的多态性。它通过改变形参的个数或类型使多个函数共用一个函数名。 计算机科学与技术学院 张淼

222 当重载的函数带有默认参数时,应该注意避免二义性。
例如: int fun(int a,int b=0); int fun(int a); 说明:遇到fun(2)的函数调用时,编译器将无法准确地确定应调用哪个函数。 计算机科学与技术学院 张淼

223 下面的函数声明中, ( )是” void max(int a, int b);”的重载函数?
A.int max(int a, int b) ; B.void max(int a, char b) ; C.float max (int a, int b, int c=0) ; D.void max (int a, int b=0) ;   计算机科学与技术学院 张淼

224 C++语言的系统函数 定义:C++编译系统本身定义了几百个在编程时可能经常用到的函数。
在使用某一系统函数前需要知道它的函数原型在哪个头文件中。这也可以通过库函数手册或联机帮助查到。在使用某个系统函数前所需要做的就是用include指令在程序中嵌入相应的头文件,然后便可以和调用自己定义的函数一样使用系统函数。 计算机科学与技术学院 张淼

225 CH2.1.6指针和引用 计算机科学与技术学院 张淼

226 指针 指针变量的定义 <类型> *<指针变量名1>[,*<指针变量名2>,...] 类型为指针所指向的变量的类型,即指针类型。指针类型不是指针本身数据值的类型,任何一个指针本身的数据值都是unsigned long int型。 计算机科学与技术学院 张淼

227 变量的地址 取地址符&来获得一个变量的地址。
对于数组和函数,它们的内存首地址可以不用&来获得,数组变量名和函数名本身就表示它们在内存中的首地址。 对于常量0,它除了表示一个整型常量外,还可以表示一个空指针。 空指针不代表任何内存空间的地址,它属于所有的指针类型。 在C++的标准头文件cstdio或stdio.h中,定义了一个符号常量NULL,用于表示空指针。 计算机科学与技术学院 张淼

228 指针赋值 对于一个指针变量只能把在定义该指针变量时所指定类型的变量的地址赋给它。
在使用指针变量前,一定要对其进行初始化或使其有确定的地址数值。 任何类型的指针都可以赋给void *类型的指针。 计算机科学与技术学院 张淼

229 间接访问操作*与-> 对于一个指针变量可以通过间接访问操作符*来访问它所指向的变量。
对于一个指向结构体类型变量的指针,如果通过该指针变量来访问相应结构体变量的成员,则可写成: 指针变量->结构成员 (*指针变量).结构成员 计算机科学与技术学院 张淼

230 指针的输出 指针输出操作有一个例外:当输出字符指针(char *)时,输出的不是指针值,而是该指针所指向的字符串。
char str[]=“ABCD”; char *p=str; cout<<p;//输出:ABCD cout<<*p;//输出:A 计算机科学与技术学院 张淼

231 指针作为形参类型——地址调用 把指针作为形参的类型可以产生两个效果: 指向常量的指针 提高参数传递效率。 通过形参改变实参的值。
const <类型> *<指针变量>; const的含义是不能改变<指针变量>所指向的数据的值。 指向常量的指针变量可以指向const常量也可指向变量,只不过不能通过它来改变所指向的变量值而已。 对于一个指向变量的指针变量,不允许它指向一个常量。 计算机科学与技术学院 张淼

232 const int *p; const int x; p=&x; *p=1; //error int y; p=&y;
Y=1;//ok 计算机科学与技术学院 张淼

233 const int x=0; int *p; p=&x;//error 计算机科学与技术学院 张淼

234 不要将指向常量的指针类型与指针类型的常量混淆了。 指针类型的常量必须初始化。 int x,y; int *const p=&x; *p=1;
p=&y;//错误,p是一个常量其值不能改变。 计算机科学与技术学院 张淼

235 int a=0; const int *p;说明( )。 A.不能修改p指针 的值 。 B.不能通过p修改p所指向的变量的值。
C.p=&a;是错误的。 D.上述B、C二者。 计算机科学与技术学院 张淼

236 已知int x; int *const p=&x;执行语句 *p=1;会发生错误。 ( )
Windows API中的所有函数都包含在DLL中 。 ( ) 计算机科学与技术学院 张淼

237 函数指针与返回指针值的函数 函数指针:C++中可以定义一个指针变量,使它指向一个函数。
<返回类型> (*<指针变量>)(<形式参数表>) double (*f)(int); 返回指针值的函数:返回值类型是一个指针类型。 int *max(const int x[],int num) { …… } 计算机科学与技术学院 张淼

238 指针和数组 计算机科学与技术学院 张淼

239 指针与动态变量 动态变量:从静态的程序中无法确定它们的存在,只有当程序运行起来,随着程序的运行,它们根据程序的需要动态产生和消亡。其内存分配在堆区。动态变量没有名字,因此对动态变量的访问需要通过指向动态变量的指针变量来进行。 计算机科学与技术学院 张淼

240 动态变量的创建 new 类型(初值); new 类型[第1维的大小]…[第n维的大小];
void *malloc(unsigned int size); 例: int *p=new int; *p=1; int *p=new int(100); char *p=new char[10]; int (*q)[4]=new int[5][4]; int *p=(int *)malloc(sizeof(int)); 计算机科学与技术学院 张淼

241 动态变量的撤消 用delete释放用new动态分配的内存空间 注意事项
在C++程序运行期间,动态变量不会自动消亡,在一个函数调用中创建的动态变量,函数返回后仍然存在(可以使用)。如果不再需要这个动态变量了,则应该显式使之消亡。 用delete释放用new动态分配的内存空间 delete 指针变量 delete[] 指针变量 用free释放用malloc动态分配的内存空间 void free(void *p) 注意事项 new和delete应成对出现,malloc和free成对出现 计算机科学与技术学院 张淼

242 new和delete示例 开辟空间用来存放结构体变量 #include <string.h> struct student {
char name[10]; int num; int age; }; void main() student *p = new student; strcpy(p->name,"张三"); p->num = 10123; p->age = 20; delete p; } 计算机科学与技术学院 张淼

243 new和malloc比较 使用方便性 程序执行效率 malloc需要计算内存空间大小,通常需要与sizeof函数配合使用
malloc的返回值为void*,赋值给某一类型的变量时需要进行强制类型转换 student *s = (student *)malloc(sizeof(student)); new运算符自动计算内存空间大小,而且返回的就是具体的数据类型,不需要进行转换 程序执行效率 new和delete是运算符,执行效率高 malloc和free是函数,执行效率相对较低 计算机科学与技术学院 张淼

244 用delete或free撤消动态变量后,C++编译程序一般不会把指向它的指针变量的值赋为NULL,这时就会出现“悬浮指针”(dangling pointer),它指向一个无效空间。这时如果再通过这个“悬浮指针”来使用相应的动态变量会导致程序的语义错误。 对于一个动态变量,如果没有撤消它,而仅仅是把指向它的指针变量指向了别处,或指向它的指针变量的生存期结束了,这个动态变量就会变成一个“孤儿”,这种现象称为内存泄露。程序中再也访问不到它,而它却一直占据内存空间,从而导致内存空间的浪费。 计算机科学与技术学院 张淼

245 2 2000 p void f() { int *p=new int(2); …… } 栈内存 堆内存 计算机科学与技术学院 张淼

246 下面程序中的变量p,数字2,变量a,b,c分别被分配在( )内存区。 //xxx.cpp
#include <iostream.h> static int c=5; void f() { int *p=new int(2);} void main() { static int a=0; int b=3; f(); cout<<a<<b<<c<<endl; } 计算机科学与技术学院 张淼

247 A. c为静态数据区;a,b,p为栈区,2为堆区 B. c为静态数据区;a,b为栈区,2,p为堆区
C. a,c为静态数据区; b为栈区,2,p为堆区 D. a,c为静态数据区;p,b为栈区,2为堆区 计算机科学与技术学院 张淼

248 引用 什么是引用? 引用定义 定义一个引用 引用是C++中针对变量的一种别名机制 引用不能单独存在,它的存在的前提是它所代表的变量首先存在
类型 &引用 = 变量名 定义一个引用 例如: int a; int& b = a; 计算机科学与技术学院 张淼

249 计算机科学与技术学院 张淼

250 引用示例 定义引用并使用引用 #include <iostream> using namespace std;
void main() { int a = 50; int& b = a; a = 100; cout<<"a="<<a<<endl; cout<<"b="<<b<<endl; } 计算机科学与技术学院 张淼

251 引用的限制 引用不能单独使用,必须进行初始化。 int& a; //错误,没有初始化引用 引用变量定义后,它不能再引用其它变量
当引用类型变量的初始化值是常数时,则必须将该引用定义成const类型。 int const a = 50; const int& b = a; 计算机科学与技术学院 张淼

252 float& b = a; //错误,引用与变量类型不一致 可以对指针引用,因为指针也是变量 int* b = &a;
引用与其所引用的变量必须具有相同的类型 int a = 50; float& b = a; //错误,引用与变量类型不一致 可以对指针引用,因为指针也是变量 int* b = &a; int*& c = b; //c是对指针变量b的引用 计算机科学与技术学院 张淼

253 函数的参数传递(1) 值传递 void swap(int a,int b) { int temp;
temp = a; a = b; b = temp; } void main() int m = 3,n = 5; swap(m,n); cout<<"m="<<m<<" n="<<n<<endl; 计算机科学与技术学院 张淼

254 函数的参数传递(2) 地址传递 void swap(int *a,int *b) { int temp;
temp = *a; *a = *b; *b = temp; } void main() int m = 3,n = 5; swap(&m,&n); cout<<"m="<<m<<" n="<<n<<endl; 计算机科学与技术学院 张淼

255 函数的参数传递(3) 引用传递 void swap(int& a,int& b) { int temp;
temp = a; a = b; b = temp; } void main() int m = 3,n = 5; swap(m,n); cout<<"m="<<m<<" n="<<n<<endl; 计算机科学与技术学院 张淼

256 void f(const int& x) { …; } void main() int a = 3; f(a); 计算机科学与技术学院 张淼

257 引用作为函数返回值类型 int &max(int x[],int num) {int i,j; j=0;
for(i=1;i<num;i++) if(x[i]>x[j]) j=i; return x[j]; } …… int a[10]; cout<<max(a,10)<<endl; max(a,10)+=1; 计算机科学与技术学院 张淼

258 CH2.2 C++面向对象程序设计基础 面向对象程序设计模拟自然界认识和处理事物的方法,将数据和对数据的操作方法放在一起,形成一个相对独立的整体——对象(object)[面向对象的程序设计就是由这些对象构造程序],同类对象还可抽象出共性,形成类(class )。一个类中的数据通常只能通过本类提供的方法进行处理,这些方法成为该类与外部的接口。对象之间通过消息(message)进行通讯。 计算机科学与技术学院 张淼

259 在面向过程的程序设计方法中,问题被看作一系列将被完成的任务,如读、计算和打印。许多函数用于完成这些任务。问题的焦点集中于函数。
计算机科学与技术学院 张淼

260 面向过程程序设计的基本任务是编写计算机执行的指令序列,并把这些指令以函数的方式组织起来。通常我们使用流程图组织这些行为(action),并描述从一个行为到另一个行为的控制流。 当我们集中精力开发函数的时候,很少会去注意那些被多个函数使用的数据(data)。在这些数据身上发生了什么事情?那些使用这些数据的函数又对它们产生了什么影响? 在多函数(multi-function)程序中,许多重要的数据被放置在全局数据区,这样它们可以被所有的函数访问。每个函数都可以具有它们自己的局部数据。 计算机科学与技术学院 张淼

261 计算机科学与技术学院 张淼

262 面向对象程序设计方法的主要出发点是弥补面向过程程序设计方法中的一些缺点。面向对象的设计思想力图使在计算机语言中对事物的描述与现实世界中该事物的本来面目尽可能地一致。OOP把数据看作程序开发中的基本元素,并且不允许它们在系统中自由流动。它将数据和操作这些数据的函数紧密的连结在一起,并保护数据不会被外界的函数意外的改变。OOP允许我们将问题分解为一系列实体——这些实体被称为对象(object),然后围绕这些实体建立数据和函数。 计算机科学与技术学院 张淼

263 计算机科学与技术学院 张淼

264 面向对象的程序设计 1 基 本 概 念 2 “面向对象”程序设计的特点 计算机科学与技术学院 张淼

265 基 本 概 念 对 象(object) 类(class) 消 息(message) 计算机科学与技术学院 张淼

266 1 基 本 概 念 对 象 表针 旋钮 其他机械机构 属性 行为 调节旋钮 计算机科学与技术学院 张淼

267 1 基 本 概 念 类 类是一个抽象的概念,用来描述某一类对象所共有的、本质的属性和行为。 类 对象 手表 一块手表
1 基 本 概 念 类是一个抽象的概念,用来描述某一类对象所共有的、本质的属性和行为。 类的一个具体实现,称为实例 类 对象 描述这类对象共有的、本质的属性和行为 具体到一只圆形的或方形的手表 手表 一块手表 手表共有的属性(表针、旋钮、内部结构) 和行为(调节旋钮) 计算机科学与技术学院 张淼

268 基 本 概 念 消 息 我们把对象之间产生相互作用所传递的信息称做消息。 发送消息 接收并响应消息 启 动 计算机科学与技术学院 张淼

269 1 基 本 概 念 消 息 我们把对象之间产生相互作用所传递的信息称做消息。 发送消息 接收并响应消息 转 向 计算机科学与技术学院 张淼

270 “面向对象”程序设计的特点 封装性 (2) 继承与派生性 (3) 多态性 计算机科学与技术学院 张淼

271 封装性 2 “面向对象”程序设计的特点 内 外 机械零件 动作 调节旋钮 读表盘
2 “面向对象”程序设计的特点 封装性 机械零件 动作 调节旋钮 读表盘 对象是一个封装体,在其中封装了该对象的属性和操作。通过限制对属性和操作的访问权限,可以将属性“隐藏”在对象内部,对外提供一定的接口,在对象之外只能通过接口对对象进行操作。 C++通过建立数据类型——类来支持封装和数据隐藏。封装性增加了对象的独立性,从而保证了数据的可靠性。一个定义完好的类可以作为独立模块使用。 计算机科学与技术学院 张淼

272 继承与派生 2 “面向对象”程序设计的特点 以汽车为例看客观世界描述事物的方式: 面向对象程序设计提供了类似的机制: 汽车 客车 货车
2 “面向对象”程序设计的特点 继承与派生 汽车 以汽车为例看客观世界描述事物的方式: 载人 载货 面向对象程序设计提供了类似的机制: 客车 货车 当定义了一个类后,又需定义一个新类,这个新类与原来的类相比,只是增加或修改了部分属性和操作,这时可以用原来的类派生出新类,新类中只需描述自己所特有的属性和操作。 小,速度快 大,速度慢 小轿车 大客车 新类称为子类或派生类,原来的类称为基类。派生可以一直进行下去,形成一个派生树。 继承性大大简化了对问题的描述,大大提高了程序的可重用性,从而提高了程序设计、修改、扩充的效率。 计算机科学与技术学院 张淼

273 语文、数学、英语、政治、物理、化学、生物
“面向对象”程序设计的特点 多态性 多态性指,同一个消息被不同对象接收时,产生不同结果,即实现同一接口,不同方法。 高中生 语文、数学、英语、政治、物理、化学、生物 计 算平均成绩 计算机科学与技术学院 张淼

274 多态性 “面向对象”程序设计的特点 多态性指,同一个消息被不同对象接收时,产生不同结果,即实现同一接口,不同方法。
大学生 高数、英语、计算机、线性代数 计 算平均成绩 计算机科学与技术学院 张淼

275 类和对象 计算机科学与技术学院 张淼

276 类 类的定义 class 类名 { <成员描述> }; 说明:1、类名代表所定义的类的名字,用标识符表示。
2、成员描述给出该类的对象所有的成员的说明,它 包括成员函数和数据成员。 3、在类定义的成员描述中还包含对成员的访问控制。 计算机科学与技术学院 张淼

277 memset Sets buffers to a specified character.
void *memset( void *dest, int c, size_t count ); dest:Pointer to destination c:Character to set count:Number of characters 计算机科学与技术学院 张淼

278 一个类中的方法可以直接访问同类中的任何成员。
计算机科学与技术学院 张淼

279 类的定义示例 定义一个Student类 class Student { private: //访问控制说明
int number , score; public: //访问控制说明 void init() number = 1001; score = 100; } void show() cout<<number<<endl<<score<<endl; }; 计算机科学与技术学院 张淼

280 数据成员 class Date { …… private://访问控制说明 int year,month,day; //数据成员说明 };
类定义中的数据成员说明描述了类的对象所包含的数据的类型,它们可以是常量成员和变量成员。 class Date { …… private://访问控制说明 int year,month,day; //数据成员说明 }; 计算机科学与技术学院 张淼

281 数据成员注意点: class A { int x=0; //error const double y=0.0 //error };
一般来说,类定义中描述的数据成员属于类的成员,在创建对象前,类中说明的数据成员并不占有内存空间。因此,在类定义中说明数据成员(某些静态数据成员除外)时不能给它们赋初值,它们的初始化应在类的构造函数中指出。 class A { int x=0; //error const double y=0.0 //error }; 计算机科学与技术学院 张淼

282 数据成员注意点: 数据成员的类型可以是任意的C++类型(包括类,void除外)。但是在说明一个数据成员的类型时,如果未见到相应的类型定义或相应的类型未定义完,则该数据成员的类型只能是这些类型的指针或引用类型。 class A;//A是在程序其他地方定义的类,这里是声明。 class B { A a;//error ,未见A的定义 B b;//error,B还未定义完 A *p; B *q; A &aa; B &bb; }; 计算机科学与技术学院 张淼

283 成员函数 成员函数描述对类定义中的数据成员所能实施的操作。成员函数的说明格式与非成员函数(称为全局函数)的定义或声明相同。成员函数的定义可以放在类定义中,也可以放在类定义外。 计算机科学与技术学院 张淼

284 成员函数 方法一 类内声明,类外定义 class Student { private: int number,score; public:
void init(); }; void Student::init() number = 1001; score = 100; } 类外定义的成员函数需指 明其属于哪个类,以区别 全局函数或其它类的成员 函数 计算机科学与技术学院 张淼

285 成员函数的定义 方法二 内联函数的隐式声明——类内定义成员函数。 class Student { private:
int number,score; public: void init() number = 1001; score = 100; } }; 计算机科学与技术学院 张淼

286 成员函数的定义 方法二 内联函数的显示声明 class Student { private: int number,score;
public: void init(); }; inline void Student::init() number = 1001; score = 100; } 计算机科学与技术学院 张淼

287 类成员函数是可以重载的(析构函数除外),它遵循一般函数名重载规则。
class A { …… public: void f(); int f(int i); double f(double d); }; 计算机科学与技术学院 张淼

288 对象 类属于类型范畴的程序实体,它存在于静态的程序(运行前的程序)中。而动态的面向对象程序(运行中的程序)则由对象构成,程序的执行是通过对象之间互相发送消息来实现的,当对象接收到一条消息后,它将调用对象类中定义的某个成员函数来处理这条消息。 计算机科学与技术学院 张淼

289 对象的创建和标识 直接方式 类名 对象名; 今后,在定义变量时,如果变量的类型为类,则称该变量为对象,用变量名来标识相应的对象。所有在函数外定义的对象称为全局对象。在函数(或复合语句)内定义的对象称为局部对象。关于全局对象和局部对象的生存期和对象标识符的作用域的规定和普通变量相同。成员对象将随着包含它的对象的创建而创建,随其消亡而消亡。成员对象标识符的作用域为它所在的类。 计算机科学与技术学院 张淼

290 对象指针 和基本数据类型的变量一样,每一个对象在初始化之后会在内存中占据一定空间。对象所占据的内存空间只是用于存放数据成员,函数并不在每个对象中都存储副本。 对象指针就是用于存放对象地址的变量。对象指针遵循一般变量指针的各种规则。 类名 *对象指针名; Point *p_Point; Point p1; p_Point=&p1; 计算机科学与技术学院 张淼

291 对象的操作 对象名.数据成员名 对象名.成员函数名(实参列表) 对象指针->数据成员名 对象指针->成员函数名(实参列表)
对象指针->成员名 等价: (*对象指针)成员名 计算机科学与技术学院 张淼

292 成员的访问控制:信息隐藏 在C++定义中,可以用访问控制修饰符public、private或protected来描述对类成员的访问限制。
public:public成员的访问不受限制,在程序中的任何地方都可以访问一个类的public成员。 private:private成员只能在本类(本类的成员函数)和友元中访问。 protected:protected成员只能在本类、派生类和友元中访问。 计算机科学与技术学院 张淼

293 在类定义中可以有多个public、private和protected访问控制说明,它们出现的先后顺序无关。C++的默认访问控制是private。
一般情况下,类的数据成员和在类内部使用的成员函数应该指定为private,只有提供给外界使用的成员函数才指定为public。指定为public的成员构成了类与外界的一种接口。操作一个对象时,只能通过访问对象类中的public成员实现。 计算机科学与技术学院 张淼

294 对象的创建和标识 间接方式 间接创建和标识对象是指在程序运行时刻,通过new操作来创建对象。所创建的对象称为动态对象,其内存空间在程序的堆区中。动态对象用delete操作撤消(即使之消亡)。动态对象需要通过指针变量来标识。 计算机科学与技术学院 张淼

295 单个动态对象的创建与撤消。 A *p; p=new A; …… delete p; A *p;
p=(A *)malloc(sizeof(A)); …… free (p); 计算机科学与技术学院 张淼

296 动态对象数组的创建与撤消 A *p; p=new A[100]; …… delete []p; A *p;
p=(A *)malloc(sizeof(A)*100); …… free(p); 计算机科学与技术学院 张淼

297 类作用域 类定义构成一个作用域:类作用域,其中的标识符局部于类定义,它们可以与类定义外的全局标识符或其他类定义中的标识符相同。在类定义外使用类定义中的标识符时,需通过对象名受限或类名受限。在类定义中使用与局部标识符同名的全局标识符时,需要在全局标识符前面加上全局域分辨符(::)来实现。 计算机科学与技术学院 张淼

298 void init(int number,int score) Student::number = number;
class Student { private: int number; int score; public: void init(int number,int score) Student::number = number; Student::score = score; } }; void main() Student s1; s1.init(); 计算机科学与技术学院 张淼

299 应在下列程序划线处填入的正确语句是( )。
应在下列程序划线处填入的正确语句是( )。 void init(); class Student {private: int number; public: void init(int number); void f(); }; void Student::init(int number) { _____________= number; } void Student::f() {_____________ //调用全局函数init 计算机科学与技术学院 张淼

300 A. this->number; ,::init(); B. this::number; ,::init();
C. Student::number; ,init(); D. student->number; ,init(); 计算机科学与技术学院 张淼

301 A a,b; class A { public: void f(); void g(int i) {x=i; f(); } private:
int x,y,z; }; A a,b; a b a.x b.x a.y b.y a.z b.z g(int i) 计算机科学与技术学院 张淼

302 this指针 a.g(3); A::g(&a,3);
每个成员函数都有一个隐藏的指针类型的形参this,其类型为:<类名> *const this;成员函数中对类成员的访问都是通过this指针进行的。 a.g(3); A::g(&a,3); void g(int i) {x=i; f(); } void g(A *const this,int i) {this->x=i; this->f(); } 计算机科学与技术学院 张淼

303 void init(int number,int score) this->number = number;
class Student { private: int number; int score; public: void init(int number,int score) this->number = number; this->score = score; } }; void main() Student s1; s1.init(); 计算机科学与技术学院 张淼

304 要求:调用a.f()时,在A::f中调用func(&a); 调用b.f()时,在A::f中调用func(&b);
void func(A * p) {…} class A { int x; public: void f(){func(?);} void g(int i){x=i;f();} }; A a,b; a.f(); b.f(); 计算机科学与技术学院 张淼

305 小技巧:在以后的MFC编程中,如果在成员函数中想调用同类中的某个成员,可以使用VC++提供的自动列出成员函数功能,使用this->,VC++将列出该类中的所有成员,我们可以从列表中选择我们想调用的成员。 自动列出成员函数功能,可以提高编写速度,减少拼写错误。我们经常不能完全记住某个函数的完整拼写,但却能够从列表中辨别出该函数,自动列出成员函数的功能在这时就显得更加有用了。事实上,在各种IDE编程环境中,我们通常都不可能记住也没有必要记住所有的函数,只要将常用的函数记住,其他不常用的函数只要记住其大概的写法和功能,在调用该函数时可以从自动列出成员函数中选取,这样可以大大节省我们的学习时间。我们不用花费大量的时间去死记硬背许多函数,利用自动列出成员函数功能和帮助系统,就能够在编程时顺利地使用这些函数,等用的次数多了,也就在不知不觉中完全掌握了这些函数。 计算机科学与技术学院 张淼

306 构造函数 什么是构造函数 构造函数最重要的作用是创建对象本身 。
构造函数是一种特殊的成员函数,它主要用于为对象分配存储空间并对其成员进行初始化. C++规定,每个类必须有一个构造函数,没有构造函数,就不能创建任何对象。 计算机科学与技术学院 张淼

307 C++又规定,如果一个类没有提供任何的构造函数,则C++提供一个默认的构造函数(由C++编译器提供),这个默认的构造函数是一个不带参数的构造函数,该构造函数的函数体为空,它只负责创建对象,而不做任何的初始化工作。 只要一个类定义了一个构造函数,不管这个构造函数是否是带参数的构造函数,C++就不再提供默认的构造函数。也就是说,如果为一个类定义了一个带参数的构造函数,还想要无参数的构造函数,则必须自己定义。 计算机科学与技术学院 张淼

308 构造函数的特点 构造函数的名称必须与类名相同 构造函数没有返回值,可以有参数,可以重载
构造函数一般是public的,但有时也把构造函数声明为私有的(private),其作用是限制创建该类对象的范围,这时只能在本类和友元类中创建该对象。 程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数。 计算机科学与技术学院 张淼

309 小知识 设计模式 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。设计模式就象经典的棋谱,不同的棋局,我们用不同的棋谱,免得自己再去思考和摸索。 Singleton模式是做为"全局变量"的替代品出现的。所以它具有全局变量的特点:全局可见、贯穿应用程序的整个生命期,它也具有全局变量不具备的性质:同类型的对象实例只可能有一个。 计算机科学与技术学院 张淼

310 构造函数示例 把Student类中的函数init改成构造函数 构造函数也可以“类内声 明,类外定义” class Student {
private: int number; int score; public: Student(int number,int score) Student::number = number; Student::score = score; } }; void main() Student s1(1002,90); //创建一个学号为1002成绩为90的学生 Student s2; //错误,类中没有无参构造函数 构造函数也可以“类内声 明,类外定义” 计算机科学与技术学院 张淼

311 在创建一个对象时,对象类的构造函数会被自动调用来对该对象进行初始化。至于调用对象类的哪个构造函数,这可以在创建对象的时候指定。
计算机科学与技术学院 张淼

312 A a3(“abcd”); 或A a2=A(“abcd”); 或A a2=“abcd”;
Class A { …… public: A(); A(int i); A(char *p); } A a1; 或 A a1=A(); A a1(); A a2(1); 或A a2=A(1); 或A a2=1; A a3(“abcd”); 或A a2=A(“abcd”); 或A a2=“abcd”; A a[4]; A b[5]={A(),A(1),A(“abcd”),2,”xyz”}; A *p1=new A; A *p2=new A(2); A *p3=new A(“xyz”); A *p4=new A[20]; 计算机科学与技术学院 张淼

313 析构函数 什么是析构函数 当一个对象生命周期结束时,其所占有的内存空间就要被回收,这个工作就由析构函数来完成。
析构函数是“反向”的构造函数,析构函数不允许有返回值,更重要的是析构函数不允许带参数,并且一个类中只能有一个析构函数. 析构函数的作用正好与构造函数相反,对象超出其作用范围,对应的内存空间被系统收回或被程序用delete删除时,析构函数被调用. 计算机科学与技术学院 张淼

314 根据析构函数的这种特点,我们可以在构造函数中初始化对象的某些成员变量,给其分配内存空间(堆内存),在析构函数中释放对象运行期间所申请的资源.
计算机科学与技术学院 张淼

315 析构函数的特点 析构函数的名称与类名相同,前面加上“~” 析构函数没有返回值,没有参数,不能重载 析构函数不能被显式调用
当对象被撤销时,系统自动调用析构函数 类中未定义析构函数,系统会自动生成默认的函数体为空的析构函数 计算机科学与技术学院 张淼

316 析构函数示例 在构造函数申请空间,析构函数释放空间 析构函数同样可以“类内 声明,类外定义” class Student {
private: char *name;int number;int score; public: Student(char *name,int number,int score) this->name = new char[strlen(name) + 1]; strcpy(this->name,name); this->number = number; this->score = score; } ~Student() delete name; }; 析构函数同样可以“类内 声明,类外定义” 计算机科学与技术学院 张淼

317 拷贝构造函数 什么是拷贝构造函数 拷贝构造函数是一种特殊的构造函数,它的功能是用一个已知的对象来初始化一个被创建的同类对象。
拷贝构造函数实际上也是构造函数,具有一般构造函数的所有特性,它是在初始化时被调用来将一个已知对象的数据成员的值逐值拷贝给正在创建的一个同类的对象。 默认情况下系统自动创建拷贝构造函数 计算机科学与技术学院 张淼

318 如果在类定义中没有给出拷贝构造函数,则编译系统将会隐式地为其提供一个拷贝构造函数,该拷贝构造函数的行为是:逐个成员拷贝初始化。对于普通成员,它采用通常的初始化操作;对于成员对象,则调用成员对象类的拷贝构造函数来实现成员对象的初始化。 计算机科学与技术学院 张淼

319 拷贝构造函数的定义 类名(const 类名 &object) { …… } 计算机科学与技术学院 张淼

320 在Student类中添加拷贝构造函数 class Student { private: int number; int score;
public: Student(const Student &student) number = student.number; score = student.score; } }; 计算机科学与技术学院 张淼

321 以下三种情况将调用拷贝构造函数 定义对象时 A a1; A a2(a1);A a2=a1;A a2=A(a1); 把对象作为值参数传给函数
void f(A x); A a; f(a); //调用时将创建形参对象x,并调用拷贝构造函数(用对象a)对其初始化。 把对象作为返回值 计算机科学与技术学院 张淼

322 return a;//创建一个A类临时对象,并调用拷贝构造函数(用对象a)对其初始化。
A f() {A a; …… return a;//创建一个A类临时对象,并调用拷贝构造函数(用对象a)对其初始化。 } void main() {A b; b=f(); 计算机科学与技术学院 张淼

323 char *name;int number;int score; public:
class Student { private: char *name;int number;int score; public: Student(char *name,int number,int score) this->name = new char[strlen(name) + 1]; strcpy(this->name,name); this->number = number; this->score = score; } ~Student() delete name; }; 计算机科学与技术学院 张淼

324 Student stu1(“zhangsan”,101,90); Student stu2(stu1);
计算机科学与技术学院 张淼

325 深拷贝和潜拷贝问题 类体内的成员需要动态开辟内存的时我们应自定义拷贝构造函进行深拷贝,以防止潜拷贝带来的堆内存的所属权产生混乱,从而造成析构错误 s1 张三 *name number score s2 *name number score 栈空间 S2 = S1 堆空间 计算机科学与技术学院 张淼

326 void f(Student s) {…… } A a; f(a); 计算机科学与技术学院 张淼

327 char *name;int number;int score; public:
class Student { private: char *name;int number;int score; public: Student(char *name,int number,int score) this->name = new char[strlen(name) + 1]; strcpy(this->name,name); this->number = number; this->score = score; } ~Student() delete name; Student(const Student &a) number=a.number; score=a.score; name=new char[strlen(a.name)+1]; strcpy(name,a.name); }; 计算机科学与技术学院 张淼

328 const常量、引用的初始化问题? 计算机科学与技术学院 张淼

329 y=1;//error,y是常量成员,其值不能改变 } };
Class A { int x; const int y=1;//error int &z=x;//error public: A() {x=0; y=1;//error,y是常量成员,其值不能改变 } }; 计算机科学与技术学院 张淼

330 成员初始化表 class A { int x; const int y; int &z; public: A():z(x),y(1)
} }; 计算机科学与技术学院 张淼

331 class A { int x; const int y; int &z; public: A():z(x),y(1),x(0) { }
}; 计算机科学与技术学院 张淼

332 成员初始化表中成员初始化的书写次序并不决定它们的初始化次序,数据成员的初始化次序由它们在类定义中的说明次序来决定。
当类中有常量数据成员或引用数据成员时,如果类中定义了构造函数,则一定要在定义的构造函数的成员初始化表中对它们进行初始化,如果类中没有定义构造函数,则编译程序不会给该类生成一个默认构造函数。因此,这样的类程序是不能用于创建对象的。 计算机科学与技术学院 张淼

333 系统提供的隐式拷贝构造函数会去调用成员对象的拷贝构造函数。自定义的拷贝构造函数不会去调用成员对象的拷贝构造函数,必须要在成员初始化列表中显示地指出。
计算机科学与技术学院 张淼

334 class A {…… }; class B { int z; A a; public: B(); B(const B& b):a(b.a)
{z=b.z} 计算机科学与技术学院 张淼

335 程序运行的某个时刻,一个对象的所有数据成员的值反映了这个对象在该时刻的状态。在不同时刻,对象的状态可能是不一样的,对象状态的改变往往是由于对象处理了一条消息(某个成员函数被调用)。但是并不是每条消息都会导致对象状态的变化。有些消息只是获取对象在某时刻的状态。 计算机科学与技术学院 张淼

336 const成员函数 在定义一个成员函数时,可以给它加上一个const说明,表示它是一个获取对象状态的成员函数。例如: class A {……
void f() const {……} }; 在const成员函数体中不能修改数据成员的值。 计算机科学与技术学院 张淼

337 class A class A { int x; { int x; char *p; char *p; public: public:
void f() const {x=10;//error p=new char[20];//error } class A { int x; char *p; public: void f() const { strcpy(p,”abcd”); *p=‘A’; } 计算机科学与技术学院 张淼

338 只有常成员函数才有资格操作常对象。 class A { int x,y; public: void f() const {…}
void g(){…} }; const A a; a.f(); //OK a.g(); //error 计算机科学与技术学院 张淼

339 类成员是其它类的对象—成员对象 一个类的成员可以是另外一个类的对象 参见工程0101a class Student { private:
char *name; int number; int score; Teacher teacher; }; class Teacher { private: char *name; }; 参见工程0101a 计算机科学与技术学院 张淼

340 成员对象的初始化 对于类中的数据成员,其类型可以是另一个类,也就是说一个对象可以包含另一个对象(称为成员对象)。 class B
{ int n; A a; public: B() {n=0;} B(int n1) {n=n1;} }; B b1,b2(1); class A { int m; public: A() {m=0;} A(int m1) {m=m1;} }; 计算机科学与技术学院 张淼

341 在创建包含成员对象的对象时,对成员对象的初始化是由成员对象类的构造函数来实现的。默认情况下,成员对象由成员对象类的默认构造函数进行初始化。
计算机科学与技术学院 张淼

342 class B { int n; A a; class A public: { int m; B() {n=0;} public:
B(int n1) {n=n1;} }; B b1,b2(1); class A { int m; public: A() {m=0;} A(int m1) {m=m1;} }; 计算机科学与技术学院 张淼

343 如需要调用成员对象的非默认构造函数对成员对象进行初始化,则要在包含成员对象的类的构造函数成员初始化表指出。
计算机科学与技术学院 张淼

344 class B { int n; class A A a; { int m; public: public: B() {n=0;}
B(int n1):a(n1) {n=n1;} }; B b1,b2(1); class A { int m; public: A() {m=0;} A(int m1) {m=m1;} }; 计算机科学与技术学院 张淼

345 class B { int n; class A A a; { int m; public: public: B():a(0) {n=0;}
B(int n1):a(n1) {n=n1;} }; B b1,b2(1); class A { int m; public: A(int m1) {m=m1;} }; 计算机科学与技术学院 张淼

346 静态成员 同一个类的不同对象需要共享数据,怎么办? 全局变量? 计算机科学与技术学院 张淼

347 静态数据成员 静态数据成员的初始化必须在类的外部进行。 数据类型 类名::静态数据成员名=值; class A
数据类型 类名::静态数据成员名=值; class A { static int shared;//声明 int x,y; public: A(){x=y=0;} void increase_all(){…} }; int A::shared=0;//定义 A a1,a2; shared: a1 a2 a1.x a2.x a1.y a2.y 计算机科学与技术学院 张淼

348 类的静态成员与该类的对象存在与否没有关系.
静态数据成员具有静态生存期,不属于任何一个对象。 类的静态成员与该类的对象存在与否没有关系. 计算机科学与技术学院 张淼

349 静态成员函数 静态成员函数只能访问静态成员。 计算机科学与技术学院 张淼

350 cout<<x;//error }
class A {public: static void f(); private: int x; } ; void A::f() { cout<<x;//error } 计算机科学与技术学院 张淼

351 cout<<x;//error cout<<a.x; }
class A {public: static void f(A a); private: int x; } ; void A::f(A a) { cout<<x;//error cout<<a.x; } 计算机科学与技术学院 张淼

352 静态成员函数没有隐藏的形式参数this. 计算机科学与技术学院 张淼

353 除了在类中访问静态成员外,还可以在类的外部访问public静态成员,这时有两种访问方式:
静态成员的使用 除了在类中访问静态成员外,还可以在类的外部访问public静态成员,这时有两种访问方式: 通过对象访问。 通过类名访问。 类的静态成员与该类的对象存在与否没有关系。 计算机科学与技术学院 张淼

354 友元(friend) 为什么需要友元 友元的分类 声明友元的关键字
为了让类外的普通函数和其它类的成员函数能够对类中的保护和私有数据进行操作,C++提供了一种称为友元的信任机制 友元的分类 友元函数(普通函数) 友元成员(类的成员函数) 友元类 声明友元的关键字 friend 计算机科学与技术学院 张淼

355 friend void func();//友元函数 friend class B;//友元类
class A { friend void func();//友元函数 friend class B;//友元类 friend void C::f();//友元类成员函数 }; 计算机科学与技术学院 张淼

356 友元函数 在类里声明一个普通函数,在前面加上friend修饰,那么这个函数就成了该类的友元,可以访问该类的一切成员
一个普通函数可以是多个类的友元函数 计算机科学与技术学院 张淼

357 使用友元函数计算两点距离 #include <iostream.h> #include <math.h>
class Point {public: //外部接口 Point(int xx=0, int yy=0) {X=xx;Y=yy;} int GetX() {return X;} int GetY() {return Y;} friend float Distance(Point &a, Point &b); private: //私有数据成员 int X,Y; }; 计算机科学与技术学院 张淼

358 double Distance( Point& a, Point& b) { double dx=a.X-b.X;
double dy=a.Y-b.Y; return sqrt(dx*dx+dy*dy); } int main() { Point p1(3.0, 5.0), p2(4.0, 6.0); double d=Distance(p1, p2); cout<<"The distance is "<<d<<endl; return 0; 在友元函数Distanc的函数体中可以访问Point类对象 a,b 的私有成员 计算机科学与技术学院 张淼

359 友元成员 一个类的成员函数也可以是另一个类的友元,从而可以使得一个类的成员函数可以操作另一个类的数据成员 计算机科学与技术学院 张淼

360 友元类 整个类也可以是另一个类的友元,该友元称为友元类 友元类的每个成员函数都可以访问另一个类的所有成员 计算机科学与技术学院 张淼

361 友元类举例 class A { friend class B; public: void Display()
{cout<<x<<endl;} private: int x; } class B { public: void Set(int i); void Display(); A a; }; B类声明为A类的友元,意味着B类的所有函数包括构造函数和析构函数)成为A类的友元。 计算机科学与技术学院 张淼

362 void B::Set(int i) { a.x=i; } void B::Display() a.Display();
在B类的set()函数中,访问了A类对象a的私有成员x 计算机科学与技术学院 张淼

363 友元不具有传递性,即假设B是A的友员,C是B的友员,如果没有显示指出C是A的友员,则C不是A的友元。
一个类的友元可以访问该类的所有成员。 计算机科学与技术学院 张淼

364 继承和派生 计算机科学与技术学院 张淼

365 类的继承与派生 保持已有类的特性而构造新类的过程称为继承。 在已有类的基础上新增自己的特性而产生新类的过程称为派生。
被继承的已有类称为基类(或父类)。 派生出的新类称为派生类。 计算机科学与技术学院 张淼

366 继承与派生问题举例 计算机科学与技术学院 张淼

367 继承与派生问题举例 计算机科学与技术学院 张淼

368 继承与派生问题举例 计算机科学与技术学院 张淼

369 继承与派生问题举例 计算机科学与技术学院 张淼

370 继承与派生的目的 继承的目的:实现代码重用。 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。
计算机科学与技术学院 张淼

371 X Y A Z B C 多继承 单继承 计算机科学与技术学院 张淼

372 单继承 多继承 多重派生 多层派生 派生类只从一个基类派生。 派生类从多个基类派生。 由一个基类派生出多个不同的派生类。
派生类又作为基类,继续派生新的类。 计算机科学与技术学院 张淼

373 单继承 单继承的定义 继承方式 公有派生(public) 私有派生(private) 保护派生(protected)
class 派生类名 : 继承方式 基类名 { 派生类新定义成员 } 计算机科学与技术学院 张淼

374 派生类除了拥有基类所有的成员(基类的构造函数、析构函数和赋值操作符重载函数除外)外,也可以具有新的成员。
计算机科学与技术学院 张淼

375 class A class B:public A { int x,y; { int z; public: public: void f();
void g(); }; class B:public A { int z; public: void h(); }; B b; b.f(); b.g(); b.h(); b b.x b.y b.z 计算机科学与技术学院 张淼

376 派生类中可以给出新的成员,也可以对基类的成员进行重定义。如果在派生类中对基类的某个成员进行了重定义,则在派生类中对该成员的访问是针对派生类中重定义的成员。
计算机科学与技术学院 张淼

377 class A class B:public A { int x,y; { int z; public: public: void f();
void g(); }; class B:public A { int z; public: void f(); void h(); }; B b; b.f();//B类中的f b.g(); b.h(); 计算机科学与技术学院 张淼

378 即使派生类定义了与基类同名但参数不同的成员函数,基类的同名函数在派生类的作用域也是不可见的。
派生类成员名的作用域嵌套在基类作用域中。对于基类的一个成员,如果派生类中没有定义与其同名的成员,则该成员名在派生类的作用域内可见。否则,该成员名在派生类的作用域内不可见,如果使用,必须用基类名受限。 即使派生类定义了与基类同名但参数不同的成员函数,基类的同名函数在派生类的作用域也是不可见的。 计算机科学与技术学院 张淼

379 class A class B:public A { int x,y; { int z; public: public: void f();
void g(); }; class B:public A { int z; public: void f(); void h() {A::f(); f();} }; B b; b.f(); b.A::f(); 计算机科学与技术学院 张淼

380 class A class B:public A { int x,y; { int z; public: public: void f();
void g(); }; class B:public A { int z; public: void f(int); void h() {f(1); f();//error A::f(); }; B b; b.f(1); b.f();//error b.A::f(); 计算机科学与技术学院 张淼

381 定义派生类时一定要见到基类的定义,否则编译程序无法确定派生类对象需要占多大内存空间以及派生类中对基类成员的访问是否合法。
派生类不能直接访问基类的私有成员,必须通过基类的非私有成员访问基类的私有成员。 计算机科学与技术学院 张淼

382 一个类可以创建对象,也可以用来定义派生类,即一个类有两种用户:实例用户和派生类。
class A {…… }; class B:public A } void f() {A a; …… 计算机科学与技术学院 张淼

383 在C++中通过引进protected成员访问控制,可以使派生类使用protected成员,但是protected成员不能被基类的实例用户使用。
这样,一个类就存在两个对外接口,一个接口由类的public成员构成,它提供给实例用户使用;另一个接口由类的public和protected成员构成,该接口提供给派生类使用。 计算机科学与技术学院 张淼

384 在C++中,派生类拥有基类的所有成员。那么基类的成员变成派生类的什么成员呢?public,private还是protected
计算机科学与技术学院 张淼

385 继承方式 基类特性 派生类特性 公有继承 public protected private 不可访问 私有继承 保护继承
计算机科学与技术学院 张淼

386 b.f();//error,f是B的protected成员 b.g();//error,g是B的protected成员
class C:public B {public: void r() {q();//ok f();//ok g();//ok h();//error } }; class A {public: void f(); protected: void g(); private: void h(); }; B b; b.q();//ok b.f();//error,f是B的protected成员 b.g();//error,g是B的protected成员 b.h();//error,h是B的不可直接访问成员 class B:protected A {public: void q();//q对A类成员的访问不受B的继承方式影响,除了h,其他都能访问。 }; 计算机科学与技术学院 张淼

387 继承方式决定了派生类的实例(对象)用户和派生类对基类成员的访问限制。
计算机科学与技术学院 张淼

388 继承方式的调整 在任何继承方式中,除了基类的private成员,都可以在派生类中分别调整其访问控制。调整时采用:
[public:|protected:|private:] <基类名>::<基类成员名>; 计算机科学与技术学院 张淼

389 b.f2();b.g2();b.f3();b.g3();//error
class A { public: void f1(); void f2(); void f3(); protected: void g1(); void g2(); void g3(); }; class B:private A { public: A::f1; A::g1; protected: A::f2; A::g2; private: A::f3; A::g3; }; B b; b.f1();b.g1();//ok b.f2();b.g2();b.f3();b.g3();//error 计算机科学与技术学院 张淼

390 谁来初始化派生类对象? 计算机科学与技术学院 张淼

391 派生类对象的初始化由基类和派生类共同完成:基类的数据成员由基类的构造函数初始化,派生类的数据成员由派生类的构造函数初始化。
在创建派生类的对象时,派生类的构造函数在进入其函数体之前会调用基类的构造函数。默认情况下是调用基类的默认构造函数,如果要调用基类的非默认构造函数,则必须在派生类构造函数的成员初始化表中指出。 参见工程0103 计算机科学与技术学院 张淼

392 B b2(1);//A::A(),B::B(int) B b3(1,2);//A::A(int),B::B(int,int)
class A { int x; public: A(){x=0;} A(int i){x=i;} }; B b;//A::A(),B::B() B b2(1);//A::A(),B::B(int) B b3(1,2);//A::A(int),B::B(int,int) class B:public A { int y; public: B(){y=0;} B(int i){y=i;} B(int i,int j):A(i) {y=j;} }; 派生类构造函数(参数表) : 基类构造函数(参数表) { …… } 计算机科学与技术学院 张淼

393 对于析构函数,先执行派生类的析构函数,而后执行基类的析构函数。 基类的构造函数和析构函数不能被派生类继承。
计算机科学与技术学院 张淼

394 派生类构造函数和析构函数的执行顺序 基类的构造函数 派生类的构造函数 派生类的析构函数 基类的析构函数 计算机科学与技术学院 张淼

395 一般情况下,类定义及函数原型的声明放在头文件(.h)中,类中成员函数的实现则放在源文件(.cpp)中。
计算机科学与技术学院 张淼

396 C++源程序结构 三个组成部分 存储和管理方式 类的定义及函数原型的声明 类中成员函数的实现 类的使用文件 类的定义及函数原型的声明(.h)
包括数据成员和成员函数的声明 类中成员函数的实现(.cpp) 成员函数的实现 主函数 参见工程0101 计算机科学与技术学院 张淼

397 定义一个类时,在定义中加入条件编译指令防止重复包含。
#ifndef student_H_H #define student_H_H class Student { public: void init(int number,int score); void show(); private: int number; int score; }; #endif 计算机科学与技术学院 张淼

398 多继承 什么是多继承 多继承的声明 C++允许为一个派生类指定多个基类,这样的继承结构称为多继承
class 派生类名 : 继承方式1 基类名1, … , 继承方式n 基类名n { …… } 计算机科学与技术学院 张淼

399 多重继承的构造函数和析构函数 构造函数的执行顺序 析构函数的执行顺序 基类构造函数 派生类构造函数
在多个基类之间按照按照它们被继承时声明的顺序(从左向右)执行。 析构函数的执行顺序 析构函数的执行顺序与构造函数相反 计算机科学与技术学院 张淼

400 class c:public A,public B {… public: void func() { f(); } }; class A
void g(); }; class B {… public: void f(); void h(); }; C c; c.f(); 计算机科学与技术学院 张淼

401 class c:public A,public B {… public: void func() { A::f(); B::f(); }
}; class A {… public: void f(); void g(); }; class B {… public: void f(); void h(); }; C c; c.A::f(); c.B::f(); 计算机科学与技术学院 张淼

402 二义性问题举例 B B1 B2 C class B { public: class C : public B1,public B2
int b; } class B1 : public B { private: int b1; class B2 : public B int b2; }; class C : public B1,public B2 { public: int f(); private: int d; } B B1 B2 C 计算机科学与技术学院 张淼

403 下面的访问是二义性的: C c; c.b c.B::b 下面是正确的: c.B1::b c.B2::b 计算机科学与技术学院 张淼

404 派生类C的对象的存储结构示意图: b b1 b2 d B类成员 B1类成员 B2类成员 C类对象 计算机科学与技术学院 张淼

405 虚基类 虚基类的引入 用于有共同基类的场合 声明 以virtual修饰说明基类 例:class B1:virtual public B 作用
主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题. 为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝 注意: 在第一级继承时就要将共同基类设计为虚基类。 计算机科学与技术学院 张淼

406 虚基类举例 class B{ private: int b;};
class B1 : virtual public B { private: int b1;}; class B2 : virtual public B { private: int b2;}; class C : public B1, public B2{ private: float d;} 下面的访问是正确的: C cobj; cobj.b; 计算机科学与技术学院 张淼

407 虚基类的派生类对象存储结构示意图: B B1 B2 C b1 b2 d B1类成员 B2类成员 C类对象 b B类成员
计算机科学与技术学院 张淼

408 虚基类的构造函数由最新派生出的类的构造函数调用。 虚基类的构造函数优先非虚基类的构造函数执行。
计算机科学与技术学院 张淼

409 对于全局对象,程序一开始,其构造函数就先被函数执行。程序即将结束前其析构函数执行。(MFC application object)
对于局部对象,当对象诞生时,其构造函数被函数执行。程序流程将离开该对象的存活范围(以致对象将毁灭)时,其析构函数执行。 对于静态对象,当对象诞生时其构造函数被函数执行。当程序将结束时(此对象因而遭致毁灭)其析构函数被执行,但比全局对象的析构函数早一步执行。 对于以new方式产生出来的局部对象,当对象诞生时其构造函数被函数执行。析构函数则在对象被delete时执行。 计算机科学与技术学院 张淼

410 在C++中,有四种方法可以产生一个对象:
堆栈(stack)中 在堆(heap)中 产生一个全局对象 产生一个局部静态对象 计算机科学与技术学院 张淼

411 CH2.3 C++面向对象程序设计进阶 多态和虚函数 运算符重载 输入输出流 模板 计算机科学与技术学院 张淼

412 CH 2.3.1 多态和虚函数 赋值兼容规则:需要基类对象的任何地方都可以使用公有派生类的对象来替代. 替代包括:
派生类的对象可以赋值给基类对象. 派生类的对象可以初始化基类的引用. 派生类的地址可以赋给指向基类的指针. 计算机科学与技术学院 张淼

413 student对象内存布局 this指针 person对象内存 student对象的内存 student继承部分
计算机科学与技术学院 张淼

414 联编:将一个函数的调用与相应的函数体的代码相连接称为函数联编。
静态联编:在编译根据CShape* pShape[6]的静态类型类决定pShape[i]->display()属于哪一个类。由于pShape[i] 的静态类型是CShape* ,所以确定display()是CShape::shout. 动态联编:在运行时,根据pShape[i]实际指向的对象类型来确定display()属于哪一个类。C++规定动态联编是在虚函数的支持下实现的。 计算机科学与技术学院 张淼

415 虚函数 虚函数是成员函数而且是非static的成员函数。说明虚函数的方法如下: virtual 类型说明 函数说明(参数表)
重定义指对派生类中定义的成员函数,其函数名、参数个数和类型以及返回值类型与基类的虚成员函数相同。(当基类虚函数返回值类型是某个类或其指针或引用时,派生类中重定义的成员函数的返回值也可以是子类型,vc++不支持)。 计算机科学与技术学院 张淼

416 使用虚函数的注意事项 只有类的成员函数才能说明为虚函数 静态成员函数不能是虚函数 内联(inline)函数不能是虚函数 构造函数不能是虚函数
析构函数可以是虚函数,而且通常声名为虚函数 参见工程0106 计算机科学与技术学院 张淼

417 多态性 什么是多态性 编译时的多态性 运行时的多态性 不同对象收到相同消息时产生不同的动作 多态性的实现与函数联编有关
将一个函数的调用与相应的函数体的代码相连接称为函数联编 编译时的多态性 静态联编所支持的多态性称为编译时的多态性 通过函数重载或运算符重载实现 运行时的多态性 动态联编所支持的多态性称为运行时的多态性 通过虚函数的继承实现 计算机科学与技术学院 张淼

418 第三,要由成员函数来调用或者通过指针、引用来访问虚函数。
运行时的多态性需要满足三个条件: 首先,类之间应该满足赋值兼容规则。 其二,要声明虚函数。 第三,要由成员函数来调用或者通过指针、引用来访问虚函数。 如果使用对象名来访问虚函数,则联编在编译过程中就可以进行,而无需在运行过程中进行。 计算机科学与技术学院 张淼

419 B b;//B::B(),A::A()和A::f b.f();//B::f b.g();//B::g
class A {public: A() {f();} ~A(){…} virtual void f(); void g(); void h(){f();g();} }; class B:public A ~B(){…} void f(); A a;//A::A()和A::f a.f();//A::f a.g();//A::g a.h();//A::h,A::f,A::g B b;//B::B(),A::A()和A::f b.f();//B::f b.g();//B::g b.h();//A::h,B::f,A::g 计算机科学与技术学院 张淼

420 p->h();//A::h,A::f,A::g p=&b; p->f();//B::f p->A::f();//A::f
class A {public: A() {f();} ~A(){…} virtual void f(); void g(); void h(){f();g();} }; class B:public A ~B(){…} void f(); A *p; p=&a; p->f();//A::f p->g();//A::g p->h();//A::h,A::f,A::g p=&b; p->f();//B::f p->A::f();//A::f p->h();//A::h,B::f,A::g p=new B;// B::B(),A::A()和A::f delete p;//A::~A() 计算机科学与技术学院 张淼

421 基类构造函数中对虚函数的调用不采用动态绑定。
计算机科学与技术学院 张淼

422 纯虚函数和抽象类 什么是纯虚函数 什么是抽象类 纯虚函数的声明 纯虚函数只有函数的声明,函数的定义必须在其派生类中完成
声明纯虚函数的类不能实例化 什么是抽象类 声明有纯虚函数的类称为抽象类 纯虚函数的声明 virtual 返回值类型 函数名() = 0; 参见工程0108 计算机科学与技术学院 张淼

423 抽象类的主要作用是通过它为一个类族建立一个公共的接口,使他们能够更有效的发挥多态特性。
抽象类派生出新类后,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。 抽象类不能实例化,但可以声明一个抽象类的指针和引用。通过指针或引用,我们就可以指向并访问派生类对象,进而访问派生类的成员,这种访问是具有多态特征的。 计算机科学与技术学院 张淼

424 virtual void display()=0; }; class B1:public B0
void display(){cout<<“B1::display”;} class D1:public B1 void display(){cout<<“D1::display”;} void f(B0 *ptr) { ptr->display(); } void main() {B0 *p; B1 b1; D1 d1; p=&b1; fun(p);//B1::display p=&d1; fun(p);//D1::display 计算机科学与技术学院 张淼

425 关于纯虚函数和抽象类的描述中,( )是错误的。 A. 纯虚函数是一种特殊的虚函数,它没有具体的实现。 B. 抽象类是指具有纯虚函数的类。
关于纯虚函数和抽象类的描述中,(     )是错误的。 A. 纯虚函数是一种特殊的虚函数,它没有具体的实现。 B. 抽象类是指具有纯虚函数的类。 C. 一个基类中说明有纯虚函数,该基类的派生类一定不再是抽象类。 D. 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。 计算机科学与技术学院 张淼

426 下列各类函数中,( )不是类的成员函数。 A. 构造函数 B. 析构函数 C. 拷贝初始化构造函数 D. 友元函数
下列各类函数中,( )不是类的成员函数。 A. 构造函数             B. 析构函数               C. 拷贝初始化构造函数        D. 友元函数 计算机科学与技术学院 张淼

427 CH 2.3.2 运算符重载 例:实现复数的表示及其加法操作。
为了能实现复数加法操作,可以在复数类的定义中定义一个成员函数add,它把调用它的复数对象和参数指定的复数对象相加,返回相加之后得到的复数对象。 计算机科学与技术学院 张淼

428 Complex(){real=0;imag=0;} Complex(double r,double i) {real=r;imag=i;}
class Complex { double real,imag; public: Complex(){real=0;imag=0;} Complex(double r,double i) {real=r;imag=i;} Complex add(const Complex &x) const {Complex temp; temp.real=real+x.real; temp.imag=imag+x.imag; return temp; }; } Complex a(1,2),b(3,4),c; c=a.add(b); Complex a(1,2),b(3,4),c; c=a+b; 计算机科学与技术学院 张淼

429 Complex(){real=0;imag=0;} Complex(double r,double i) {real=r;imag=i;}
class Complex { double real,imag; public: Complex(){real=0;imag=0;} Complex(double r,double i) {real=r;imag=i;} Complex operator + (const Complex &x) const {Complex temp; temp.real=real+x.real; temp.imag=imag+x.imag; return temp; }; } 计算机科学与技术学院 张淼

430 friend Complex operator + (const Complex &c1, const
class Complex {double real,imag; public: …… }; Complex operator + (const Complex &c1, const Complex &c2) {Complex temp; temp.real=c1.real+c2.real; temp.imag=c1.imag+c2.imag; return temp; } friend Complex operator + (const Complex &c1, const Complex &c2) 计算机科学与技术学院 张淼

431 运算符重载是对已有的运算符赋予多重含义。它的主要优点之一就是用户自定义的数据类型可使用编译系统预定义的运算符。
计算机科学与技术学院 张淼

432 两种形式 重载为类成员函数。 重载为友元函数。 计算机科学与技术学院 张淼

433 重载为类成员函数时 参数个数=原操作数个数-1 (后置++、--除外)
定义形式 函数类型 operator 运算符(形参) { ...... } 重载为类成员函数时 参数个数=原操作数个数-1 (后置++、--除外) 重载为友元函数时 参数个数=原操作数个数,且至少应该有一个自定义类型的形参。 计算机科学与技术学院 张淼

434 作为成员函数重载 双目操作符重载函数的声明格式 class <类名> {
<返回值类型> operator #(<类型>); } 双目操作符重载函数的定义格式 <返回值类型> <类名>::operator #(<类型><参数>){…} 计算机科学与技术学院 张淼

435 双目操作符重载函数的使用格式 <类名> a; <类型> b; a#b; 或 a.operator#(b);
计算机科学与技术学院 张淼

436 <返回值类型> operator #(); } 单目操作符重载函数的定义格式
单目操作符重载函数的声明格式 class <类名> { <返回值类型> operator #(); } 单目操作符重载函数的定义格式 <返回值类型> <类名>::operator #(){…} 计算机科学与技术学院 张淼

437 单目操作符重载函数的使用格式 <类名> a; #a; 或 a.operator#();
对于单目操作符,其重载函数是单目操作符的前置用法。而操作符++和—有前置和后置两种用法。为了能够区分,则可以定义另一个带有int型参数的操作符++和—的重载函数来表示它们的后置用法。 计算机科学与技术学院 张淼

438 Counter& operator ++() //前置 {value++; return *this;}
class Counter { int value; public: Counter(){value=0;} Counter& operator ++() //前置 {value++; return *this;} const Counter operator ++(int) //后置 { Counter temp=*this; ++(*this); return temp;} } 计算机科学与技术学院 张淼

439 作为全局(友元)函数重载 双目操作符重载函数的声明格式
<返回值类型> operator #(<类型1> <参数1>,<类型2> <参数2>){…} 使用格式 <类型1> a; <类型2> b; a#b operator#(a,b) 计算机科学与技术学院 张淼

440 <返回值类型> operator #(<类型> <参数>) {…} 单目操作符重载函数的使用格式
单目操作符重载函数的声明格式 <返回值类型> operator #(<类型> <参数>) {…} 单目操作符重载函数的使用格式 <类名> a; #a; operator#(a); 计算机科学与技术学院 张淼

441 为了能够区分前置和后置++和—的重载函数,它们的后置用法:
<返回值类型> operator++(<类型><参数>,int); 计算机科学与技术学院 张淼

442 规则和限制 可以重载C++中除下列运算符外的所有运算符: . * :: ?: sizeof()
不改变原运算符的优先级和结合性。 不能改变操作数个数。 经重载的运算符,其操作数中至少应该有一个是自定义类型。 在重载运算符()、[]、->或者=时,运算符重载函数必须声明为类的一个成员。对于其他的运算符,运算符重载函数可以是成员函数或者友元函数. 但在某些情况下,操作符必须以全局函数来重载,才能满足使用上的要求。 计算机科学与技术学院 张淼

443 重载操作符+,使其能够实现实数与复数的混合运算。
计算机科学与技术学院 张淼

444 Complex(){real=0;imag=0;} Complex(double r,double i) {real=r;imag=i;}
class Complex { double real,imag; public: Complex(){real=0;imag=0;} Complex(double r,double i) {real=r;imag=i;} friend Complex operator + (const Complex &c1, const Complex &c2) friend Complex operator + (const Complex &c, double d) friend Complex operator + (double d, const Complex &c) } 计算机科学与技术学院 张淼

445 Complex a(1,2),b(3,4),c1,c2,c3; c1=a+b; c2=b+21.5 c3=10.2+a;
计算机科学与技术学院 张淼

446 如果在类对象a的类中重载运算符“+”,则a+3的显示调用( )。
A. a.operator(3) ; B. a->operator+(3); C. a.operator+(3) ; D. 3.operator+(a) ; 计算机科学与技术学院 张淼

447 赋值运算符的重载 两个同类对象之间可以赋值,其含义是用一个对象的状态来改变另一个对象的状态。C++编译程序会为每个类定义一个隐式的赋值操作符重载函数,其行为是逐个对成员进行赋值操作。 对象的成员中有数组或动态的数据类型时,就不能直接相互赋值,否则在程序的编译或执行过程中出现编译或运行错误。例如: 计算机科学与技术学院 张淼

448 { ps = new char[strlen(s) + 1]; strcpy(ps, s); } ~CDemo()
class Cdemo { public: CDemo(char *s) { ps = new char[strlen(s) + 1]; strcpy(ps, s); } ~CDemo() { if (ps) delete[] ps; } void print() { cout<<ps<<endl; } private: char *ps; }; void main() { CDemo d1("Key"), d2("Mouse"); d1 = d2; } 计算机科学与技术学院 张淼

449 除了会产生与默认拷贝构造函数可能产生的类似问题以外,还会导致内存泄露。
计算机科学与技术学院 张淼

450 #include <iostream.h> #include <string.h> class CDemo
{public: // 同上面的斜体部分代码 CDemo& operator = (const CDemo &a) // 赋值运算符重载 { if (ps) delete[] ps; if (a.ps){ps = new char[strlen(a.ps) + 1]; strcpy(ps, a.ps);} else ps = 0; return *this; } private: char *ps; }; void main() { CDemo d1("Key"), d2("Mouse"); d1 = d2; d1.print(); } 运行结果为: Mouse 计算机科学与技术学院 张淼

451 如果一个函数的返回值是一个对象的值,它就被认为是一个常量,是不能出现在等号左边作为左值的(也就是说不能被改变)。
C++语言中要求赋值表达式是可以作为左值的。由于引用的实质就是对象的地址,所以通过引用当然可以改变对象的值。 计算机科学与技术学院 张淼

452 注意: 拷贝构造函数和赋值操作符=重载函数区别: 创建一个对象时,用另一个已存在的同类对象对其初始化,调用拷贝构造函数。
对两个已存在的对象,通过赋值操作用其中一个对象来改变另一个对象的状态时,调用赋值操作符=重载函数。 例如:A a; A b=a; b=a; 调用拷贝构造函数,等价于A b(a); 调用赋值操作符重载函数 计算机科学与技术学院 张淼

453 赋值运算符不能重载为友元函数,只能是一个非静态成员函数。 并且它不能被派生类继承。
计算机科学与技术学院 张淼

454 CH 2.3.3输入/输出流类库 输入/输出(简称I/O)是程序的一个重要组成部分,程序运行所需要的数据往往要从外设(如键盘、文件等)得到,程序的运行结果通常也要输出到外设(如显示器、打印机、文件等)中去。 在C++中,输入/输出不是语言所定义的成分,而是由具体的实现(编译程序)作为标准库的功能来实现的。 计算机科学与技术学院 张淼

455 C++标准库中,除了提供基于字节的输入/输出操作外,为了方便使用,还提供了基于C++基本数据类型数据的输入/输出操作。
计算机科学与技术学院 张淼

456 以面向对象方式进行输入/输出则是通过C++的I/O类库来实现的。I/O类库提供的输入/输出操作是由一些I/O类来实现的。
由于C++支持过程式和面向对象两种程序设计范型,因此,在C++中,输入/输出操作也可以以这两种方式来进行,C++标准库也以两种方式提供了输入/输出功能。 在C++中,以过程式的方式进行输入/输出是通过从C语言保留下来的函数库中的输入/输出函数来实现的,用这些函数可以实现对基本类型数据的I/O操作。 以面向对象方式进行输入/输出则是通过C++的I/O类库来实现的。I/O类库提供的输入/输出操作是由一些I/O类来实现的。 计算机科学与技术学院 张淼

457 ios istream ifstream istrstream iostream ostream fstream strstream
ofstream ostrstream iostream fstream strstream 计算机科学与技术学院 张淼

458 istream,ostream iostream.h
iftream,oftream,fstream fstream.h 计算机科学与技术学院 张淼

459 我们还可以对>>,<<进行重载,使得通过I/O类的对象可以对用户自定义的数据(如对象等)进行输入/输出操作。
istream类重载了>>(提取),用它可以进行基本类型数据的输入操作。ostream类重载了<<(插入),它可以对基本类型的数据进行输出。 我们还可以对>>,<<进行重载,使得通过I/O类的对象可以对用户自定义的数据(如对象等)进行输入/输出操作。 计算机科学与技术学院 张淼

460 控制台I/O 控制台I/O指从计算机系统的标准输入设备输入程序所需要的数据以及把程序的计算结果输出到计算机系统的标准输出设备。
计算机科学与技术学院 张淼

461 预定义的控制台对象 在I/O类库中预定义了四个I/O对象:cin,cout,cerr,clog,利用这些对象可以直接进行控制台的输入/输出。其中cin属于istream类的对象,它对应计算机的标准输入设备,用来处理标准输入,即键盘输入;cout,cerr以及clog属于ostream类的对象,cout对应计算机的用于输出程序正常运行结果的标准输出设备,而cerr和clog则对应计算机的用于输出程序错误信息的设备,用来处理标准出错信息,通常它们都对应显示器。cerr不对输出信息进行缓冲。 计算机科学与技术学院 张淼

462 屏幕输出 使用预定义的插入符 使用成员函数 ostream& ostream::put(char ch):输出一个字符
cout.put(‘m’); char c=‘a’;cout.put(c); ostream& ostream::write(const char *p,int count):输出p所指向内存空间中count个字节。 cout.write(“hello”,strlen(“hello”)); 计算机科学与技术学院 张淼

463 键盘输入 使用预定义的提取符 在输入时,各个数据之间用空白符分开,一般常用空格符、也可用tab键(水平制表符)或换行符。因此,从键盘输入字符时,空白符只用于输入字符的分隔符,而本身不作为从输入流中提取的字符。 提取符可以从输入流中读取一个字符序列,即一个字符串。在处理这种字符序列时,字符串被认为是一个以空白符结束的字符序列。在从输入流中,每读入一个字符串,系统自动加上‘\0’字符。 计算机科学与技术学院 张淼

464 使用成员函数 istream::get(char &ch):输入一个字符 该函数不忽略空白字符,即将输入流的空白字符也作为一个字符。
istream::getline(char *p,int count,char delim=‘\n’):输入一个字符串直到输入count-1个字符或遇到delim指定的字符为止,并自动加上一个‘\0’. istream::read(char *p,int count):读入count个字符至p所指向的内存空间。 计算机科学与技术学院 张淼

465 <<和>>的重载
为了能用>>和<<对自定义类的对象进行输入输出操作,就需要对自定义的类重载<<和>>。 class A {int x,y; public: friend ostream& operator <<(ostream & out,const A &a) } 计算机科学与技术学院 张淼

466 ostream& operator <<(ostream & out,const A &a) {
out<<a.x<<‘,’<<a.y; return out; } A a,b; cout<<a<<b<<endl; 计算机科学与技术学院 张淼

467 文件I/O 计算机科学与技术学院 张淼

468 在外部存储器中保存数据的方式通常有两种:文件和数据库。
程序运行结果有时需要永久保存起来,以供其它程序或本程序下一次运行时使用。程序运行所需要的数据也常常要从其他程序或本程序上一次运行所保存的数据中获得。用于永久性保存数据的设备称为外部存储器,如磁盘、光盘等。 在外部存储器中保存数据的方式通常有两种:文件和数据库。 计算机科学与技术学院 张淼

469 文件是在计算机内存中以二进制表示的数据在外部存储介质上的另一种存放形式。
在C++中,把文件看成是由一系列字节所构成的字节串,称为流式文件。对文件中数据的操作(输入、输出)通常是逐个字节顺序进行。 计算机科学与技术学院 张淼

470 在对文件数据进行读写前,首先要打开文件。打开文件的目的是:在程序内部的一个表示文件的变量与外部的一个具体文件之间建立联系。
计算机科学与技术学院 张淼

471 每个被打开的文件都有一个内部的位置指针,他指出文件的当前读写位置。
b1 b2 b3 b4 …… 位置指针 进行读/写操作时,每读/写一个字节,文件指针就会自动往后移动一个字节的位置。 计算机科学与技术学院 张淼

472 iofile.open(文件名,打开方式);
fstream iofile; iofile.open(文件名,打开方式); ofstream outfile; outfile.open(文件名,打开方式); ifstream infile; infile.open(文件名,打开方式); fstream iofile(文件名,打开方式); ofstream outfile(文件名,打开方式); ifstream infile(文件名,打开方式); 计算机科学与技术学院 张淼

473 在以ios::out方式打开文件,而未指定ios::in,ios::ate,ios::app方式时,则隐含ios::trunc方式。
计算机科学与技术学院 张淼

474 if(!outfile) 或(outfile.fail())或!outfile.is_open()
由于种种原因,打开文件操作可能失败。因此,打开文件时应判断打开是否成功,只有文件打开成功后才能对文件进行操作。判断文件打开是否成功可采用以下方式: if(!outfile) 或(outfile.fail())或!outfile.is_open() 计算机科学与技术学院 张淼

475 文件打开成功后可以使用插入符<<和成员函数put,write函数对文件进行写操作。
使用提取符>>和成员函数get,geline,read来进行文件读操作。 计算机科学与技术学院 张淼

476 为了能够随机读写文件中的数据,必须要显示地指出读写的位置。 对于以输入方式打开的文件,可用下面的操作来指定文件内部指针位置。
seekg(位置);//指定绝对位置 seekg(偏移量,参照位置); //指定绝对位置 tellg()//获取指针位置。 计算机科学与技术学院 张淼

477 对于以输出方式打开的文件,可用下面的操作来指定文件内部指针位置。 seekp(位置);//指定绝对位置
tellp()//获取指针位置。 计算机科学与技术学院 张淼

478 其中位置和偏移量都是long型量,并以字节数为单位。
参照位置可以是ios::beg(文件头)、ios::cur(当前位置) 、ios::end(文件尾) 计算机科学与技术学院 张淼

479 文件读写完毕后,通常要关闭文件,其目的是把暂存在内存缓冲区中的内容写入到文件中,并归还打开文件时申请的内存资源。
计算机科学与技术学院 张淼

480 文本文件和二进制文件 文件就是一片内存中的数据在硬盘上的另一种存放形式,也就是二进制数据,即每个文件都是二进制的。
如果一个文件中的每个字节的内容都可以表示成字符的数据,我们就称这个文件为文本文件。 除了文本文件之外的文件称为二进制文件。 计算机科学与技术学院 张淼

481 二进制文件是包含在 ASCII 及扩展 ASCII 字符中编写的数据或程序指令的文件。一般是可执行程序、图形、图象、声音等等文件。
文本文件和二进制文件 文件通常分为二进制文件和文本文件。 二进制文件是包含在 ASCII 及扩展 ASCII 字符中编写的数据或程序指令的文件。一般是可执行程序、图形、图象、声音等等文件。 文本文件(也称为ASCII文件):它的每一个字节存放的是可表示为一个字符的ASCII代码的文件。它是以 “行”为基本结构的一种信息组织和存储方式的文件,可用任何文字处理程序阅读的简单文本文件。 计算机科学与技术学院 张淼

482 当我们按照二进制方式往文件中写入数据,则将数据在内存中的存储形式原样输出到文件中。
当我们按照文本方式往文件中写入数据时,一旦遇到换行字符(ASCII为10),则会转换为回车-换行(ASCII为13、10)。在读取文件时,一旦遇到回车-换行的组合(即连续的ASCII 13、10),则会转换为换行字符(ASCII为10)。 当我们按照二进制方式往文件中写入数据,则将数据在内存中的存储形式原样输出到文件中。 计算机科学与技术学院 张淼

483 CH 2.3.4类属(泛型)机制——模板 无性繁殖并不只是存在于遗传工程上,对程序员而言它也是一个由来已久的操作。过去,我们只不过是以一个简单而基本的工具,也就是文字编辑器,重制我们的代码。今天C++提供给我们一个更好的繁殖方法:template 在程序设计中经常需要用到这样的一些程序实体:这些程序实体所完成的功能完全相同,但它们所操作的数据类型不同。 计算机科学与技术学院 张淼

484 void int_sort(int x[],int num) void double_sort(double x[],int num)
void A_sort(A x[],int num) 计算机科学与技术学院 张淼

485 class IntStack {int buf[100]; public: bool push(int); bool pop(int&);
}; class DoubleStack {double buf[100]; public: bool push(double); bool pop(double &); }; class AStack {A buf[100]; public: bool push(A); bool pop(A&); }; 计算机科学与技术学院 张淼

486 新的C++语法使“数据类型”也以参数的姿态出现。 有了template,你就可以拥有宏“只写一次”的优点,以及重载函数“类型检查”的优点。
计算机科学与技术学院 张淼

487 模板的含义 模板的作用 以所处理的数据类型的说明作为参数的函数和类,分别称为函数模板和类模板
一个模板并非一个实实在在的类或函数,仅仅是一个类或函数的描述 模板的作用 模板可以实现逻辑相同,数据类型不同的程序代码的复制 模板机制可以减轻编程和维护的工作量和难度 计算机科学与技术学院 张淼

488 模板函数和模板类 模板函数 模板类 定义函数模板之后可以用类型实参调用该函数 建立类模板之后,可以用类型实参定义模板类并创建实例对象
编译器将根据调用该函数实参数据类型,生成相应的重载函数,该重载函数称为模板函数,是一个实实在在的函数 模板类 建立类模板之后,可以用类型实参定义模板类并创建实例对象 模板类是一个实实在在的类,对象是该模板类的实例 计算机科学与技术学院 张淼

489 函数模板 函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。 计算机科学与技术学院 张淼

490 函数模板 - 通用函数 求两个数最大值的函数模板 int max(int a, int b) {return a>b?a:b;}
float max(float a, float b) char max(char a, char b) template <class T> T max(T a,T b) { return a>b?a:b; } 计算机科学与技术学院 张淼

491 函数模板的定义和使用 函数模板的定义 函数模版的使用 template <class type1,class type2…>
函数定义 函数名<类型>(参数列表) 计算机科学与技术学院 张淼

492 例 求绝对值函数的模板 #include<iostream.h> template<class T>
T abs(T x) { return x<0?-x:x; } void main() { int n=-5; double d=-5.5; cout<<abs(n)<<endl; cout<<abs(d)<<endl; } 计算机科学与技术学院 张淼

493 运行结果: 5 5.5 分析 编译器从调用abs()时实参的类型,推导出函数模板的类型参数。例如,对于调用表达式abs(n),由于实参n为int型,所以推导出模板中类型参数T为int。 当类型参数的含义确定后,编译器将以函数模板为样板,生成一个函数: int abs(int x) { return x<0?-x:x; } 计算机科学与技术学院 张淼

494 有时编译程序无法根据调用时的实参类型确定所调用的模板函数,这时需要在程序中显示地实例化函数模板。
如 abs<int>(n); 计算机科学与技术学院 张淼

495 如:template <class T,int size> void f(T a) {T temp[size]; …… }
除了类型参数,模板也可带有非类型参数。 如:template <class T,int size> void f(T a) {T temp[size]; …… } void main() { f<int,10>(1); 计算机科学与技术学院 张淼

496 类模板 - 通用数据类型 什么是类模版 类模版的定义
类模板是用户为类定义的一种模式,使类中的某些数据成员的类型、成员函数的参数或返回值能够使用任意的数据类型 相当于自定义的通用数据类型 类模版的定义 template <class type1,class type2…> 类定义 计算机科学与技术学院 张淼

497 例 类模板应用举例 #include <iostream.h> #include <stdlib.h>
// 结构体Student struct Student { int id; //学号 float gpa; //平均分 }; 计算机科学与技术学院 张淼

498 template <class T> //类模板:实现对任意类型数据进行存取 class Store { private:
T item; // 用于存放任意类型的数据 int haveValue; // 用于标记item是否已被存入内容 public: Store(void); // 缺省形式(无形参)的构造函数 T GetElem(void); //提取数据函数 void PutElem(T x); //存入数据函数 }; // 缺省形式构造函数的实现 Store<T>::Store(void): haveValue(0) {} 计算机科学与技术学院 张淼

499 template <class T> // 提取数据函数的实现 T Store<T>::GetElem(void)
{ // 如果试图提取未初始化的数据,则终止程序 if (haveValue == 0) { cout << "No item present!" << endl; exit(1); } return item; // 返回item中存放的数据 template <class T> // 存入数据函数的实现 void Store<T>::PutElem(T x) { haveValue++; // 将haveValue 置为 TRUE,表示item中已存入数值 item = x; // 将x值存入item 计算机科学与技术学院 张淼

500 Store<Student> S3; Store<double> D; S1.PutElem(3);
void main(void) { Student g= {1000, 23}; Store<int> S1, S2; Store<Student> S3; Store<double> D; S1.PutElem(3); S2.PutElem(-7); cout << S1.GetElem() << " " << S2.GetElem() << endl; S3.PutElem(g); cout << "The student id is " << S3.GetElem().id << endl; cout << "Retrieving object D " ; cout << D.GetElem() << endl; //输出对象D的数据成员 // 由于D未经初始化,在执行函数D.GetElement()时出错 } 计算机科学与技术学院 张淼

501 s2.f();//error,连接程序将指出“void S<int>::f()”不存在 sub(); return 0; }
//file1.cpp #include “file2.h” int main() {S<float>s1; s1.f();//ok S<int>s2; s2.f();//error,连接程序将指出“void S<int>::f()”不存在 sub(); return 0; } //file2.h template <class T> class S //类模版S的定义 {T a; public: void f(); extern void sub(); 计算机科学与技术学院 张淼

502 template <class T> void S<T>::f() //类模版S的实现 {… }
//file2.cpp #include “file2.h” template <class T> void S<T>::f() //类模版S的实现 {… } Void sub() {s<float> x;//实例化“s<float>”并创建该类的一个对象x。 x.f();//ok 计算机科学与技术学院 张淼

503 template <class T> class S //类模版S的定义 {T a; public: void f(); }
//file2.h template <class T> class S //类模版S的定义 {T a; public: void f(); } void S<T>::f() //类模版S的实现 {… extern void sub(); 计算机科学与技术学院 张淼

504 如果两个源文件都用同一个模板的相同实例,则两个源文件的编译结果都将有相应实例的代码。如何解决?
由开发环境解决。 由连接程序解决。 计算机科学与技术学院 张淼

505 CTree int version CTree float version
template <class T> Class CTree {…} Tree.h Compile preprocess A.cpp B.cpp #include “tree.h” CTree <int> obj1; CTree <double>obj2; #include “tree.h” CTree <int> obj1; CTree <double>obj2; A.obj compile B.obj CTree int version CTree double version CTree int version CTree float version link CTree int version CTree double version exe 计算机科学与技术学院 张淼

506 在C++标准库中,除了从C标准库保留下来的一些功能外,所提供的功能大都以模板形式给出。这些模板构成了C++的标准模板库(Standard Template Library,简称STL)。
计算机科学与技术学院 张淼

507 面向对象标记 用图形把面向对象程序设计对问题的描述直观的表示出来,设计人员、程序开发人员、用户都可以通过它进行方便的交流。
实际使用的面向对象标记图有很多种,每一种标记方法都必须准确而清楚的描述下面四个问题: 类:包括数据成员和成员函数 对象:类的实例 类及对象的关系:继承或包含 类及对象之间的联系:相互作用与消息传送等。 计算机科学与技术学院 张淼

508 UML不仅可以描述前面我们提到的问题,而且主要着眼于整个软件开发过程中的可视化建模,相当完整,当然也比较复杂。
现在广泛使用的面向对象标记有很多种,而且,国际上已经有标准的标记记法,称为UML(Unified Modeling Language,统一建模语言),由OMG(Object Technology Group,对象技术组织)于1997年确认并开始推行。 UML不仅可以描述前面我们提到的问题,而且主要着眼于整个软件开发过程中的可视化建模,相当完整,当然也比较复杂。 计算机科学与技术学院 张淼

509 第三部分 MFC 计算机科学与技术学院 张淼

510 内容 MFC内部机理 MFC消息映射机制 框架窗口界面设计(一) 图形和文本(第八章) 对话框和控件(第四、五章)
框架窗口界面设计(二)(第六章) 文档和视图(第七章) 计算机科学与技术学院 张淼

511 Visual C++ Windows环境下最主要的应用开发系统之一。 C++语言的集成开发环境。
强大的调试功能为大型复杂软件的开发提供了有效的排错手段。 计算机科学与技术学院 张淼

512 WinMain()——入口函数 窗口的创建 设计一个窗口类; 注册窗口类; 创建窗口; 显示及更新窗口。 消息循环
计算机科学与技术学院 张淼

513 MFC 简介 什么是MFC? 编程方式的改变 Microsoft Foundation Class
Microsoft 提供的 MFC 是封装 Windows API 的面向对象 C++ 类库 以层次结构组织 封装了大部分的API函数 MFC 也是一个应用程序的框架结构 提供图形环境下的应用程序框架和组件 编程方式的改变 并不经常直接调用 API 从 MFC 类创建对象并调用其成员函数 计算机科学与技术学院 张淼

514 MFC 类库结构 CObject CCmdTarget CWnd 应用类结构 窗口支持 异常类 文件服务类 文档类 框架窗口类 控制条类
属性页表类 对话框类 视图类 控件类 图形设备环境类 控制支持类 Windows套接字类 图形对象类 菜单类 ODBC支持类 DAO支持类 同步类 其它类: Internet支持类 自动化类型 运行时刻对象支持 简单值类型 结构 其它支持类 集合模板类 用于同步的类 数组类 列表类 映射类 Internet类 计算机科学与技术学院 张淼

515 CH3.1 MFC内部机理 MFC程序的初始化过程 RTTI(Runtime Type Information)运行时类型识别
Dynamic Creation 动态创建 Persistence 永久保存 Message Mapping 消息映射 Message Routing 消息传递 计算机科学与技术学院 张淼

516 应用程序对象 头文件 应用程序对象 Afxwin.h 包含该头文件可以引用MFC中的类 一个MFC应用程序有且仅有一个应用程序对象
应用程序对象应该继承自CWinApp 应用对象必须是全局对象 InitInstance虚函数 应用对象自动调用InitInstance虚函数,一般应覆盖该函数完成初始化工作,如创建应用的主窗口 该函数返回FALSE程序将退出 计算机科学与技术学院 张淼

517 AFX 函数 什么是AFX函数 常用的AFX函数 全局函数,并非MFC类的成员函数 任何地方都可以使用 AfxAbort 无条件终止应用程序
AfxBeginThread 创建新线程并开始执行 AfxEndThread 终止当前执行的线程 AfxMessageBox 显示Windows消息框 AfxGetApp 返回指向应用程序对象的指针 AfxGetAppName 返回应用程序的名称 AfxGetMainWnd 返回指向应用程序主窗口的指针 AfxGetInstanceHandle 返回应用程序当前实例的句柄 AfxRegisterWndClass 为MFC应用程序注册自定义WNDCLASS类 计算机科学与技术学院 张淼

518 MFC 如何使用应用程序对象 是什么启动了我们的程序? CWinApp取代了WinMain的地位 执行步骤 CWinApp的主要函数
virtual BOOL InitApplication(); virtual BOOL InitInstance();//初始化应用实例 virtual BOOL Run();//进入消息循环 virtual BOOL ExitInstance();//最后执行的函数 执行步骤 调用应用对象的构造函数创建全局应用对象 执行CWinApp::InitApplication 执行pApp->InitInstance,必须重写该函数 如:通常在该函数中打开应用程序的主框架窗口 计算机科学与技术学院 张淼

519 类作用域 类定义构成一个作用域:类作用域,其中的标识符局部于类定义,它们可以与类定义外的全局标识符或其他类定义中的标识符相同。在类定义外使用类定义中的标识符时,需通过对象名受限或类名受限。在类定义中使用与局部标识符同名的全局标识符时,需要在全局标识符前面加上全局域分辨符(::)来实现。 计算机科学与技术学院 张淼

520 MFC中最重要的封装是对Win32 API的封装,因此,理解Windows Object和MFC Object (C++对象,一个C++类的实例)之间的关系是理解MFC的关键之一。所谓Windows Object(Windows对象)是Win32下用句柄表示的Windows操作系统对象;所谓MFC Object (MFC对象)是C++对象,是一个C++类的实例,这里MFC Object是有特定含义的,指封装Windows Object的C++ Object,并非指任意的C++ Object。MFC Object 和Windows Object是不一样的,但两者紧密联系。 计算机科学与技术学院 张淼

521 一个MFC窗口对象是一个C++ CWnd类(或派生类)的实例,是程序直接创建的。在程序执行中它随着窗口类构造函数的调用而生成,随着析构函数的调用而消失。而Windows窗口则是Windows系统的一个内部数据结构的实例,由一个“窗口句柄”标识,Windows系统创建它并给它分配系统资源。Windows窗口在MFC窗口对象创建之后,由CWnd类的Create成员函数创建,“窗口句柄”保存在窗口对象的m_hWnd成员变量中。Windows窗口可以被一个程序销毁,也可以被用户的动作销毁。 计算机科学与技术学院 张淼

522 MFC对象 HWND m_hWnd; 其他成员变量 成员函数 窗口 计算机科学与技术学院 张淼

523 MFC Object是相应C++类的实例,这些类是 MFC或者程序员定义的; Windows Object是Windows系统的内部结
从数据结构上比较 MFC Object是相应C++类的实例,这些类是 MFC或者程序员定义的; Windows Object是Windows系统的内部结 构,通过一个句柄来引用; MFC给这些类定义了一个成员变量来保存MFC Object对应的Windows Object的句柄。 计算机科学与技术学院 张淼

524 MFC Object是高层的,Windows Object是低 层的; MFC Object封装了Windows Object的大部分
从层次上讲比较 MFC Object是高层的,Windows Object是低 层的; MFC Object封装了Windows Object的大部分 或全部功能,MFC Object的使用者不需要直接应 用Windows Object的HANDLE(句柄)使用 Win32 API,代替它的是引用相应的MFC Object的成员函数。 计算机科学与技术学院 张淼

525 MFC Object通过构造函数由程序直接创建;Windows Object由 相应的SDK函数创建。
从创建上比较 MFC Object通过构造函数由程序直接创建;Windows Object由 相应的SDK函数创建。 MFC中,使用这些MFC Object,一般分两步: 首先,创建一个MFC Object,或者在STACK中创建,或者在HEAP 中创建,这时,MFC Object的句柄实例变量为空,或者说不是一个 有效的句柄。 然后,调用MFC Object的成员函数创建相应的Windows Object, MFC的句柄变量存储一个有效句柄。 当然,可以在MFC Object的构造函数中创建相应的Windows对象, MFC的GDI类就是如此实现的,但从实质上讲,MFC Object的创建和 Windows Object的创建是两回事。 计算机科学与技术学院 张淼

526 MFC Object随着析构函数的调用而消失;但Windows Object必须由相应的Windows系统函数销毁。
从销毁上比较 MFC Object随着析构函数的调用而消失;但Windows Object必须由相应的Windows系统函数销毁。 设备描述表CDC类的对象有所不同,它对应的HDC句柄对 象可能不是被销毁,而是被释放。 当然,可以在MFC Object的析构函数中完成Windows Object的销毁,MFC Object的GDI类等就是如此实现 的,但是,应该看到:两者的销毁是不同的。 计算机科学与技术学院 张淼

527 CH3.2 MFC消息映射机制 SDK中有窗口过程函数,用switch/case语句处理消息 MFC对消息如何处理? 消息映射
计算机科学与技术学院 张淼

528 将一个消息映射添加到一个类中 声明消息映射 在类的实现文件中加入表和表项的定义 添加成员函数定义处理消息
将DECLARE_MESSAGE_MAP语句添加到类声明中 在类的实现文件中加入表和表项的定义 添加成员函数定义处理消息 成员函数的名字与消息映射宏名字对应 如:ON_WM_PAINT()对应OnPaint()函数 BEGIN_MESSAGE_MAP(theClass, baseClass) //当前类 ,当前类的父类 // END_MESSAGE_MAP() 计算机科学与技术学院 张淼

529 鼠标 映射鼠标消息(以左键按下消息为例) 添加消息映射宏 声明消息处理函数 实现消息处理函数 参见工程Mouse
ON_WM_LBUTTONDOWN () 声明消息处理函数 afx_msg void OnLButtonDown( UINT nFlags, CPoint point ); nFlags:位标志 point:鼠标位置 实现消息处理函数 void CMfcTsetView::OnLButtonDown(UINT nFlags, CPoint point) 参见工程Mouse 计算机科学与技术学院 张淼

530 键盘 映射键盘消息(以字符键为例) 添加消息映射宏 声明并实现消息处理函数 参见工程Keyboard ON_WM_CHAR ()
afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ); nChar :虚拟键值 nRepCnt :重复次数 nFlags:位标志 参见工程Keyboard 计算机科学与技术学院 张淼

531 AfxWndProc AfxCallWndProc WindowProc OnWndMsg OnCommand 标准消息 OnNotify
计算机科学与技术学院 张淼

532 CH3.3 框架窗口界面设计(一) 消息路由 菜单的设计 交互对象的动态更新 计算机科学与技术学院 张淼

533 资源 用户接口(UI)资源是指功能菜单、对话框、程序图标、光标等资源,它是Windows应用程序界面的重要组成部分。资源的使用极大方便了应用程序界面的设计,也大大方便了应用程序与用户的交互。 这些用户资源的实际内容(二进制代码)是借助各种工具产生的。并以各种扩展名的文件存在,如.ico,.bmp,.cur等。程序员必须在一个所谓的资源描述文档(.rc)中描述它们。RC编译器读取RC文件的描述后,将所有用户接口资源文件集中制作一个.RES文件。 这些资源可以使用VC++6.0提供的资源编辑器来实现创建和编辑。 计算机科学与技术学院 张淼

534 资源 资源分类 资源ID 菜单 对话框 …… 资源的唯一标识 WM_COMMAND消息 wParam低16位传递菜单项资源ID
计算机科学与技术学院 张淼

535 命令消息的路由——拐弯上溯 AfxWndProc AfxCallWndProc WindowProc OnWndMsg OnCommand
OnNotify OnCmdMsg 计算机科学与技术学院 张淼

536 CFrameWnd::OnCommand
CWnd::OnCommand CFrameWnd::OnCmdMsg 1 CView::OnCmdMsg 4 3 CWnd::OnCmdMsg (CCmdTarget::OnCmdMsg) CWinApp::OnCmdMsg (CCmdTarget::OnCmdMsg) 2 CWnd::OnCmdMsg (CCmdTarget::OnCmdMsg) CDocument::OnCmdMsg DispatchCmdMsg CCmdTarget ::OnCmdMsg msg handler DispatchCmdMsg DispatchCmdMsg DispatchCmdMsg msg handler msg handler msg handler 计算机科学与技术学院 张淼

537 CMainFrame 交给 View类 DOC类 APP类 处理 没有 计算机科学与技术学院 张淼

538 消息的分类 标准消息 命令消息 通告消息 除WM_COMMAND之外,所有以WM_开头的消息。 从CWnd派生的类,都可以接收到这类消息。
来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。 从CCmdTarget派生的类,都可以接收到这类消息。 通告消息 由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。 计算机科学与技术学院 张淼

539 菜单类:CMenu 用于管理菜单 它是一个Windows HMenu的封装,提供了与窗口有关的菜单资源建立、修改、跟踪及删除的成员函数
计算机科学与技术学院 张淼

540 命令更新 当要显示一个菜单时,操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类(如CFrameWnd)接管,它创建一个CCmdUI对象,并与第一个菜单项相关联,并调用CCmdUI对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。如果对这个菜单项捕获了CN_UPDATE_COMMAND_UI消息,便可以在这个消息的消息响应函数中进行菜单项显示的处理(如可用不可用等)。 处理完第一个菜单项后同一个CCmdUI对象就设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。 计算机科学与技术学院 张淼

541 CH3.4 图形和文本 设备描述表类 - CDC 获取和释放设备描述表 该类及其子类支持设备描述表对象
PAINTSTRUCT ps; CDC * pDC = BeginPaint(&ps); …… EndPaint(&ps); CDC * pDC = GetDC(); …… ReleaseDC(pDC); 仅用OnPaint处理程序 用于非OnPaint处理程序 以上获取和释放设备描述表的函数都是CWnd类的成员函数 计算机科学与技术学院 张淼

542 CDC类的派生类 通常我们不直接使用CDC类,而使用其派生类 CPaintDC CClientDC CWindowDC MetaFileDC
用于在窗口客户区画图,仅限于在OnPaint处理程序 CClientDC 用于在窗口客户区画图,在OnPaint处理程序之外使用,构造对象时传递NULL参数可以实现在整个屏幕画图 CWindowDC 用于在窗口内任意地方画图,包括非客户区 MetaFileDC 用于向GDI元文件画图 计算机科学与技术学院 张淼

543 画图 画直线和曲线 画椭圆、多边形等 MoveTo Chord LineTo Ellipse Polyline Pie PolylineTo
Arc ArcTo PolyBezier PolyBezierTo PolyDraw 画椭圆、多边形等 Chord Ellipse Pie Polygon Rectangle RoundRect 所有函数都是设备描 述表类的成员函数 计算机科学与技术学院 张淼

544 画笔类 - CPen 创建画笔对象 方法一 方法二 直接构造对象 构造一个未初始化的画笔对象,再调用方法初始化
CPen pen(PS_SOLID,1,RGB(255,0,0)); 方法二 构造一个未初始化的画笔对象,再调用方法初始化 CPen pen; pen.CreatePen(PS_SOLID,1,RGB(255,0,0)); 计算机科学与技术学院 张淼

545 画笔类 - CPen 创建画笔对象 方法三 构造一个未初始化的画笔对象,通过向LOGPEN结构中填充描述画笔特性的参数,然后调用CPen的成员函数CreatePenIndirect生成画笔 CPen pen; LOGPEN lp; lp.lopnStyle = PS_SOLID; lp.lopnWidth.x = 1; lp.lopnColor = RGB (255, 0, 0); pen.CreatePenIndirect (&lp); 计算机科学与技术学院 张淼

546 画笔类 - CPen 使用创建好的画笔 创建好画笔对象后,只要将它选入设备描述表即可
CPen pen(PS_SOLID,10,RGB(255,0,0)); dc.SelectObject(&pen); dc.Ellipse (0, 0, 100, 100); 计算机科学与技术学院 张淼

547 画刷类 - CBrush 创建画刷对象 方法一 方法二 直接构造对象 构造一个未初始化的画刷对象,再调用方法初始化
CBrush brush(RGB(255,0,0)); 方法二 构造一个未初始化的画刷对象,再调用方法初始化 CBrush brush; brush.CreateSolidBrush(RGB(255,0,0)); 计算机科学与技术学院 张淼

548 画刷类 - CBrush 创建阴影线画刷 方法一 方法二 直接构造对象 构造一个未初始化的画刷对象,再调用方法初始化
CBrush brush(HS_DIAGCROSS,RGB(255,0,0)); 方法二 构造一个未初始化的画刷对象,再调用方法初始化 CBrush brush; brush.CreateHatchBrush( HS_DIAGCROSS, RGB(255,0,0) ); 计算机科学与技术学院 张淼

549 画刷类 - CBrush 画刷的使用 同画笔对象一样,创建好画刷对象后,只要将它选入设备描述表即可 CBrush brush (
HS_DIAGCROSS, RGB (255, 255, 255) ); dc.SelectObject (&brush); dc.SetBkColor (RGB (192, 192, 192)); dc.Rectangle (0, 0, 100, 100); 计算机科学与技术学院 张淼

550 字体类 - CFont 什么是字体 字体的创建 字体是一组具有特定尺寸(高度)和字样的字符 参见工程Hello 参见Font.doc
字样指示字体共有属性,如粗细等 字体的创建 CFont font; //调用以下方法之一初始化字体对象 font.CreateFont font.CreateFontIndirect font.CreatePointFont font.CreatePointFontIndirect 通过LOGFONT结构来创建字体 参见工程Hello 参见Font.doc 计算机科学与技术学院 张淼

551 坐标空间 Microsoft Windows下的程序运用坐标空间和转换来对图形输出进行缩放、旋转、平移、斜切和反射。
一个坐标空间是一个平面的空间,通过使用两个相互垂直并且长度相等的轴来定位二维对象。 Ymax Xmin (0,0) Xmax Ymin 计算机科学与技术学院 张淼

552 坐标空间 Win32应用程序设计接口(API)使用四种坐标空间:世界坐标系空间、页面空间、设备空间、和物理设备空间。应用程序运用世界坐标系空间对图形输出进行旋转、斜切或者反射。 Win32 API把世界坐标系空间和页面空间称为逻辑空间;最后一种坐标空间(即物理设备空间)通常指应用程序窗口的客户区;但是它也包括整个桌面、完整的窗口(包括框架、标题栏和菜单栏)或打印机的一页或绘图仪的一页纸。物理设备的尺寸随显示器、打印机或绘图仪所设置的尺寸而变化。 计算机科学与技术学院 张淼

553 转换 http://www.sunxin.org
如要在物理设备上绘制输出,Windows把一个矩形区域从一个坐标空间拷贝到(或映射到)另一个坐标空间,直至最终完整的输出呈现在物理设备上(通常是屏幕或打印机) 。 如果该应用程序调用了SetWorldTransform函数,那么映射就从应用程序的世界坐标系空间开始;否则,映射在页面空间中进行。在Windows把矩形区域的每一点从一个空间拷贝到另一个空间时,它采用了一种被称作转换的算法,转换是把对象从一个坐标空间拷贝到另一个坐标空间时改变(或转变)这一对象的大小、方位、和形态,尽管转换把对象看成一个整体,但它也作用于对象中的每一点或每条线。 计算机科学与技术学院 张淼

554 转换 下图是运用SetWorldTransform函数而进行的一个典型转换。 Ymax Ymax Ymin Ymin Ymin Ymax
世界坐标系空间 页面空间 设备空间 物理设备 计算机科学与技术学院 张淼

555 页面空间到设备空间的转换 页面空间到设备空间的转换是原Windows接口的一部分。这种转换确定与一特定设备描述表相关的所有图形输出的映射方式。 所谓映射方式是指确定用于绘图操作的单位大小的一种量度转换。映射方式是一种影响几乎任何客户区绘图的设备环境属性。另外还有四种设备环境属性:窗口原点、视口原点、窗口范围和视口范围,这四种属性与映射方式密切相关。 计算机科学与技术学院 张淼

556 页面空间到设备空间的转换 页面空间 窗口原点 窗口 设备空间 视口原点 视口
页面空间到设备空间的转换所用的是两个矩形的宽与高的比率,其中页面空间中的矩形被称为窗口,设备空间中的矩形被称为视口,Windows把窗口原点映射到视口原点,把窗口范围映射到视口范围,就完成了这种转换,如下图所示: 页面空间 窗口原点 窗口 设备空间 视口原点 视口 计算机科学与技术学院 张淼

557 设备空间到物理空间的转换 设备空间到物理空间的转换有几个独特之处:它只限于平移,并由Windows的窗口管理部分控制,这种转换的唯一用途是确保设备空间的原点被映射到物理设备上的适当点上。没有函数能设置这种转换,也没有函数可以获取有关数据。 计算机科学与技术学院 张淼

558 默认转换 一旦应用程序建立了设备描述表,并立即开始调用GDI绘图或输出函数,则运用默认页面空间到设备空间的转换和设备空间到客户区的转换(在应用程序调用SetWorldTransform函数之前,不会出现世界坐标空间到页面空间的转换)。 默认页面空间到设备空间的转换结果是一对一的映射;即页面空间上给出的一点映射到设备空间的一个点。正如前文讲到的,这种转换没有以矩阵指定,而是通过把视口宽除以窗口宽,把视口高除以窗口高而得出的。在默认的情况下,视口尺寸为1x1个象素,窗口尺寸为1x1页单位。 设备空间到物理设备(客户区、桌面或打印机)的转换结果总是一对一的;即设备空间的一个单位总是与客户区、桌面、或打印机上的一个单位相对应。这一转换的唯一用途是平移。无论窗口移到桌面的什么位置,它永远确保输出能够正确无误地出现在窗口上。 默认转换的一个独特之处是设备空间和应用程序窗口的y轴方向。在默认的状态下,y轴正向朝下,负y方向朝上。 计算机科学与技术学院 张淼

559 逻辑坐标和设备坐标 几乎在所有的GDI函数中使用的坐标值都是采用的逻辑单位。Windows必须将逻辑单位转换为“设备单位”,即像素。这种转换是由映射方式、窗口和视口的原点以及窗口和视口的范围所控制的。 Windows对所有的消息(如WM_SIZE、WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP),所有的非GDI函数和一些GDI函数(例如GetDeviceCaps函数),永远使用设备坐标。 “窗口”是基于逻辑坐标的,逻辑坐标可以是象素、毫米、英寸等单位;“视口”是基于设备坐标(象素)的。通常,视口和客户区是相同的。 缺省的映射模式为MM_TEXT。在这种映射模式下,逻辑单位和设备单位相同。 计算机科学与技术学院 张淼

560 逻辑坐标和设备坐标的相互转换 窗口(逻辑)坐标转换为视口(设备)坐标的两个公式:
xViewport=(xWindow-xWinOrg)* xViewOrg yViewport=(yWindow-yWinOrg)* yViewOrg 视口(设备)坐标转换为窗口(逻辑)坐标的两个公式: xWindow=(xViewPort-xViewOrg)* xWinOrg yWindow=(yViewPort-yViewOrg)* yWinOrg xViewExt xWinExt yViewExt yWinExt xWinExt xViewExt yWinExt yViewExt 计算机科学与技术学院 张淼

561 在MM_TEXT映射方式下逻辑坐标和设备坐标的相互转换
窗口(逻辑)坐标转换为视口(设备)坐标的两个公式: xViewport = xWindow-xWinOrg+xViewOrg yViewport = yWindow-yWinOrg+yViewOrg 视口(设备)坐标转换为窗口(逻辑)坐标的两个公式: xWindow = xViewport-xViewOrg+xWinOrg yWindow = yViewport-yViewOrg+yWinOrg 计算机科学与技术学院 张淼

562 CH3.5 对话框和控件 对话框有两种类型:模态对话框和非模态对话框。
模态对话框在应用程序能够继续执行之前必须被关闭掉。即当我们显示一个模态对话框时应用程序就会暂停,直到我们关闭对话框我们才能继续执行程序中的其他任务。 非模态对话框允许我们在显示对话框时转而执行程序的其他任务而不用关闭对话框。 计算机科学与技术学院 张淼

563 对话框类:CDialog 由于对话框是一个特殊的窗口,所以该类是从CWnd类中派生出来的,包括 通用对话框类CDialog
支持文件选择、颜色选择、字体选择、打印、替换文本的公共对话框子类 计算机科学与技术学院 张淼

564 控件的创建和基本使用方法 在对话框模板中用编辑器指定控件,将对话框看作控件的父窗口。
编程方式,即调用相应控件类的成员函数Create来创建,并在Create函数指定控件的父窗口指针。 计算机科学与技术学院 张淼

565 计算机科学与技术学院 张淼

566 对话框控件访问七种方式 GetDlgItem()->Get(Set)WindowText()
GetDlgItemText()/SetDlgItemText() GetDlgItemInt()/SetDlgItemInt() 将控件和整型变量相关联 将控件和控件变量相关联 SendMessage() SendDlgItemMessage() 计算机科学与技术学院 张淼

567 Z-order 窗口的Z次序表明了重叠窗口堆中窗口的位置,这个窗口堆是按一个假想的轴定位的,这个轴就是从屏幕向外伸展的Z轴。Z次序最上面的窗口覆盖所有其它的窗口,Z次序最底层的窗口被所有其它的窗口覆盖。应用程序设置窗口在Z次序中的位置是通过把它放在一个给定窗口的后面,或是放在窗口堆的顶部或底部。 Windows系统管理三个独立的Z次序——一个用于顶层窗口、一个用于兄弟窗口,还有一个是用于最顶层窗口。最顶层窗口覆盖所有其它非最顶层窗口,而不管它是不是活动窗口或是前台窗口。应用程序通过设置WS_EX_TOPMOST风格创建最顶层窗口。 一般情况下,Windows系统把刚刚创建的窗口放在Z次序的顶部,用户可通过激活另外一个窗口来改变Z次序;Windows系统总是把活动的窗口放在Z次序的顶部,应用程序可用函数BringWindowToTop把一个窗口放置到Z次序的顶部。函数SetWindowPos和DeferWindowPos用来重排Z次序。 计算机科学与技术学院 张淼

568 窗口 兄弟窗口 活动窗口 共享同一个父窗口的多个子窗口叫兄弟窗口。
活动窗口是应用程序的顶层窗口,也就是当前使用的窗口。只有一个顶层窗口可以是活动窗口,如果用户使用的是一个子窗口,Windows系统就激活与这个子窗口相应的顶层窗口。 任何时候系统中只能有一个顶层窗口是活动的。用户通过单击窗口(或其中的一个子窗口)、使用ALT+TAB或ALT+ESC组合键来激活一个顶层窗口,应用程序则调用函数SetActiveWindow来激活一个顶层窗口。 计算机科学与技术学院 张淼

569 窗口 前台窗口和后台窗口 在Windows系统中,每一个进程可运行多个线程,每个线程都能创建窗口。创建正在使用窗口的线程称之为前台线程,这个窗口就称之为前台窗口。所有其它的线程都是后台线程,由后台线程所创建的窗口叫后台窗口。 用户通过单击一个窗口、使用ALT+TAB或ALT+ESC组合键来设置前台窗口,应用程序则用函数SetForegroundWindow设置前台窗口。如果新的前台窗口是一个顶层窗口,那么Windows系统就激活它,换句话说,Windows系统激活相应的顶层窗口。 计算机科学与技术学院 张淼

570 CH3.6 框架窗口界面设计(二) 单文档和多文档程序框架窗口 计算机科学与技术学院 张淼

571 文档窗口对SDI程序来说,和主框架窗口是一致的,主框架窗口就是文档窗口;对于MDI程序,文档窗口是主框架窗口的子窗口。
主框架窗口是直接放置在桌面(DeskTop)上的那个窗口,每个应用程序只能有一个主框架窗口。主框架窗口负责管理各个用户交互对象并根据用户操作相应地创建或更新文档窗口及其视图。 文档窗口对SDI程序来说,和主框架窗口是一致的,主框架窗口就是文档窗口;对于MDI程序,文档窗口是主框架窗口的子窗口。 文档窗口一般都有相应的可见边框,它的客户区(除了窗口标题栏、边框外的区域)是由相应的视图来构成的,因此可以说视图是文档窗口内的子窗口。 计算机科学与技术学院 张淼

572 窗口状态的改变 MFC AppWizard为每个窗口设置了相应的大小和位置。
运行程序时,会自动调用框架内部的WinMain函数,并自动查找该应用程序类的全局变量theApp,然后自动调用用户应用程序类的虚函数InitInstance,该函数会进一步调用相应的函数来完成主窗口的构造和显示工作,代码: BOOL CEx_SDIApp::InitInstance() { … m_pMainWnd->ShowWindow(SW_SHOW); // 显示窗口 m_pMainWnd->UpdateWindow(); // 更新窗口 return TRUE; } m_pMainWnd是主框架窗口指针变量,ShowWindow是CWnd类的成员函数,用来按指定的参数显示窗口。 计算机科学与技术学院 张淼

573 通过指定ShowWindow函数的参数值可以改变改变窗口显示状态。例如下面的代码是将窗口的初始状态设置为“最小化”:
BOOL CEx_SDIApp::InitInstance() { ... m_pMainWnd->ShowWindow(SW_SHOWMINIMIZED); m_pMainWnd->UpdateWindow(); return TRUE; } 计算机科学与技术学院 张淼

574 计算机科学与技术学院 张淼

575 窗口风格 通常有一般和扩展两种形式。可在函数CWnd::Create或CWnd::CreateEx参数中指定,CreateEx函数可同时支持以上两种风格,CWnd::Create只能指定窗口的一般风格。控件和对话框的窗口风格可直接通过其属性对话框来设置。 除了上述风格外,框架窗口还有以下三个自己的风格。它们都可以在PreCreateWindow重载函数的CREATESTRUCT结构中指定。 FWS_ADDTOTITLE 该风格指定相关的信息如文档名添加到框架窗口标题的后面。 FWS_PREFIXTITLE 该风格使得框架窗口标题中的文档名显示在应用程序名之前。 FWS_SNAPTOBARS 该风格用来调整窗口的大小,使它刚好包含了框架窗口中的控制栏。 计算机科学与技术学院 张淼

576 AppWizard中进行修改 修改CREATESTRUCT结构
窗口创建前,自动调用PreCreateWindow虚函数。用MFC AppWizard创建文档应用程序结构时,MFC已为主窗口或文档窗口类自动重载了该虚函数。可以在此函数中通过修改CREATESTRUCT结构来设置窗口的绝大多数风格。 计算机科学与技术学院 张淼

577 使用ModifyStyle和ModifyStyleEx
BOOL ModifyXXXX( DWORD dwRemove, DWORD dwAdd,UINT nFlags = 0 ); 参数dwRemove指定需要删除的风格,dwAdd指定需 要增加的风格,nFlags表示SetWindowPos的标 志。 框架窗口设定扩展风格只能通过调用ModifyStyle函数来进行。 计算机科学与技术学院 张淼

578 改变窗口的大小和位置 CWnd类的成员函数SetWindowPos或MoveWindow可以改变窗口的大小和位置。SetWindowPos可以改变窗口的大小、位置,还可以改变所有窗口在堆栈排列的次序(Z次序) 。 计算机科学与技术学院 张淼

579 状态栏 状态栏的定义 AppWizard创建的SDI或MDI应用程序框架中,有一个静态的indicator数组,是在MainFrm.cpp文件中定义的,被MFC用作状态栏的定义。 数组中的元素是一些标识常量或是字串资源的ID号。图列出了indicators数组元素与标准状态栏窗格的关系。 Static UINT indicators[]= { ID_SEPARATOR, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, } 图6.28 indicators数组的定义 计算机科学与技术学院 张淼

580 增加和减少窗格 在状态栏中增加信息行窗格,在indicators数组中增加一个ID_SEPARATOR标识;在状态栏中增加指示器窗格,在indicators数组中增加一个在字符串表中定义过的资源ID。状态栏减少一个窗格,只需减少indicators数组元素。 计算机科学与技术学院 张淼

581 调用CStatusBar::SetPaneText函数更新任何窗格中的文本。函数原型:
在状态栏上显示文本 调用CStatusBar::SetPaneText函数更新任何窗格中的文本。函数原型: BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE ); 计算机科学与技术学院 张淼

582 改变状态栏的风格 在MFC的CStatusBar类中,有两个成员函数可以改变状态栏风格,它们是:
void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth ); void SetPaneStyle( int nIndex, UINT nStyle ); 计算机科学与技术学院 张淼

583 图标和光标 使用GetClassLong和SetClassLong重新指定应用程序窗口的图标,原型:
DWORD SetClassLong( HWND hWnd, int nIndex, LONG dwNewLong); DWORD GetClassLong( HWND hWnd, int nIndex); 计算机科学与技术学院 张淼

584 使用系统光标 HCURSOR LoadStandardCursor( LPCTSTR lpszCursorName ) const;
标准光标可以使用CWinApp:: LoadStandardCursor加载到程序中,函数: HCURSOR LoadStandardCursor( LPCTSTR lpszCursorName ) const; lpszCursorName用来指定一个标准光标名,可以是下列宏定义: IDC_ARROW 标准箭头光标 IDC_IBEAM 标准文本输入光标 IDC_WAIT 漏斗型计时等待光标 IDC_CROSS 十字形光标 IDC_UPARROW 垂直箭头光标 IDC_SIZEALL 四向箭头光标 IDC_SIZENWSE 左上至右下的双向箭头光标 IDC_SIZENESW 左下至右上的双向箭头光标 IDC_SIZEWE 左右双向箭头光标 IDC_SIZENS 上下双向箭头光标 计算机科学与技术学院 张淼

585 用编辑器创建或从外部调入的光标资源,通过函数CWinApp::LoadCursor进行加载,原型:
使用光标资源 用编辑器创建或从外部调入的光标资源,通过函数CWinApp::LoadCursor进行加载,原型: HCURSOR LoadCursor( LPCTSTR lpszResourceName ) const; HCURSOR LoadCursor( UINT nIDResource ) const; 计算机科学与技术学院 张淼

586 afx_msg BOOL OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message );
更改程序中的光标 更改程序中的光标最简单的方法是MFC ClassWizard映射WM_SETCURSOR消息,该消息是光标移动到一个窗口内并且还没有捕捉到鼠标时产生的。CWnd为此消息的映射函数定义这样的原型: afx_msg BOOL OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message ); pWnd表示拥有光标的窗口指针,nHitTest用来表示光标所处的位置。message用来表示鼠标消息。 在OnSetCursor函数调用SetCursor来设置相应的光标,并将OnSetCursor函数返回TRUE,就可改变当前的光标了。 计算机科学与技术学院 张淼

587 CH3.7 文档和视图 Document/View
人有“体面”,数据也有“体面”,实际的数据就是体,显示在屏幕上(甚而打印机上)的画面就是面。 MFC之所以为Application Framework,最重要的一个特征就是它能够将管理数据的程序代码和负责数据显示的程序代码分离开来,这种能力由MFC的Document/View提供。 Document/View是MFC的基石。 甚至OLE复合文件都是建筑在文档/视图的基础上 计算机科学与技术学院 张淼

588 其实Document/View不是什么新主意,Xerox PARC实验室是这种观念的滥觞,它就是Smalltalk环境中的关键部分——MVC(Model-View-Controller)
Model——Document Controller——Document Template 计算机科学与技术学院 张淼

589 “文档/视图” 是一种程序类型,最适合于开发文本编辑之类的软件。
MFC中有两种类型的文档视结构程序:单文档界面(SDI)应用程序和多文档界面(MDI)应用程序。 在单文档界面程序中,用户在同一时刻只能操作一个文档。 一个多文档界面应用程序也能操作文档,但它允许同时操作多个文档。 计算机科学与技术学院 张淼

590 注意,有两种情况不宜采用文档/视图结构:不是面向数据的应用或数据量很少的应用程序,不宜采用文档/视图结构。如一些工具程序包括磁盘扫描程序、时钟程序,还有一些过程控制程序等。不使用标准的窗口用户界面的程序,像一些游戏等。 计算机科学与技术学院 张淼

591 CDocument:负责管理数据的类,提供保存和加载数据的功能,即数据的体。
CView:负责显示数据,是数据的用户窗口,为用户提供了文档的可视数据显示,它把文档的部分或全部内容在窗口中显示出来。以及给用户提供对数据的编辑和修改功能,即数据的面。 计算机科学与技术学院 张淼

592 CMyDoc::serialize(…) { //把数据读出文件 //放入Document中 }
CMyView::OnDraw(…) { //取得Document //调用GDI函数 //将数据表现出来 } CMyDoc::serialize(…) { //把数据读出文件 //放入Document中 } The C++complier geni ngs,all ofwhich a are Caused by the… Hello,I am J.J Hou,a Goodman. The difference,omit lpCmdLine parameter Hello,I am J.J Hou,a Goodman. Document是数据的体,View是数据的面 计算机科学与技术学院 张淼

593 CMyDoc::serialize(…) { //把数据从Document写入档案 }
CMyView::OnDraw(…) { //记录鼠标位置为DWORD //并将DWORD加入Document } CMyDoc::serialize(…) { //把数据从Document写入档案 } 04 00 FF FF F 6B 65 C C C View是Document的第一线,负责与使用者接触 计算机科学与技术学院 张淼

594 persistence 永久保存 放在RAM中的东西,生命受电力的左右,不可能永久保存;唯一的办法就是把它写到文件中去。
只要把其中的成员变量依次写入文件。 对于成员对象,先要记载其类名,然后才是对象中的数据。 读,当程序从文件中读到一个类名称时,如何实现一个对象? 计算机科学与技术学院 张淼

595 MFC的六大关键技术 MFC程序的初始化过程 RTTI(Runtime Type Information)运行时类型识别
Dynamic Creation 动态创建 Persistence 永久保存 Message Mapping 消息映射 Message Routing 消息传递 计算机科学与技术学院 张淼

596 Serialize序列化(串行化) MFC有一套Serialize机制,目的在于把文件名的选择,文件的开关,缓冲区的建立,数据的读写、提取运算符>>和插入运算符<<的重载、对象的动态创建等都包装起来。 Serialization:存储和恢复对象的过程。 负责这项任务的是MFC CObject类中的一个名为Serialize的虚函数,文件的读写操作均通过它。 计算机科学与技术学院 张淼

597 CArchive 没有基类 CArchive允许以永久的二进制形式(通常使用磁盘存储)保存复杂的对象网络。
这些对象被删除后存储器仍保存它们。以后程序员可以从保留存储器中装入这些数据,在内存中重组这些对象。——串行化 计算机科学与技术学院 张淼

598 IDR_MAINFRAME字符串资源中各子串的含义
(1)CDocTemplate::windowTitle,主窗口标题栏上的字符串,MDI程序不需要指定,将以IDR_MAINFRAME字符串为默认值。 (2)CDocTemplate::docName,缺省文档的名称。如果没有指定,缺省文档的名称是无标题。 (3)CDocTemplate::fileNewName,文档类型的名称。如果应用程序支持多种类型的文档,此字符串将显示在"File/New"对话框中。如果没有指定,就不能够在"File/New"对话框处理这种文件。 计算机科学与技术学院 张淼

599 IDR_MAINFRAME字符串资源中各子串的含义
(4)CDocTemplate::filterName,文档类型的描述和一个适用于此类型的通配符过滤器。这个字符串将出现在“File/Open”对话框中的文件类型列表框中。要和CDocTemplate::filterExt一起使用。 (5)CDocTemplate::filterExt,文档的扩展名。如果没有指定,就不能够在“File/Open”对话框中处理这种文档。要和CDocTemplate::filterName一起使用。 (6)CDocTemplate::regFileTypeId,如果你以::RegisterShellFileTypes向系统的注册表注册文件类型,此值会出现在HEY_CLASSES_ROOT之下成为其子项,并仅供Windows内部使用。如果没有指定,这种文件类型就无法注册。 (7)CDocTemplate::regFileTypeName,这也是存储在注册表中的文件类型名称。它会显示于程序中用以访问注册表的对话框内。 计算机科学与技术学院 张淼

600 CWinApp拥有一个对象指针:CDocManager* m_pDocManager
CDocManager拥有一个指针链表CPtrList m_templateList,用来维护一系列的Document Template。 CDocTemplate拥有三个成员变量,分别由Document,View,Frame的CRuntimeClass指针。另有一个成员变量m_nIDResource,用来表示此Document显示时应采用的UI对象。 计算机科学与技术学院 张淼

601 在MFC中,文档类负责管理数据,提供保存和加载数据的功能。视类负责数据的显示,以及给用户提供对数据的编辑和修改功能。
计算机科学与技术学院 张淼

602 MFC给我们提供Document/View结构,将一个应用程序所需要的“数据处理与显示”的函数空壳都设计好了,这些函数都是虚函数,我们可以在派生类中重写这些函数。有关文件读写的操作在CDocument的Serialize函数中进行,有关数据和图形显示的操作在CView的OnDraw函数中进行。我们在其派生类中,只需要去关注Serialize和OnDraw函数就可以了,其它的细节我们不需要去理会,程序就可以良好地运行。 计算机科学与技术学院 张淼

603 当我们按下“File/Open”,Application Framework会激活文件打开对话框,让你指定文件名,然后自动调用CGraphicDoc::Serialize读取文件。Application Framework还会调用CGraphicView::OnDraw,传递一个显示DC,让你重新绘制窗口内容。 计算机科学与技术学院 张淼

604 一个文档对象可以和多个视类对象相关联,而一个视类对象只能和一个文档对象相关联。
MFC给我们提供Document/View结构,是希望我们将精力放在数据结构的设计和数据显示的操作上,而不要把时间和精力花费在对象与对象之间、模块与模块之间的通信上。 一个文档对象可以和多个视类对象相关联,而一个视类对象只能和一个文档对象相关联。 计算机科学与技术学院 张淼


Download ppt "Visual C++ 程序设计 张 淼 哈尔滨工业大学(威海)计算机学院."

Similar presentations


Ads by Google