计算机可视化编程 基于Visual C++6.0的面向对象编程 第 四 讲 主讲教师:隋振 学时:32
第 3 章 C++面向对象程序设计
与传统的面向过程的程序设计语言相比,C++语言的最大特征是支持面向对象程序设计OOP(Object Oriented Programming),它引入了类、继承、多态和重载等面向对象的新机制。通过本章的学习,使我们系统地介绍C++面向对象设计的基本方法。 本章主要内容: 类和对象 类继承 多态性:虚函数,重载,模板
3.1 面向对象程序设计概述 3.1.1 结构化程序设计 结构化程序设计的特点: 是一种自上而下、逐步细化的模块化程序设计方法。 3.1 面向对象程序设计概述 3.1.1 结构化程序设计 结构化程序设计的特点: 是一种自上而下、逐步细化的模块化程序设计方法。 Wirth N的观点:算法 + 数据结构 = 程序 是一种面向过程程序设计方法,即一个程序是由多个过程(在C++中为函数)模块组成,过程之间通过函数参数和全局变量进行相互联系。
结构化程序设计的特点: 与非结构化程序相比,结构化程序在调试、可读性和可维护性等方面都有很大的改进。 代码重用性不高:以过程为中心设计新系统,除了一些标准函数,大部分代码都必须重新编写。 由于软、硬件技术的不断发展和用户需求的变化,按照功能划分设计的系统模块容易发生变化,使得开发出来的模块的可维护性欠佳。 面向过程模式将数据与过程分离,若对某一数据结构做了修改,所有处理数据的过程都必须重新修订,这样就增加了很多的编程工作量。
3.1.2 面向对象程序设计方法及特征 什么是对象: 现实世界是由各种各样的事物组成,包括真实的事物和抽象的事物。例如,人、动物、汽车(真实的事物)和程序、直线(抽象的事物)等。 每一类事物都有自己特定的属性(如大小、形状、重量等)和行为(如生长、行走、转弯、运算等),人们通过研究事物的属性和行为而认识事物。 在计算机科学中将这些现实世界中的事物称之为对象。对象是包含现实世界中事物特征的抽象实体,它反映了系统为之保存信息和与之交互的方法。 在程序设计领域,可以用如下公式表示: 对象 = 数据 + 作用于这些数据上的操作
什么是类: 为了描述属性和行为相同的一类对象,引入了类(class)的概念。 类是具有相同数据结构(属性)和相同操作功能(行为)的对象的集合,它规定了这些对象的公共属性和行为方法。 对象是类的一个实例,例如,汽车是一个类,而行驶在公路上的一辆汽车则是一个对象。 对象和类的关系相当于程序设计语言中变量和变量类型的关系。
一个简单例子: class Time { private: int hour; // 数据成员,表示小时 int minute; // 数据成员,表示分钟 int second; // 数据成员,表示秒 public: void setTime(int h, int m, int s) // 成员函数,设置时间 { hour=(h>=0 && h<24) ? h:0; minute=(m>=0 && m<60) ? m:0; second=(s>=0 && s<60) ? s:0; } void showTime() // 成员函数,输出时间 { cout<<hour<<':'<<minute<<':'<<second<<endl; } };
main() { Time EndTime; // 声明对象EndTime // 设置对象EndTime的时间(属性,数据成员) EndTime.setTime(12, 23, 36); cout<<"The time is:"; // 显示对象EndTime的时间 EndTime.showTime(); } 运行结果: The time is:12 : 23 : 36
3.2 C++类 为了支持面向对象程序设计,C++在C语言结构(struct)数据类型的基础上引入了类这种抽象数据类型。
3.2.1 类的定义与实现 C++类将对象的属性抽象为数据成员,将对象的行为抽象为成员函数,并对它们进行封装。数据成员又称成员变量,成员函数又称为方法。 C++类在形式上类似于C语言中用户自定义的结构类型,但定义类时规定了成员的访问控制权限。对象只能访问所属类的公有成员,而类的私有成员只能在类的成员函数中被访问。 C++类定义的基本形式
C++类定义的基本形式: class <类名> { private: <私有数据成员和私有成员函数的声明列表>; public: <公有数据成员和公有成员函数的声明列表>; protected: <保护数据成员和保护成员函数的声明列表>; };
说明: 类的定义由关键字class开始,其后为用户定义的类名,花括号括起来的部分称为类体。 关键字private、public和protected称为访问权限控制符,用来设置数据成员和成员函数的访问属性,其默认值为private。 private属性表示数据成员和成员函数是类的私有成员,它们只允许被本类的成员函数访问或调用,数据成员一般定义为private属性;
说明: public属性表示数据成员和成员函数是类的公有成员,它们允许被本类或其它类的成员函数(通过对象)访问或调用,是类的外部接口,成员函数一般定义为public属性; protected属性表示数据成员和成员函数是类的保护成员,它们允许被本类的成员函数和派生类的成员函数访问或调用。 例:
例 定义类Time(表示时间)。 私有数据成员hour、minute 和second只能在类的成员 函数中被访问或赋值; class Time{ private: // 最好不要省略private int hour; // 数据成员,表示小时 int minute; // 数据成员,表示分钟 int second; // 数据成员,表示秒 public: void setTime(int, int, int); // 成员函数,设置时间 void showTime(); // 成员函数,输出时间 }; 公有成员函数setTime、showTime 可在外部被调用,但必须通过一个 对象作为对象的成员使用。
类的实现: 利用C++类进行面向对象编程,定义类的成员只是完成了工作的第一步,最重要的工作是实现定义的类。 类的实现实质上是类的成员函数的实现,即定义类的成员函数。 成员函数的定义形式与一般函数的定义形式基本相同,但必须在成员函数名前加上类名和作用域限定符(::)。 成员函数的定义也可放在类体内(该函数声明之处),这时成员函数将变成内联函数。 例:
例 类Time的实现。 void Time::setTime(int h, int m, int s) { hour=(h>=0 && h<24) ? h:0; // 设置时间 minute=(m>=0 && m<60) ? m:0; second=(s>=0 && s<60) ? s:0; } void Time::showTime() cout<<hour<<':'<<minute<<':'<<second<<endl; private成员hour、minute和 second不允许外界存取, 所以为类Time增加两个 public成员函数,供外界 设置或显示private成员。
C++面向对象编程约定之一: 一般将类的定义放在头文件(.h)中,类的实现放在源文件(.cpp)中,而main主函数可以放在另一个源文件中。在源文件中用#include编译预处理指令包含头文件。 利用类声明对象: 对象是类的一个实例,定义并实现了类,就可以利用定义好的类来声明对象,即创建对象。声明对象的形式与声明普通变量类似,例如: Time t1, start ; point *pt1=&t1
成员的访问: 声明对象后,就可以通过成员运算符“ . ”或指向运算符“->”访问对象的公有成员,但不能访问对象的私有成员。 例如,公有成员函数调用: t1.setTime(); start.showTime(); pt1->setTime(); 而任何形如t1.hour、t1.minute、start.second等私有成员变量的直接访问都是非法的。 例:
例 类Time的使用,声明对象并设置对象属性。 main() { Time EndTime; // 声明对象EndTime EndTime.setTime(12, 23, 36); // 设置对象EndTime的时间 cout<<"The time is:"; EndTime.showTime(); // 显示对象EndTime的时间 }
? 3.2.2 构造函数和析构函数 在定义类时不能对成员变量进行初始化,因为无法确定成员变量属于哪一个对象。 3.2.2 构造函数和析构函数 如何进行成员 变量的初始化? 在定义类时不能对成员变量进行初始化,因为无法确定成员变量属于哪一个对象。 成员变量一般都定义为私有属性,也不能在声明对象后利用赋值运算对成员变量进行初始化。 成员变量的初始化一般是利用一个名为构造函数的成员函数来完成。 ?
什么是构造函数: 构造函数是一种特殊的成员函数,它是在创建对象时(声明或new动态创建)系统自动调用的成员函数。 什么是析构函数: 析构函数也是一种特殊的成员函数,它是在对象生存期结束时系统自动调用的成员函数。 构造函数的名称与类名相同,析构函数的名称必须在类名前加上“~”符号。注意,构造函数和析构函数不能指定任何返回值类型,包括void返回类型。
计算机可视化编程 基于Visual C++6.0的面向对象编程 第 五 讲 主讲教师:隋振 学时:32
3.3 类的继承 继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。 3.3 类的继承 继承 继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。 在C++面向对象程序设计中,所谓类的继承就是利用现有的类创建一个新的类。新类继承了现有类的属性和行为。 为了使新类具有自己所需的功能,它可以扩充和完善现有类的属性和行为,使之更具体。 微软基础类MFC就是通过类的继承来体现类的可重用性和可扩充性。 发扬
3.3.1 基类和派生类 1. 问题的提出 在现实世界中,一类事物的对象常常也属于另一类事物。 3.3.1 基类和派生类 1. 问题的提出 在现实世界中,一类事物的对象常常也属于另一类事物。 在面向对象程序设计方法中,一个类的对象也常常是另一个类的对象,即一个类具有了另一个类的属性和方法。 在定义一个类时,根据类的继承性,我们能够且应尽可能地利用现有的类来定制新的类,而不必重新设计新的类。
2. 基类和派生类的概念 在继承关系中,新定义的类称为被继承类的派生类或子类,而被继承的类称为新定义类的基类或父类。派生类继承了基类的所有成员。 一个派生类也可以作为另一个派生类的基类。 3. 派生类的定义 class <派生类名> : [<派生方式>] <基类名> { . . . // 派生类新增加的成员声明列表 };
说明: 派生方式决定了基类的成员在派生类中的访问权限。派生方式共有三种:public、private和protected(缺省值为private)。 虽然派生类继承了基类的所有成员,但为了不破坏基类的封装性,无论采用哪种派生方式,基类的私有成员在派生类中都是不可见的,即不允许在派生类的成员函数中访问基类的私有成员。
三种派生方式的区别: 采用public派生,基类成员的访问权限在派生类中保持不变,即基类所有的公有或保护成员在派生类中仍为公有或保护成员。public派生最常用。 (1) 可以在派生类的成员函数中访问基类的非私有成员; (2) 可通过派生类的对象直接访问基类的公有成员。 采用private私有派生,基类所有的公有和保护成员在派生类中都成为私有成员,只允许在派生类的成员函数中访问基类的非私有成员。private派生很少使用。 采用protected保护派生,基类所有的公有和保护成员在派生类中都成为保护成员,只允许在派生类的成员函数和该派生类的派生类的成员函数中访问基类的非私有成员。
例 定义类Point,然后定义类Point的派生类Circle。 #include <iostream.h> class Point // 定义基类,表示点 { private: int x; int y; public: void setPoint(int a, int b) { x=a; y=b; }; // 设置坐标 int getX() { return x; }; // 取得X坐标 int getY() { return y; }; // 取得Y坐标 };
class Circle : public Point // 定义派生类,表示圆 { private: int radius; public: void setRadius(int r) { radius=r; }; // 设置半径 int getRadius() { return radius; }; // 取得半径 int getUpperLeftX() { return getX()-radius; }; // 取得外接正方形左上角的X坐标 int getUpperLeftY() { return getY() + radius; }; // 取得外接正方形左上角的Y坐标 };
说明: 派生类Circle通过public派生方式继承了基类Point的所有成员(除私有成员外所有成员的访问权限不变),同时还定义了自己的成员变量和成员函数。 若将类Circle的派生方式改为private或protected,则下述语句是非法的:c.setPoint(200, 250); 无论哪种派生方式,派生类都继承了基类的所有成员,包括私有成员。我们虽然不能在派生类Circle中直接访问私有数据成员x和y,但可以通过继承的公有成员函数getX()、getY()和setPoint()访问或设置它们。 ! 容易 混淆
3.3.3 多重继承 1. 单继承和多重继承的概念 class A class A class B class B class C 3.3.3 多重继承 1. 单继承和多重继承的概念 class A class A class B class B class C class C 一个派生类同时从多个基类派生而来,即有多个直接基类 —— 多重继承 每个派生类只有一个直接基类 —— 单继承
3.7 Microsoft Visual C++的语法扩充 经过多年的发展,C++有很多版本,微软公司就推出了不少C++编译器。微软公司最早推出的C++编译器是Microsoft C++(1.0版到8.0版)。1993年,微软推出了第一个可视化编译器即Visual C++ 1.0,以后不断推出它的新版本,2001年推出了Visual C++ 7.0。1998年,美国国家标准化协会ANSI和国际标准组织ISO联合正式制定了C++国际标准。Visual C++编译器除了遵循一般的C++标准,还结合自己的开发环境、工具和MFC类对C++语法进行了一些扩充。
3.7.1 Visual C++自定义数据类型 数据类型 意义 FAR 对应于far NEAR 对应于near CONST 对应于const BOOL 布尔类型,值为TRUE(真)或FALSE(假) UINT 32位无符号整形,对应于unsigned int BYTE 8位无符号整形,对应于unsigned char WORD 16位无符号整形,对应于unsigned short int DWORD 32位无符号长整形,对应于unsigned long int SHORT 短整形 LONG 32位长整形,对应于long LONGLONG 64位长整形 FLOAT 浮点型,对应于float CHAR Windows字符 VOID 任意类型
LPCSTR 32位字符串指针,指向一个常数字符串 LPSTR 32位字符串指针 LPVOID 32位指针,指向一个未定义类型的数据 LPARAM 32位消息参数,作为窗口函数或回调函数的参数 LPRESULT 32位数值,作为窗口函数或回调函数的返回值 LPCRECT 32位指针,指向一个RECT结构的常量 PROC 指向回调函数的指针 WNDPROC 32位指针,指向一个窗口函数 WPARAM 16位或32位数值,作为窗口函数或回调函数的 参数 HANDLE 对象句柄,其它还有HPEN、HWND、 HCURSOR、HDC等 CONST 常量 COLORREF 32位数值,代表一个颜色值
3.7.3 MFC MFC是一个编程框架 MFC (Microsoft Foundation Class Library)中的各种类结合起来构成了一个应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序,使程序设计更加简单。 MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C++提供了相应的工具来完成这个工作:AppWizard可以用来生成初步的框架文件(代码和资源等);资源编辑器用于帮助直观地设计用户接口;ClassWizard用来协助添加代码到框架文件;最后,编译,则通过类库实现了应用程序特定的逻辑。
3.7.3 MFC (Hierarchy Chart)
3.7.4 编程规范 为了阅读理解源程序,Visual C++源程序中变量的取名一般采用匈牙利表示法则。该法则要求每一个变量名都有一个前缀,用于表示变量的类型,后面是代表变量含义的一串字符。 例如:前缀n表示整形变量,前缀sz表示以0结束的字符串变量,前缀lp表示指针变量。这些前缀还可以组合起来使用。前缀一般是小写字母,前缀后的第一个字符要大写。如:nWidth表示一个整形变量,lpszMyname表示一个字符串的指针。 在给类和成员变量取名时也使用特定的前缀,如CView是一个类(视图类),m_xStart是一个类的整形成员变量(起点的X坐标)。
Visual C++中的前缀及说明 前缀 表示的类型 例 子 a 数组变量 aScore[50] b 布尔变量 bFlag,bIsEnd c 字符变量 cSex n,i 整形变量 nWidth,iNum x、y 无符号整形变量(X、Y坐标) xStart,yPos s 字符串变量(不常使用) sMyName sz 以0结束的字符串变量 szMyName p 指针变量 pszString,pMyDlg lp 长指针变量 lpszMyname h 句柄 hWnd,hPen,hDlg fn 函数 FnCallBack() m_ 类的成员变量 m_xStart C 类和结构 CDialog,CView,CMysdiApp,CRuntimeClass Afx,afx,AFX 应用程序框架 AfxGetApp(),afx_msg ID*_ 资源标识 ID_,IDD_,IDC_,IDB_,IDI_