1、C+程序设计语言-第四讲类第一节 面向对象程序设计思想一、软件系统开发方法 现在常用的软件系统开发的方法有:传统方法、结构化方法、传统方法、结构化方法、信息建模方法、面向对象方法信息建模方法、面向对象方法。下图是系统开发方法论技术和工具:方法系统流数据与用户的沟通处理逻辑传统系统流程图表格、版面、网格图采访英语、脚本、程序流程图结构化数据流图数据词典、数据结构图、E-R图采访、用户审查、讨论判定树/表,结构化英语、结构图 数据建模(信息工程)商业领域分析、处理模型商业领域分析、E-R图采访、用户审查、讨论,头脑风暴商业系统设计面向对象对象模型对象模型、属性采访、用户审查、讨论,头脑风暴对象、模
2、型、服务、场景、判定树/表,结构化英语教材中强调了两种方法:结构化程序设计面向对象程序设计具体内容参看教材p95-96二 面向对象程序设计的基本特点 面向对象软件开发方法是吸收了软件工程领域有益的概念和有效的方法而发展起来的一种软件开发方法。它集抽象性抽象性、封装性封装性、继承性继承性和多态性多态性于一体,可以帮助人们开发出模块化、数据抽象程度高的,体现信信息隐蔽息隐蔽、可复用可复用、易修改易修改、易扩充易扩充等特性的程序。在系统分析与设计中,抽象用来确定必要的信息系统需求,删除不必要的部分。为了突出重点,抽象有意地忽略信息系统的某些性质、属性或功能。抽象是一种突出重点、去掉细节的总结。例如:
3、地图的抽象,根据抽象的层次,地图可分为国家地图、行政省或地区地图、城市地图、邮政代码地图等。参看教材p97封装或信息隐蔽 封装或信息隐蔽指软件的组成部分(模块、子程序、方法等)应该互相独立,或者隐藏设计的细节。在系统分析和设计中,系统分析员把问题域分解为小的封装单元,这些分析和设计决定最终成为软件模块,封装就是有利于灵活地局部修改和维护软件模块。封装或信息隐蔽用于以下两种情况1、当人们只能使用或接触整个系统的某个子集时。例如:当开发一个信息系统时,开发小组成员受命开发系统的某个部分,不需接触其它成员开发的部分。所以,封装或信息隐蔽适合于团队开发。2.有意防止信息系统的某些部件注意或利用系统的其
4、它部件。这涉及到封装的另外一个方面-分配责任。正如现实生活中一个人负有某种责任,信息系统中的一个部件也有自己的责任,比如自动取款机的付钞功能,系统其它部件则担任付钞以外的其它责任。注意:在传统方法中,封装通常局限于将功能和数据分开封装;而在面向对象的方法中,封装将功能和数据同时装入对象中。继承 继承是表示相似性质的机制。正如一个人同时继承父母的外貌特点一样,信息系统组成成分也从有关部件继承某些特点。例如:右图显示了人和教师、学生及管理者之间的继承关系。人和教师、学生及管理者之间是继承(父子)关系,教师、学生、管理者之间是兄弟关系。人的特点如姓名、性别、年龄等,为继承层次中所有子节点继承;有些特
5、点则为子节点独有,如授课时间只对教师适用,平均分数只对学生适用,头衔只对管理者适用。同样,一些人能做的事也对教师、学生及管理者适用,例如吃饭、睡觉等,同样,有些事则为子节点独有。注意:继承在层次中是自上而下单向进行的。管理者人学生教师多态多态一般指具有多种形态的能力。如:水有三种形态,液体、气体和固体。在某种程度上,在车里观察交通灯的反应是多态的,当交通灯闪现不同颜色时,人的反应是不同的。又如打印程序可以打印字符、数字、图形和图像,打印程序由于知道如何打印图文,因而是多态的。第二节 面向对象的方法 面向对象是一种软件开发方法,软件开发的目的是为了进行数据处理,所以程序中包含了数据以及对数据的操
6、作代码。面向对象的编程解决问题的思路从对象面向对象的编程解决问题的思路从对象(人、地方、事情等)角度入手,而不像传统方法与(人、地方、事情等)角度入手,而不像传统方法与结构化方法一样从功能入手。结构化方法一样从功能入手。面向对象的开发强调从问题域的概念问题域的概念到软件程序和界软件程序和界面面的直接映射直接映射;心理学的研究也表明,把客观世界看把客观世界看成是许多对象更接近人类的自然思维方式成是许多对象更接近人类的自然思维方式。对象比函数更为稳定;软件需求的变动往往是功能相关的变动,而其功能的执行者-对象-通常不会有大的变动。另外,面向对象的开发也支持、鼓励软件工程实践中的信息隐藏、数据抽象和
7、封装。在一个对象内部的修改被局部隔离。面向对象开发的软件易于修改、扩充和维护。面向对象编程-基本概念 在面向对象编程中,程序被看作是相互协作的对象集合,每个对象都是某个类的实例,所有的类构成一个通过继承关系相联系的层次结构。面向对象的语言常常具有以下特征:对象生成功能、消息传递机制、类和遗传机制对象生成功能、消息传递机制、类和遗传机制。这些概念当然可以并且也已经在其他编程语言中单独出现,但只有在面向对象语言中,他们才共同出现,以一种独特的合作方式互相协作、互相补充。过程化编程模式:参数输入-|代 码|-结果输出 为实现某个功能,参数被传入某个处理过程,最后传回计算结果。面向对象编程模式:界面-
8、|对象数据结构和 操作 OO编程中,功能是通过与对象的通讯通过与对象的通讯获得的。对象对象可以被定义为一个封装了状态和行为的实体;或者说是数据结构(或属性)和操作。状态状态实际上是为执行行为而必须存于对象之中的数据、信息。对象的界面对象的界面,也可称之为协议,是一组对象能够响应的消息的集合。消息消息是对象通讯的方式,因而也是获得功能的方式。对象受到发给他的消息后,或者执行一个内部操作(有时成为方法或过程),或者再去调用其他对象的操作。所有对象都是类的实例。类是具有相同特点的对象的集合,或者也可以说,类是可用于产生对象的一个模版。对象响应一个消息而调用的方法,由接受该消息的对象自己决定。类可以以
9、一种层次结构来安排。第三节 面向对象的标记参看教材p100-101第四节 类与对象一一 类和类定义类和类定义二二对象和对象定义对象和对象定义三三初始化问题初始化问题四四静态成员静态成员五五小结小结一一 类和类定义类和类定义-1、类的声明 类和对象是面向对象程序设计(OOP)的两个最基本概念。所谓对象就是客观事物在计算机中的抽象描述;类是对具有相似属性和行为的一组对象的统一描述。类的定义类的定义 C+的类是在结构体的基础上扩充而来的。类是把各种不同类型的数据(称为数据成员)和对数据的操作(成员函数)组织在一起而形成的用户自定义的数据用户自定义的数据类型。类型。C+中,类定义包括类说明和类实现两大
10、部分。说明部分提供了对该类所有数据成员和成员函数的描述,而实现部分提供了所有成员函数的实现代码。类定义的一般形式为:class 类名private:数据成员或成员函数 protected:数据成员或成员函数 public:数据成员或成员函数;/注意:此处分号不要少定义类时,当未指明成员是哪部分时,默认是属于private成员,但一般不要采用默认形式。几点说明(1)大括号的部分是类的成员(数据成员和函数成员),它们分成三部分,分别由private、public、proctected三个关键字后跟冒号来指定。这三部分可以任何顺序出现,且在一个类的定义中,这三部分并非必须同时出现。(2)类的数据成员
11、可以使任何数据类型;(3)类的数据成员允许使用存储类型static,但其它三种存储类型不能使用;(4)类中数据成员不允许使用表达式进行初始化;(5)类中数据成员与成员函数可用const修饰。(6)经常习惯地将类定义的说明部分或者整个定义部分(包含实现部分)放到一个头文件中。如:下例中定义描述图书的类定义 class Record private:/private成员 char bookname20;/数据成员bookname,/用于表示图书的名称 int number;/数据成员number,表示图书编号 public:/public成员 void regist(char*a,int b);/
12、成员函数regist,用于给 /各数据成员赋值 void show();/成员函数show,显示各数据成员的值;根据类的定义,可看出:类是实现封装的工具,所谓封装就是将类的成员按使用或存取的方式分类,有条件地限制对类成员的使用,而封装是通过public和private与成员函数实现的。private的成员构成类的内部状态,public的成员则构成与外界通信的接口,通过public的成员函数来使用private的数据成员,从而在C+中实现了封装。2、类成员的访问控制 从访问权限上来分,类的成员又分为:公有的公有的(public)、私有的私有的(private)和保护的保护的(protected)
13、三类。公有的成员公有的成员用public来说明,公有部分往往是一些操作(即成员函数),它是提供给用户的接口功能。这部分成员可以在程序中引用。私有的成员私有的成员用private来说明,私有部分通常是一些数据成员,这些成员是用来描述该类中的对象的属性的,用户是无法访问它们的,只有成员函数或经特殊说明的函数才可以引用它们,它们是被用来隐藏的部分。保护的成员保护的成员用protected来说明,这种成员只有该类的派生类可以访问,其余的在这个类外不能访问。保护类(protected)将在以后介绍。Who可以直接访问Who不能直接访问目的与用途private本类Public成员函数、本类保护成员函数、类
14、的友元对象数据成员一般放在私有部分,实现信息隐蔽。protected本类公有成员函数、本类派生类(公有和私有派生)成员函数本类对象、派生类对象便于子类访问的成员一般放在保护部分。public本类公有成员函数、派生类成员函数、公有派生类对象私有派生类对象成员函数一般放在公有部分,实现共享。类成员的访问权限表返回返回类名:私有部分的成员保护部分的成员公有部分的成员 对于一个无继承关系的单一类(独立类),其访问权限可参考下图:该类的对象3、类成员函数的特性 类的成员函数可以分为内联函数内联函数和外联函数外联函数。内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。而说明在类体内,定义
15、在类体外的成员函数叫外联函数外联函数。外联函数的函数体在类的实现部分。引入内联函数的目的是为了解决程序中函数调用的效率问题。因为内联函数在调用时不是像一般函数那样要转去执行被调用函数的函数体,执行完成后再转回调用函数中,执行其后语句,而是在调用函数处用内在调用函数处用内联函数体的代码来替换,这样将会节省调用开销,提联函数体的代码来替换,这样将会节省调用开销,提高运行速度高运行速度。在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。显然,这种做法不会产生转去转回的问题,但是由于在编译时将函数休中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而
16、在时间代销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。内联函数的定义方法 内联函数的定义方法与一般函数一样,只是在函数定义的头前加上关键字inline即可。如:inline int add_int(int x,int y,int z)return x+y+z;返回返回二二 对象和对象定义对象和对象定义-1、对象的创建类是抽象的概念,而对象是具体的,类只是一种数据类型,而对象是属于该类(数据类型)的一个变量,占用了各自的存储单元,每个对象各自具有了该类的一套数据成员(静态成员除外),而所有成员函数是所有对象共有的。每个对象的函数成员都通过指针指向同一个代码空间。(参看
17、教材p124图)对象的定义格式:(1)class 类名 对象1,对象2,对象n;(2)类名 对象1,对象2,对象n;(3)类名 对象1(参数表),对象n(参数表);如:TDate date1,date2,*Pdate,date31;2、对象成员的使用 访问对象的成员包括读写对象的数据成员和调用它的成员函数,其访问格式是:.或者 .()前者用来表示数据成员的,后者用来表示成员函数的。如:上例中已定义了类Record,则:Record book1,book2;/此处的book1,book2就是Record /类型,也就是类的两个对象 可参看教材p125程序5-3-1 如上例中,对象的主函数如下:v
18、oid main()Record book1,book2;/定义对象book1和book2 /调用成员函数regist,给book1的两个数据成员 /bookname和number赋值 book1.regist(“C+编程教程”,1001);/调用成员函数regist,给book2的两个数据成员赋值 book2.regist(“C+语言参考”,1002);/调用成员函数show,显示book1对象的数据成员 /bookname和number的值 book1.show();/调用成员函数show,显示book2对象的数据成员 /bookname和number的值 book2.show();如改为
19、下面的代码,则错误:void main()Record book1,book2;/由于bookname和number是类Record的私有成员,在类外不能直接使用,即对象不能直接使用。strcpy(book1.bookname,“C+编程教程”);book1.number=1001;strcpy(book2.bookname,“C+语言参考”);book2.number=1002;book1.show();book2.show();访问对象的成员需注意以下几点:访问对象的成员需注意以下几点:1.对于类的私有成员,只能通过其成员函数来访问,不能在类外对私有成员访问。2.调用成员函数时要在函数名之
20、前加上对象名和.即可,即先指明对象,再指明成员。也可以采用指向对象的指针来访问,但要在函数名前加上指针变量名和“-”。3.任何对对象私有数据的访问都必须通过向对象发送消息来实现,而且所发送的消息还必须是该对象能够识别和接受的。在C+中,消息发送正是通过公有成员函数的调用来实现的。由于类接口隐藏了对象的内部细节,用户只能通过类接口访问对象,因此,在类设计中必须提供足够的公有接口以捕获对象的全部行为,这正是类设计中的一个最基本的要求。返回返回3、对象数组参看教材p305-306第五节第五节 构造函数河析构函数构造函数河析构函数 全局变量和静态变量在定义时,将自动赋初值为0;局部变量在定义时,其初始
21、值不固定的。而当对象被定义时,由于对象的意义表达了现实世界的实体,所以一旦定义对象,就必须有一个有意义的初始值,在C+中,在定义对象的同时,给该对象初始化的方法就是利用构造函数。下面分别从三个方面讲述初始化问题:1、类中数据成员的初始化:构造函数和析构函数 由于在类的定义中不能对数据成员初始化,那么类中数据成员利用构造函数进行初始化。2、类中常量和引用的初始化:初始化表 3、类中数据成员的初始化:初始化表 4、类的对象初始化:拷贝构造函数1、构造函数和析构函数 构造函数和析构函数是在类体中说明的两种特殊的成员函数。构造函数构造函数的功能是在创建对象时,使用给定的值来将对象初始化。析析构函数构函
22、数的功能是用来释放一个对象的,在对象删除前,用它来做一些清理工作,它与构造函数的功能正好相反。下面举一例子来说明构造函数和析构函数的特点:程序5-1class TDatepublic:TDate(int y,int m,int d);TDate();int IsLeapYear();void Print();private:int year,month,day;/类的实现部分TDate:TDate(int y,int m,int d)year=y;month=m;day=d;cout构造函数已被调用。n;TDate:TDate()cout析构函数被调用。n;int TDate:IsLeapYe
23、ar()return(year%4=0&year%100!=0)|(year%400=0);void TDate:Print()coutyear.month.dayendl;构造函数的特点如下:1)构造函数是成员函数,函数体可写在类体内,也可定在类体外。2)构造函数是一个特殊的函数,该函数的名字与类名相同;该函数不指定类型说明,它有隐含的返回值,该值由系统内部使用。该函数可以一个参数,也可以有多个参数。3)可以定义多个构造函数。即构造函数可以重载,也就是可以定义多个参数个数不同的函数。.。4)程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数。析构函数的特点如下:1)析构函数是成员函
24、数,函数体可写在类体内,也可定在类体外。2)析构函数也是一个特殊的函数 它的名字同类名,并在前面加“”字符,用来与构造函数加以区别;析构函数不指定数据类型,并且也没有参数。3)一个类中只可能定义一个析构函数。4)析构函数可以被调用,也可以系统调用。在下面两种情况下,析构函数会被自动调用。如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用。当一个对象是使用new运算符被动态创建的,在使用delete运算符释放它时,delete将会自动调用析构函数。缺省构造函数和缺省析构函数缺省构造函数和缺省析构函数 在类定义时没有定义任何构造函数时,则编译器自动生成一个不带参数的缺
25、省构造函数,其格式如下::()同理,如果一个类中没有定义析构函数时,则编译系统也生成一个称为缺省析构函数,缺省析构函数是一个空函数,其格式如下::2、类中常量和引用的初始化 上一小节讲述了应用析构函数对数据成员变量的初始化,那么如何初始化常量和引用?如何初始化常量和引用?(应用初始化表应用初始化表,注意在类定义中不能直接用。)返回返回#includeclass initi_data private:int x;int℞const float pi;public:initi_data(int x1):rx(x),pi(3.14)x=x1;void display()cout x=x ,rx
26、=rx ,pi=pi endl;void main()initi_data ob(100);ob.display();结果:结果:x=100,rx=100,pi=3.14那么,从左边例子中可以看出,初始初始化表的格式:化表的格式:析构函数:引用名(变量),常量名(常数),3、类中对象成员的初始化1.对象成员的概念 C+中允许将一个已定义的类的对象作为另一个类的数据成员,这称为类的组合类的组合。当一个类的成员是某一个类的对象时,则称该对象为对象成员(或子对象)。如:class A ;class Bprivate:A a;public:;在类中出现了子对象或称对象成员时,该类的构造函数要包含对子对
27、象的初始化,通常采用成员初始化表的方法来初始化子对象。在成员初始化表中包含对子对象的初始化和对类中其他成员的初始化。下面举一例子说明成员初始化的构造。#include class A private:int A1,A2;public:A(int i,int j)A1=i;A2=j;void print()coutA1“,”A2endl;class Bprivate:A a;/对象成员 int b;public:B(int i,int j,int k):a(i,j),b(k)void print();void B:print()a.print();coutbendl;void main()B b
28、(6,7,8);b.print();该程序的输出结果为:6,7 8其中,a(i,j),b(k)是成员初始化表,它有二项,前一项是给子对象a初始化,其格式如下:()后一项是给类B的数据成员b初始化。这一项也可以写在构造函数的函数体内,使用赋值表达式语句b=k;给类B的数据成员初始化。4、类的对象初始化 拷贝构造函数是C+中引入的一种新的构造函数。定义一个拷贝构造函数的方式是:类名(const 类名&形式参数)函数体 由此可看出:(1)拷贝构造函数的名称与类的名称相同,且它只有一个参数,该参数就是对该类对象的引用。(2)拷贝构造函数的功能是用于实现对象值的拷贝,通过将一个同类对象的值拷贝给一个新对
29、象,来完成对新对象的初始化,即用一个对象去构造另外一个对象。在下述三种情况下,需要用拷贝初始化构造函数来用一个对象初始化另一个对象。1)明确表示由一个对象初始化另一个对象时,如:TPoint P2(P1);2)当对象作为函数实参传递给函数形参时,如:上例 P=f(N);3)当对象用为函数返回值时。如果类中没有说明拷贝初始化构造函数,则编译系统自动生成一个具有上术形式的缺省拷贝初始化构造函数。作为该类的公有成员。实例 Example是一个人员信息类。用普通构造函数生成obj1,用拷贝构造函数生成obj2。#include#include class Example private:char*na
30、me;int num;public:example(int i,char*str)/构造函数定义 name=str;num=i;example(const Example&x)/拷贝构造函数定义 num=x.num;void list()/定义显示函数list cout数据成员num的值=numendlendl;void main()example obj1(215,“张立三”);/调用函数Example(int i,char*str)构造obj1 example obj2(obj1);/使用拷贝构造函数构造obj2 obj2.list();/显示obj2的值 /其它程序部分 程序的执行结果是
31、:数据成员num的值=215数据成员num的值=215说明:(1)上例中在main函数中的语句Example obj2(obj1);在执行时,系统会自动调用类Example的拷贝构造函数完成对obj2对象的构造。(2)如果程序员没有为所设计的类提供显式的拷贝构造函数,则系统会自动提供一个默认的拷贝构造函数,其功能是:把作为参数的对象的数据成员逐个拷贝到目标变量中,这称为成员级复制(或浅拷贝)。返回返回第六节 类的组合 第七节 类模板 模板就是使程序能够对不同类型的数据进行相同方式的处理。C+中的模板分为类模板和函数模板。说明类模板的一般格式为:template class 类模板名 priva
32、te:私有成员定义 protected:保护成员定义 public:公有成员定义 ;几点说明几点说明(1)类型形式参数表可以包含基本数据类型,也可以包含类类型,如果是类类型,则须加前缀class。当参数有多个时,需用逗号隔开。(2)类模板中的成员函数的定义,可以放在类模板的定义体中(此时与类中的成员函数的定义方法一致),也可以放在类模板的外部定义成员函数,此时成员函数的定义格式如下:template 函数值的返回类型 类模板名:成员函数(形参)函数体 (3)利用类模板定义的类称为“类属类”或“参数化类”,它只是描述了适用于一组类型的通用样板,本身还不是一种真正的类类型,所以不能用类属类直接创建
33、对象实例。(4)类属类的实例化,需要用下列格式的语句:类模板名 对象名;类属类定义实例参见教材p276程序9.2.1与程序9.2.2类属类的实例化实例参见教材p278程序9.2.3【程序【程序9_1】定义类模板ABC,内含成员函数set和get。用ABC生成对象abc1和abc2。它们的数组元素数不同,显示的结果也不同。类模板的使用方法可以总结为:(1)给出类模板的定义体。(2)在适当的位置创建一个类模板的实例,即一个实实在在的类定义,同时创建该模板类的对象。(3)有了对象名,以后的使用就和普通类的对象是一致的。小结小结 掌握类与对象的概念,类与对象的定义方法及二者间的区别。掌握类的成员函数的定义方法、保存方法及调用方法。掌握类中成员的访问机制和方法。了解对象的作用域和生存期。理解并掌握构造函数、析构函数、拷贝构造函数、默认构造函数和缺省参数的构造函数的含义、定义方法以及在对象的构造和撤消中的作用。理解并掌握当一个类的对象作为另一个类的数据成员时,利用初始化表调用构造函数的方法、构造函数的执行顺序。掌握静态成员的使用。返回返回