实验6 类和对象练习.docx
- 文档编号:28216963
- 上传时间:2023-07-09
- 格式:DOCX
- 页数:34
- 大小:45.76KB
实验6 类和对象练习.docx
《实验6 类和对象练习.docx》由会员分享,可在线阅读,更多相关《实验6 类和对象练习.docx(34页珍藏版)》请在冰豆网上搜索。
实验6类和对象练习
实验6类和对象编程练习
一.实验目的及要求
1.学习面向对象设计的基本概念:
模块分解、类、函数、对象等的概念和编程。
2.实践简单类的设计,包括数据抽象、成员函数编写、封装、重载、静态成员等,并应用这些类完成给定的计算任务。
二.实验内容及步骤
1.关于面向对象的知识点和例题
⑴模块分解的两种思路
上一章给出的程序实例,要解决的问题都十分简单,代码量最多也只有20行源程序。
而现实中,一个简单的信息系统、手机软件或者游戏,代码量至少也有几千到几万行源程序,更不用说Windows这样的操作系统了。
若像上章实例那样把代码全都放在一个main函数中,仅编译一遍就需要很长时间,更不要说查错和更版了。
因此,必须寻求一个进行模块分解的有效途径。
模块是一个独立的程序单元,具有明确的定义、可以单独地进行设计和编译,并可以被复用。
如何将一个大的程序分解为多个小的模块,曾经有面向过程和面向对象两种完全不同的思路。
前者以功能为线索,称为“功能分解”(functionaldecomposition),分解的结果为函数的集合;后者以对象为线索,称为“面向对象设计”(objectorienteddesign),分解的结果为对象的集合。
功能分解的基本思想是从软件的功能出发,将一个大的计算问题分解为一系列较小的子问题,每个子问题又进一步细分为更小的子问题,…,如此分解下去,最后的子问题就是一个功能单一的模块,可由一个函数(function)来实现。
这便是上世纪70年代“自顶向下,逐步细化”的“软件工程”思想,因它与C语言的“结构化程序设计”一致,故在那个年代曾经风靡一时。
功能分解乍看起来无不合理,但实际上却存在严重的弊病。
事实上,功能是相对计算的对象——数据定义的,不同的数据应当有不同计算方法。
将功能与数据分割开来考虑问题,一旦数据改变,所有功能便全部失效,就像设计“sayhello”这一功能一样,不考虑功能实现的载体或功能基于的属性,则设计出的SayHello函数就不知是说“你好”呢,还是“喵”或者“汪汪”。
面向过程方法是通过针对不同的对象设计不同的函数,或向函数传递不同的数据来区分不同的计算的,例如下面三组程序:
①针对不同对象设计不同的函数
#include"stdafx.h"
usingnamespaceSystem;
voidSayHelloForCat()
{
Console:
:
WriteLine(L"喵");
}
voidSayHelloForDog()
{
Console:
:
WriteLine(L"汪汪");
}
voidSayHelloForHuman()
{
Console:
:
WriteLine(L"你好");
}
intmain(array : String^>^args) { SayHelloForHuman(); SayHelloForCat(); SayHelloForDog(); return0; } ②针对不同对象向函数传递不同的数据 #include"stdafx.h" usingnamespaceSystem; voidSayHello(String^hello){ Console: : WriteLine(hello); } intmain(array : String^>^args){ String^hello; hello=L"你好"; SayHello(hello); hello=L"喵"; SayHello(hello); hello=L"汪汪"; SayHello(hello); return0; } ③针对不同对象向函数传递不同的参数 #include"stdafx.h" usingnamespaceSystem; voidSayHello(intflag){ if(flag==0) Console: : WriteLine(L"你好"); elseif(flag==1) Console: : WriteLine(L"喵"); elseif(flag==2) Console: : WriteLine(L"汪汪"); } intmain(array : String^>^args){ SayHello(0); SayHello (1); SayHello (2); return0; } 在面向对象设计中,功能是和具体的对象绑定的,函数是针对对象来定义的,这样设计出的函数就不会因对象的不同而产生歧义。 对象是现实世界的一个实体,也就是我们所说的“东西”(当然也包括人)。 所有对象都具有属性和行为,属性为对象所固有,如人的躯体,可用数据来表示;行为是对象的活动或功能,如人的吃饭、思考等,可用函数来定义。 只有属性而无行为的人是植物人,当然行为也不能脱离属性而存在。 由于对象的类型是由属性决定的,因此功能根据对象定义也就是根据属性定义,也可以说功能是为属性服务的,就像吃饭是为了躯体的存活服务的一样,因此,对象不仅把数据和处理数据的函数捆绑在一起,而且建立了它们之间的关系及其定位,即函数不再作为单独的模块存在,而是以数据为中心,为数据提供计算服务而存在。 就像是政府不应再是“衙门”,而应作为为人民群众提供服务的机构而存在一样。 //例3.1定义人、猫、狗三个类,并分别让它们的对象“Sayhello” #include“stdafx.h” usingnamespaceSystem; refclassHuman{//定义Human类,只包括一个函数 public: voidsayHello(){Console: : WriteLine("你好");} }; refclassCat{//定义Cat类,只包括一个函数 public: voidsayHello(){Console: : WriteLine("喵");} }; refclassDog{//定义Dog类,只包括一个函数 public: voidsayHello(){Console: : WriteLine("汪汪");} }; intmain(array : String^>^args){ Humanme;//声明一个Human对象me me.sayHello();//Letmesayhello(输出“你好”) Cather;//声明一个Cat对象her her.sayHello();//Lethersayhello(输出“喵”) Doghim;//声明一个Dog对象him him.sayHello();//Lethimsayhello(输出“汪汪”) return0; } ⑵类的组成和设计 每样东西都属于某种类型,比如一只铅笔,“铅笔”便是它所属的类型,张三是一个人,“人”便是它所属的类型。 人们总要根据对象的特征为它分类,分类中要进行适当的抽象,保留主要特征、忽略次要特征,比如人有白人、黑人;狗有金毛、博美等等,虽然特征差异相当明显,但我们仍然把两种人都称为“人”,两种狗都称为“狗”,这就是抽象。 事物的分类是根据对对象主要特征的抽象得出的,同一类事物属性和方法的个数和类型相同,例如人的身高、体重、肤色等,但值可以不同。 经过抽象形成类之后,对象便是类的一个实例。 面向对象设计有两项主要任务,一是对对象特征进行抽象而形成类,二是声明和创建类的实例——对象,基于它调用类的方法、使用类的属性进行计算。 当一系列相同和不同类的对象通过方法的调用产生有机组合和相互作用时,一个软件系统就形成了。 在两项任务中,类的设计是第一位的,它不仅决定了对象所包含的信息是否完整,也决定了对象产生的行为是否正确。 设计一个类,一是要找出代表对象特征的属性(数据),二是要设计出服务于这些属性的方法(函数)。 本节和下节主要讨论前者,后者留到函数一节专门讨论。 由上述讨论得知,类是由代表对象性质的属性和代表对象行为的方法构成。 属性和方法又是由“数据成员”和“成员函数”构成。 数据成员是变量、对象等实体或者对它们的引用;成员函数定义了一系列的计算功能,包括数据的入口和出口,例如: refclassHuman{//定义一个“人”类 private: String^m_name,^m_sex;//私有属性: 姓名,性别 intm_height,m_weight;//私有属性: 身高,体重 public: Human(String^name,String^sex,intheight,intweight);//构造函数(入口) String^getName();//出口 String^getSex();//出口 intgetHeight();//出口 intgetWeight();//出口 String^sayHello();//其它函数: sayhello doubleCalcBMI();//其它函数: 计算体重指数 }; 鉴于上述类和对象的基本概念,设计一个类应当遵循以下原则: ①类应当以其代表的对象来命名,如Human,而不应以某种功能来命名,如SayHello。 ②类应当是以数据为中心,而不是以功能为中心设计的。 ③方法应首先为本类的属性服务,故成员函数应围绕着数据成员来设计。 如吃饭是为自身的生存服务,故入口应当是为本类的数据成员赋值;CalcBMI计算基于的数据也应来自数据成员。 当然,函数也可以用于处理外部数据,但不应是主要功能,这点与C语言的思路完全不同,后者主要从外部获得数据。 在讨论了类和对象的概念之后,我们再将它们与数据类型和数据对象做个比较。 从概念上讲,类就相当于数据类型,对象就相当于数据对象,因为和类一样,数据类型是对一类(如整型)数据的抽象,它定义了一定长度和编码的数据存储空间,以及作用在该空间数据上的一组运算方法。 用某数据类型声明的一个数据对象(有名常量或变量)是该数据类型的一个实例,它占用数据类型所定义长度的存储空间,以数据类型所定义的编码方式编码,可以用数据类型所定义的运算规则进行运算,这相当于类的一个对象。 举例说,数据类型int相当于一个整型类,它定义了四字节的存储空间,数据在空间中是以整型(原码和补码)方式编码,这些相当于类的属性。 在int上定义了+、-、*、/、%五种整数运算,其中整除将商截尾,即3/4的结果为0。 同样是+、-、*、/运算,定义在数据类型double上其计算方法却完全不同,从而有3.0/4.0的结果为0.75。 因此,相同的方法作用在不同类的对象上,其行为有可能不同,这取决于类的属性。 从上述讨论可见,只要确定了属性和方法,我们同样可以定义自己的数据类型,既可以用类(class)来定义,也可以用结构体(struct)来定义。 在C中,结构体内不能定义方法,而在C++中是允许的。 例如复数类: refstructComplex{//与class不同,struct成员的访问权限默认为public doublem_real,m_image;//属性: 实部和虚部 Complex(doublereal,doubleimage);//构造函数(入口) Complex(Complex%y);//复制构造函数 doublegetReal();//出口,得到实部 doublegetImage();//出口,得到虚部 Complexoperator+(Complexy);//定义复数加法运算 }; ⑶类的定义 采用以下语法,可以定义一个C++/CLI的“托管引用类”: refclass类名//类头 {//类体: private: 私有数据成员声明、私有成员函数声明或定义 public: 公有数据成员声明、公有成员函数声明或定义 };//注意分号不可以少 refclass是C++/CLI新增的带空格关键字,用它定义的类为托管类(managedclass),用托管类声明的对象为托管对象,它将创建在托管内存中,由.NETFramework进行管理。 private: 和public: 为访问权限修饰符,关于访问权限见后述。 “数据成员声明”为类的属性声明,属性可以是某个类型的变量或某个类的对象。 “成员函数声明或定义”可以是函数原型声明,也可以是函数的完整定义(见后)。 以下代码定义了一个矩形类: refclassRectangle{ private: //表示其下类成员的访问权限为私有 doublem_width;//数据成员 doublem_height;//数据成员 public: //表示其下类成员的访问权限为公有 Rectangle(doublewidth,doubleheight){//构造函数 m_width=width; m_height=height; } voidsetRectangle(doublewidth,doubleheight){//入口函数 m_width=width; m_height=height; } doublegetWidth(){returnm_width;}//出口函数 doublegetHeight(){returnm_height;}//出口函数 doublegetArea(){returnm_width*m_height;}//计算函数 }; 数据成员可以是某类型的有名常量、变量或某个类的对象,也可以是某类型或某个类的句柄(C++/CLI)或指针(ANSI/ISOC++)。 例如: constdoublePI;//用const将PI声明为有名常量 doublem_radius;//m_radius为double型变量 DateTimem_dt;//m_dt为DateTime类对象 String^m_name;//m_name为String类句柄 int*m_p;//m_p声明为int型指针 类定义中的成员函数可以是其完整定义,即函数头(heading)+函数体(body)。 例如: doublecalcBMI(){//functionheading returnm_weight*703/(m_height*m_height);//functionbody } 也可以仅仅是函数头后加一个分号构成的“函数原型”(prototype)。 在后一种情况下,这个函数必须有“外联定义”。 为了标明它是某个类的成员函数,外联定义时必须在函数名前加限定符“类名: : ”,例如: doubleHuman: : CalcBMI(){returnm_weight*703/(m_height*m_height);} 类定义中的修饰符private: 和public: 标明其后所有数据成员以及成员函数的访问权限,直到遇到另一个修饰符。 除此之外,还有另外一个访问权限修饰符protected。 这些访问权限修饰符的意义如下: private: 私有成员,只能被本类的成员函数所访问。 public: 公有成员,允许被所有类的成员函数访问。 protected: 受保护成员,允许被本类及其派生类(第5章中讨论)的成员函数所访问。 关于类成员的访问将在本章第4节中讨论。 ⑷封装 封装(encapsulation)是面向对象方法的一个重要概念,也是面向对象程序的一个突出特点。 所谓封装,就是通过声明访问权限为私有,将类的某些属性隐藏起来,对外只暴露类的公有方法,使外界只能通过类所提供的接口函数来访问对象的数据,从而达到了保护重要数据的目的。 就像人通过皮肤将内脏封装起来,而只通过嘴来摄取食物、通过肛门排出粪便一样。 ⑸函数的概念 无论是面向过程的C语言,还是面向对象的C++,程序的所有运算过程都是定义在函数中的。 运算需要数据(就像工厂开工需要原料一样),问题是数据来自哪里? 在C语言中,数据是在函数被调用时,通过参数表(或全局变量)从“主调函数”传入的,运算结束后,除了向主调函数传回结果外,它与数据之间不会再有任何联系。 故在C程序中,数据与函数是分离的。 函数在程序中是作为被服务的角色而存在。 C++中,成员函数是与数据成员一道定义在类中,主要是为数据成员的处理服务(就像人吃饭是为自身的生存服务一样)。 外部数据通过入口成员函数进入对象的数据成员,后者在对象内部接受成员函数的处理(成员函数访问同类数据成员不受访问权限的限制),结果从函数名返回主调函数,或通过出口成员函数送到外部。 因此C++的函数虽然也是完成计算功能,但处理的数据主要来自对象内部,这导致了与外界交换数据的接口可能变得十分简单。 故在C++程序中,数据与函数是一体的,函数是作为服务于数据的角色而存在。 由于C++兼容C,因此在C++中也允许定义像C那样的非成员函数。 在这种情况下,函数的作用相当于一个“来料加工厂”,它从参数表取得数据,在函数内部实现数据处理,处理结果从函数名或通过引用参数返回主调函数。 虽然C++允许面向对象程序兼容面向过程方法,但不主张在C++程序中过多地使用函数模块。 纯粹的面向对象语言Java和C#都已删除了对面向过程特征的兼容性,即不允许脱离类单独地定义和使用函数。 //例3.2定义一个时间类,包括时间更新函数 #include"stdafx.h" usingnamespaceSystem; refclassMyTime{ private: DateTimem_dt; public: MyTime(DateTimedt){m_dt=dt;} DateTimegetTime(){returnm_dt;} boolUpdate()//时间更新函数 { DateTimedt=DateTime: : Now; if(dt.Second! =m_dt.Second){ m_dt=dt; returntrue; } returnfalse; } }; //独立的主函数,应用时间类实时显示时间 intmain(array : String^>^args) { DateTimedt; MyTime^time=gcnewMyTime(dt.Now); inti=0; while(i<60) { if(time->Update()) { Console: : WriteLine(time->getTime()); i++; } } return0; } 该程序的运行结果是: 每秒显示一次时间,一分钟后退出。 ⑹函数的定义 ①一般语法 定义一个函数的一般语法是: 返值类型函数名(形参表){函数体} 其中“返值类型”是函数返回值(即return之后表达式)的数据类型(若return之后为对象,则为对应的类)或者void(无返值情形);“函数名”为一个标识符,若为外联成员函数,前面还需加类名限定符“类名: : ”;“形参表”为由逗号隔开的变量或对象声明(或空);“函数体”为定义的计算过程。 ②构造函数 一种特殊的成员函数称为“构造函数”,它的函数名就是类名,没有返值,必须为公有。 构造函数在创建该类的一个对象时被自动调用,其作用,一是为该类的对象分配内存(幕后进行),二是作为入口函数,为对象的数据成员赋初值(代码实现)。 例如: public: Rectangle(doublewidth,doubleheight){//Rectangle类的构造函数 m_width=width;//用形参为数据成员赋初值 m_height=height;//用形参为数据成员赋初值 } 若类中未定义构造函数,则编译系统将会自动产生一个无参数的构造函数。 ③析构函数 第二种特殊的成员函数称为“析构函数”,它的函数名是“~类名”,无形参,无返值,必须为公有。 构造函数在销毁该类的一个对象时被自动调用,其作用是回收系统为对象分配的内存空间(幕后进行),并可由程序员完成销毁前的清理工作。 例如: public: ~Rectangle(){/*可加入清理资源代码*/}//Rectangle类的析构函数 若类中未定义析构函数,编译系统将会自动产生一个默认的析构函数。 ④拷贝构造函数 第三种特殊的成员函数称为“拷贝构造函数”,它的函数名是类名,形参为对本类对象的“跟踪引用”,函数体内将形参的数据成员逐个赋值给本类的数据成员。 例如: public: Rectangle(Rectangle%r){//Rectangle类的拷贝构造函数 m_width=r.m_width;//用形参引用的数据成员为本类数据成员赋初值 m_height=r.m_height;//用形参引用的数据成员为本类数据成员赋初值 } 拷贝构造函数在生成本类对象的一份拷贝(同值的另一个对象)时被自动调用,例如: Rectanglerect(3,4);//创建Rectangle的一个对象rect Rectanglerect1=rect;//生成rect的一份拷贝(调用拷贝构造函数) ⑤入口函数 入口函数是普通的成员函数,但它的作用仅仅是为数据成员赋值(与构造函数的区别是后者为一次性赋初值),一般以set开头,例如: voidsetRectangle(doublewidth,doubleheight){//入口函数 m_width=width;//用形参为数据成员赋值 m_height=height;//用形参为数据成员赋值 } ⑥出口函数 出口函数是普通的成员函数,但它的作用仅仅是返回数据成员的值,一般以get开头,例如: doublegetWidth(){returnm_width;}//返回m_width的值 doublegetHeight(){returnm_height;}//返回m_height的值 ⑦属性函数 属性函数是C++/CLI为简化数据的出入而新增加的功能,必须为公有,其语法为: property属性类型属性函数名{ 属性类型get(){return数据成员;} voidset(属性类型形参){数据成员=形参;} } 例如: propertydoubleWidth{//属性函数Width double
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 实验6 类和对象练习 实验 对象 练习