P05面向对象设计思想_第1页
P05面向对象设计思想_第2页
P05面向对象设计思想_第3页
P05面向对象设计思想_第4页
P05面向对象设计思想_第5页
已阅读5页,还剩78页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

面向对象程序设计江汉大学数学与计算机科学学院韩海程序设计思想1主要的程序设计理念运算符重载——针对对象实现类似于原运算符的功能继承与组合——如何利用已有的类建立更复杂的对象虚函数——指针指向类族中的哪个对象,就调用该对象相应的方法成员抽象类——类中只规定有哪些行为,但并不具体实现多态——让同一种行为应用于各种对象、各种情况模板——提高编码的效率异常处理——设置错误处理代码,应对可能出现的错误const——保护数据,防止被意外破坏流——把不同类型的信息视为一个数据序列泛型——忽略数据元素在存储上的差异,强调行为上的共性,强化代码的通用性2类是规定行为的集合 基本类型 自定义类集合示例 int

CMyEmployee元素示例 15 2010053134,张三,男,1982.3.5.

运算 算术运算 规定的行为(方法)

类是基本数据类型的扩展,两者的核心都是集合及集合上的运算,基本类型的运算表现为“+”、“-”、“*”、“/”等算术运算,是固有运算,可直接使用。定义类中包含哪些数据元素相对简单。类的核心问题是定义类的方法成员,通过方法成员表现出类的元素(对象)具有哪些行为。3对象的属性与操作对象的行为通常可以分为计算类和非计算类。非计算类包括显示、存储、传输等行为,比如关于图形处理建立的区域类族大体上会有三角形、方形、多边形等自定义类,各个对象都有在屏幕上画出该形状这一行为,而且通常都用同一个成员名来描述这个行为,如“对象.Draw()”。这一现象将在下一节“抽象类”当中讨论。

计算类行为是指获取对象的相关信息,或者通过对象及其它数据计算得到一些信息。比如上述关于区域的类族,各个对象通常都需要对外公布(允许外部访问,public)宽度和高度两个数据,这样的信息称为“对象的属性”,通常也有相对固定的成员名,例如取对象的宽度通常写作“对象.GetWidth()”。这一类行为当中包含运算符重载。4为什么要有运算符重载?起因(在上一单元已述,重现):

(1)常规的运算符只运用于基本数据类型,并且有固定的用法,基本上与数学上的用法一致。——好记

(2)类是数据与处理(称为“方法”)的结合体,有很多处理与常规的运算有直接联系,或者是常规运算在意思上的延伸,比如CString类的加法延伸为拼接。

——好理解

(3)设计一个用于实现加法的函数Add,带有两个入口参数分别记作a和b,则调用该函数写作“Add(a,b)”,这一写法当然不如“a+b”更简洁易懂。——好写目标:

希望为类设计一个与常规运算符在意义上接近的处理或者计算,并且沿用常规运算符原有的写法5运算符重载方式重载方式用成员函数重载用友元函数重载 特 点“=”等少量的几个运算符必须用成员函数的方式重载重载二元运算符时,右侧的操作数可以是各种类型,但左侧的操作数必须是类的对象成员函数的形参数目等于运算符操作数的数量减1(1)通常用全局函数实现(2)需要访问类的私有成员时才需要定义成友元函数6不能重载的运算符 以下运算符不能被重载:

成员访问

.

域限制符 ::

条件运算 ?: 取字节数 sizeof7经常被重载的运算符运算符 分类 重载方式+,-,*,/,% 算术运算 两种均可>,>=,<,<=,==,!= 关系运算 两种均可= 赋值 成员函数+=,-=,*=等 复合赋值 两种均可,建议成员函数++,-- 自增自减 两种均可,建议成员函数[],-> 下标,指向 成员函数<<,>> 移位/流 两种均可new与delete 内存管理 两种均可特别地,C++规定圆括号“()”也可以重载已述,本节将只讲解++、[]和()这几个运算符的重载8为CClock类定义前置“++”前述CClock类的声明如下:classCClock

{public:

CClock(int

h,int

m,ints);

~CClock();

int

GetHour(); //取小时数

int

GetMinit(); //取分钟数

CString

GetTime(); //取当前时间字符串

voidStepup(); //令时钟走一步(1秒)

CClockoperator++();private:

int

m_hour,m_minit,m_second;//时分秒};CClock类的对象是时钟,“++”的功能显而易见。用成员函数为CClock类定义前置“++”,首先需要在类中添加相应的函数声明。注意,类中已有Stepup函数可以利用9编写“operator++”利用已有的Stepup函数,前置自增功能很容易实现:CClock

CClock::operator++(){ Stepup(); return*this;}C++以“是否有形参”来区分前置自增和后置自增,后置自增的重载函数如下:CClock

CClock::operator++(int){ CClockx(*this); //创建临时对象

Stepup(); //即this->Stepup() returnx;}注意:(1)后置自增函数带有一个int型形参,这是区分前置后置的标记,并无其它含义,函数体内也不使用该参数值,甚至可以没有形参的名字;(2)不要忘记在类中添加相应的函数声明10友元函数定义前置“++”CClock类的声明如下:classCClock

{public:

CClock(int

h,int

m,ints);

~CClock();

int

GetHour(); //取小时数

int

GetMinit(); //取分钟数

CString

GetTime(); //取当前时间字符串

voidStepup(); //令时钟走一步(1秒)

friendCClock

operator++(CClock&x);private:

int

m_hour,m_minit,m_second;//时分秒};CClock

operator++(CClock

&x){

c.Stepup(); returnx;}11友元函数定义前置“++”这是前置自增的友元函数声明:

friendCClock

operator++(CClock

&x);后置自增的友元函数声明为:

friendCClock

operator++(CClock

&x,int);相应的函数代码:CClock

operator++(CClock

&x,int){

CClock

m(x);//记得吗,这将调用拷贝构造函数

c.Stepup(); returnm;}最初编写代码的时候忘记了引用,没有引用是不行的12为CClock定义下标[]设x是CClock类的对象,即一个时钟,原本x[?]是没有意义的,但不妨人为地做如下定义:

x[0]---时钟的当前小时数

x[1]---时钟的当前分钟数

x[2]---时钟的当前秒数

x[i]----1,i不是0、1、2时根据运算符重载的有关规则,下标运算“[]”必须用成员函数实现,则在写法上“x[i]”是函数调用“x.operator[](i)”的变形,“k=x[i]”也可以写成:

k=x.operator[](i)13为CClock类定义“[]”在CClock类中添加关于[]的成员函数如下:classCClock{public:

CClock(int

h,int

m,ints);

~CClock();

int

GetHour(); //取小时数

int

GetMinit(); //取分钟数

CString

GetTime(); //取当前时间字符串

voidStepup(); //令时钟走一步(1秒)

CClockoperator[]();private:

int

m_hour,m_minit,m_second;//时分秒};14CClock类的“operator[]”int

CClock::operator[](inti){

if(i==0) returnm_hour;

if(i==1) returnm_minit;

if(i==2) returnm_second; return-1;};15重载“[]”的意义设x是一个对象,通过运算符重载,可以把原本没有意义的写法“x[?]”赋予确定的含义,这与前述的运算符重载有着重大差异。

既然如此,对于任意一个允许重载的运算符,也可以赋予它与原运算符完全没有关联的功能。但是极少有人这样用,因为运算符重载的目的在于借用运算符原有的含义、用原有的写法实现对象的某些处理功能,如果这些功能与被重载的运算符相去甚远,则重载的效果只会让人造成概念上的混乱,不利于软件开发。正如在定义一个函数时,通常都以与函数功能相关的英文单词或者编写作为函数名。16CClock类的“operator()”对于对象x,运算符重赋予了“x[?]”一定的意义,对于同样原本没有意义的“x(?)”也可以依照此例处理。实际上,把对“[]”重载时所有的“[”换成“(”、所有的“]”换成“)”,前述代码同样可以编译通过。很多资料上把“operator()”称为函数调用运算符,这容易在概念上造成混乱。建议:不用管它叫什么名字,因为对于对象x而言,“x(?)”原本没有意义,现在通过运算符重载规定了该写法的含义。17继承还是组合面向对象程序设计的基本设计单位是“类”,类的本质是规定了对象的数据信息和行为。设计更复杂的程序时,可以利用已有的类,在利用方法上就有了本页的标题:继承还是组合?——本节只考虑public继承设X和Y是两个类,x是X的对象,y是Y的对象继承(“isa”关系)——如果X是Y的派生类,则:

xisay. (见下页图元类族示例)组合(“hasa”关系)——如果y是x的一个子对象,则:

xhasay.例如:CMyEmployee类中包含两个CString子对象,

Theemployeehasanumber. Theemployeehasaname.18继承描述“isa”关系Point(点)isaelement(图元)Rigion(区域)isaelement(图元)Circle(方形)isaRigion(区域)...... 图元颜色,尺寸,边界画图,擦除,取边界点线起点,终点,线型区域填充模式,透明度直线弧线......曲线......方形......圆形......多边形......注意,不论是继承还是组合,在x中都包含一个y的对象作为x的一部分!继承与组合到底有什么差别?19继承与组合的差异xisay(继承)(1)编写X类的成员函数代码时,可以访问y的public成员和protected成员,包括方法成员也包括数据成员(2)在类的外部编写代码时,可以访问x的public成员,也能访问y的public成员(3)CX的构造函数以“:CY(...)”指明如何调用基类的构造函数xhasay(组合)(1)编写X类的成员函数代码时,只能访问y的public成员,包括方法成员和数据成员(2)在类的外部编写代码时,只能访问x的public成员(3)CX的构造函数以“:子对象名(...)”指明如何调用子对象的构造函数20测试1,派生类内部CY类的声明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}CX类的声明:classCX:publicCY//继承{public:f_x1();

intd_x1;protected:f_x2();

intd_x2;private:f_x3();

intd_x3;}编写CX的函数代码时,允许访问CX和CY的哪些成员?21测试2,派生类外部CY类的声明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}CX类的声明:classCX:publicCY//继承{public:f_x1();

intd_x1;protected:f_x2();

intd_x2;private:f_x3();

intd_x3;}x是CX的对象,“x.?”是合法的访问?22测试3,组合类内部CY类的声明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}编写CX的函数代码时,允许访问CX和CY的哪些成员?CX类的声明:classCX //组合{public:f_x1();CYy1;protected:f_x2();CYy2;private:f_x3();CYy3;}访问CY的成员时,必须指明访问哪一个子对象的成员,比如“y1.f1()”、“y2.f1()”、“y3.d1=5”23测试4,组合类外部CY类的声明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}CX类的声明:classCX //组合{public:f_x1();CYy1;protected:f_x2();CYy2;private:f_x3();CYy3;}x是CX的对象,“x.?”是合法的访问?x.f_x1() x.y1.f1() x.y1.d1=5;24多用组合少用继承如本页标题所示,在建立新的类时,多用组合少用继承。并且把子对象置于private保护之下。原因:面向对象的核心思想之一是封装,即允许外部访问对象的哪些成员。以继承的方式建立新的类,编写代码的人往往容易忽略可以从外部访问基类的public成员,从而导致一些预料之外的信息暴露。当然,最根本的还是根据“isa”还是“hasa”关系来选用继承和组合。25继承导致类族转换话题:面向对象的程序设计模式中经常会设计一系列有继承与派生关系的类,从一个基类开始往下派生出的所有的类形成一个“类族”。类族的设计思想显然是为了代码重用。在一个类族中,对象尽管属于不同的类,但通常都有一些相同的特征或者行为。即:类族中任意一个类的对象都拥有基类的数据成员(虽然有可能因为继承方式而不能访问),视作具有相同的特征信息;类族中的对象通常也具有一些同名的函数成员,视作具有相同的行为,这些函数的功能相同或相似,但实现的具体代码可以不同。26类族对象的共性例如,以“图元”为基类的类族具有颜色、尺寸等共同的数据成员,以及画、擦等同名的方法成员;除此之外,以“区域”为基类的类族还具有填充模式、透明度这两个共同的数据成员。图元颜色,尺寸,边界画图,擦除,取边界点线起点,终点,线型区域填充模式,透明度直线弧线......曲线......方形......圆形......多边形......27对图元类族确定名称为了后续叙述的方便,为各个类及各个成员命名CElementcolor,size,borderDraw,Erase,...CPointCSegmentstart,end,styleCAreapattern,transparencyCLineCArc......CCurve......CSquare......CCircle......CPolygon......28创建对象与调用方法设有如下的创建对象:

CElementx1; //图元

CArea x2; //区域

CSegmentx3; //线

CCirclex4; //圆则,以下调用非常明确是调用哪个对象的哪个方法:

x1.Draw(); x2.Draw(); x3.Draw(); x4.Draw();原本没有疑问的用法会因为指针而产生歧义,见下页。29重要规则C++规定:

派生类的地址可以赋值给指向基类的指针变量你如何理解这个规则?请先看下面的例子为了说明问题的简便,改用下面的最简单的类声明:classCA{public: voidfun() {cout<<"FunofCA\n";};};classCB:publicCA{public: voidfun() {cout<<"FunofCB\n";}; voidfb(){cout<<"Thisisfb\n";};};记住各函数的显示效果!30类族中的指针指向指出下面代码中的错误:main(){ CAx,*p; CBy,*q; p=&y; q=&x; p->fun(); p->fb(); q->fun();}派生类地址可以赋值给指向基类的指针变量,反之不行这一行是对的,不用写“p=(CA*)&y;”p的类型决定了通过p只能访问CA的成员,fb是CB的成员但不是CA的成员31指针指向类族中的对象指出下面代码中的运行结果:main(){ CAx,*q; CBy;

x.fun(); q=&x; q->fun();

y.fun(); q=&y; q->fun();}最后一行显示值得探讨:q是指向基类CA的指针变量,但此时指向派生类对象y,“q->fun()”到底调用哪个函数?另外,编程者希望它调用哪个函数?32再谈规则上述规则的意义:(1)因为代码重用而产生了类族,类族中的对象都有相同或者相似的行为。在编程者看来,类族中的对象都是“差不多”的。(2)编程者希望借用一个指针变量,不论该指针指向类族中的哪一个对象,都能正确地访问相应的成员。如果派生类对基类的某个方法成员编写了新代码(覆盖),则希望指针指向派生类对象时,能够访问新代码。(3)以“基类*q”定义指针变量,则通过q只能访问基类的成员,而不能访问派生类新增的成员,从而保证不会出现访问一个不存在的成员的现象。派生类地址可以赋值给指向基类的指针变量,反之不行前述示例说明没能做到这一点33虚函数虚函数专门用于解决上述问题:如前例,基类中定义了方法成员fun,派生类更新了该方法。以“CA*q;”定义指针变量,希望当q指向基类CA的对象时,“q->fun()”访问基类CA的成员函数fun;当q指向派生类CB的对象时,“q->fun()”访问派生类CB更新之后的成员函数fun。简言之:希望q指向谁就调用谁的成员函数对于上述需求,需要在基类中把该方法成员定义成“虚函数”。声明虚函数的格式如下:

virtual

返回值类型函数名(形参表);34声明虚函数下面是定义CA和CB两个类,在适当的位置加上虚函数标记virtual:classCA{public: voidfun() {cout<<"FunofCA\n";};};classCB:publicCA{public: voidfun() {cout<<"FunofCB\n";}; voidfb(){cout<<"Thisisfb\n";};};virtual35有关虚函数的说明类的静态成员函数和内联函数不能声明为虚函数定义成员函数为虚函数,并不代表该函数是“虚”的,而是为了通过指针变量能够访问正确的方法成员,通过对象名访问成员与虚函数无关一旦在一个类中定义了虚函数,则以该类为起点的类族中该函数都是虚函数,派生类中的相应函数不再需要用virtual说明构造函数不能声明为虚函数,析构函数往往声明为虚函数基类中声明虚函数时必须明确形参,派生类中相应函数不仅要同名,也要求形参相同36测试classCX{public:virtualint

fa();

int

fb();}classCY:publicCX{public:virtualint

fb();}classCZ:publicCY{public:virtualint

fa(intn);

int

fb(intn);}classCW:publicCZ{public:virtualint

fa();

int

fb();}说明各个类的对象能访问哪些函数成员?哪些类的哪些函数构成一组虚函数画出层次结构图37虚函数的实现原理面向对象程序设计允许函数重载(overload)与函数覆盖(override),前者导致函数同名但形参不同,后者则是同名同形参。前面已有若干示例说明后者的有关规则,核心问题是当代码中出现一个函数调用时,究竟是调用哪一段具体的函数代码。解决问题的方法是对虚函数采用动态联编方式。动态联编——也称动态绑定,是与静态联编相对而言的。当类族中含有虚函数时,为类族的每个对象安排虚函数表(是各个虚函数入口地址的列表,与数据成员安排在一起)。对于通过指向基类的指针访问函数成员的情况,编译时处理成“从虚函数表中找函数的入口地址”。38设计图元类族的方法成员Q:请设计CElement类的Draw方法的功能及代码CElementcolor,size,borderDraw,Erase,...CPointCSegmentstart,end,styleCAreapattern,transparencyCLineCArc......CCurve......CSquare......CCircle......CPolygon......Draw的功能好说,就是在指定位置画出这个图元。代码没法写,因为在CElement类中并不知道图元的具体情况。如果真的要写代码,只能是空代码,什么也不做!39纯虚函数与抽象类采用继承与派生的层次式设计构建一个类族时,越上层的类就越抽象,越下层的类就越具体。有时上层的方法根本无法明确行为,比如“图元”的Draw

在C++中,允许一个类只规定方法成员的首部而不编写函数体,这样的方法称为“纯虚函数”,含有纯虚函数的类称为“抽象类”。纯虚函数是在类声明中用如下形式定义函数成员:

virtual函数值类型函数名(形参表)=0;注意,上述格式中的“=0”表示该函数是纯虚函数。虽然在语法上virtual不是必须的,但一般只有类族中基类的虚函数才设计成不编写函数体。40抽象类的作用抽象类中至少含有一个纯虚函数,试图调用纯虚函数将导致没有相应的代码可以执行,所以,不允许创建抽象类的对象!抽象类的作用表现在规定由此向下的一个类族(或者类族分支)中有哪些共同的方法成员,并统一这些方法成员的函数名称及参数形式。比如,关于“图元”的类族中,“图元”定义方法成员“Draw”,“线”定义方法成员“GetLength”,区域定义方法成员“GetArea”,这些方法都无法编写代码,但在相应的类族中有相同的名称,并且有相同的形参列表。41什么是多态多态(Polymorphism)——简言之,“一个接口,多种实现”接口,是指函数,包括普通函数和成员函数一个接口,是指同名函数,显然不是一个函数而是一组函数多种实现,是指同一个函数名之下有多种不同的代码以应对不同的参数、对象、环境等因素接口,泛指实体与外界联系的方式、通道。对象与外界联系的方式可以有数据成员和函数成员,但“封装”导致对于多数类而言,外界不能直接访问对象的数据成员,只能通过类所提供的方法成员获取信息,或者令对象产生相应的行为。所以,面向对象中的“接口”通常指成员函数。42多态的种类有资料把多态划分为通用多态和特定多态,前者包括参数多态和包含多态,后者包括重载多态和强制多态,但这样的划分值得商榷,原因见下页1.参数多态——由函数模板产生的模板函数(同名函数处理不同类型的数据),由类模板产生的模板类,除了数据类型不同,其它部分相同2.包含多态——类族中同名成员函数,在指针指向某对象时,动态绑定相应的函数成员3.重载多态——函数重载、运算符重载,以同名函数处理不同数量、不同类型的数据4.强制多态——某些运算符具有自动数据类型转换功能,如“int+double”时,先把int转换成double,再进行加法运算多态的四种形式43归并一下多态的种类多态的作用在于以相同或者相似的形式处理不同类型、不同数量的数据,或者处理不同类的对象。分析前述划分,可以再归并一下,得到三个种类:1.函数重载(overload),以同一范围内的一组同名函数应对不同数量、类型的数据2.函数覆盖(override),以类族中的同名函数应对类族中的各个对象3.自动转换(含运算符重载),基本运算符应对不同类型的数据/对象只有类模板不在此列。先看看什么是类模板,什么是模板类,似曾相识的概念。44函数模板:由template开始编写的一段函数定义,表示一组函数,除了类型标记不同、其它部分都相同模板函数:由函数模板生成的函数函数模板并不是函数定义,为什么可以直接调用? 因为由函数模板可以生成相应的函数定义由谁来生成函数定义代码?在什么时候生成? 编译器在首次遇到对模板函数调用的时候生成生成函数代码时其中未定的类型怎么处理? 根据调用时参数的类型将相应的类型标识符代替模板中的类型标记复习:函数模板与模板函数函数模板:模板函数:函数模板并不是函数定义,为什么可以直接调用?由谁来生成函数定义代码?在什么时候生成?生成函数代码时其中未定的类型怎么处理?45类模板:由template开始编写的一段类声明(含其中的成员函数代码),其中除了含有特定的类型标记,表示一组类声明,除了类型标记不同、其它部分都相同模板类:由类模板生成的类类模板并不是类,为什么可以直接定义它的对象? 因为由类模板可以生成相应的类定义,即类声明由谁来生成类声明的代码?在什么时候生成? 编译器在首次遇到使用模板类的时候生成生成类声明的代码时其中未定的类型怎么处理? 使用模板类时需要指明类模板中的类型标记用什么具体的类型标识符代替类模板与模板类46一个简单的类模板先来个最简单的。如果想根据下面的类来定义一个类模板,让其中的int可以是各种数据类型:

classCA { public: CA(int

n){mm=n;}; voidf(){cout<<mm<<endl;};

intmm; };template<typename

T>TTCA<T>如果想在类声明之后再写函数体,怎么办?47template<typenameT>classCA{public: CA<T>(Tn){mm=n;}; voidf(){cout<<mm<<endl;}; Tmm;};类模板后面写成员函数代码template<typenameT>voidCA<T>::f(){cout<<mm<<endl;}不就是把这个函数体移到后面去吗?;48template<typenameT>classCA{public: CA<T>(Tn){mm=n;}; voidf(); Tmm; //演示!}; template<typenameS>voidCA<S>::f(){ cout<<mm<<endl;}voidmain(){ CA<int>x(3);

x.f(); CA<double>y(4.567);

y.f();}看一看效果49含双参数的类模板如果类模板中需要两个不同类型的参数,怎么办?classCA{public:CA(inta,doubleb){mm=a;nn=b;};voidf(){cout<<mm<<','<<nn<<endl;};

intmm;

double

nn;};template<typename

S,typename

T>SSTTCA<S,T>把这个函数体移出去也不难;template<typename

S,typenameT>voidCA<S,T>::f(){cout<<mm<<','<<nn<<endl;}50template<typename

S,typename

T>classCA{public: CA<S,T>(Sa,T

b){mm=a;nn=b;}; voidf();private: Smm; Tnn; };template<typename

X,typename

Y>voidCA<X,Y>::f(){ cout<<mm<<'('<<sizeof(mm)<<")\n";

cout<<nn<<'('<<sizeof(nn)<<")\n";}voidmain(){ CA<char,int>t1('1',2); CA<double,int>t2(3.45,6); CA<int,double>t3(7,8.9); t1.f(); t2.f(); t3.f();}再看看效果51类模板只声明了生成若干个类的可能性,只有在编译器遇到对类的实际使用时(比如定义类的对象),才会生成相应的类——称为类模板的实例化类模板实例化时必须显式指明模板所含参数的类型类模板中除了在class后面首次出现的类名之外,其它用到类名的时候都要写“类名<参数表>”的完整写法,但构造函数、析构函数则可以省略为只用“类名”在类声明之后编写成员函数代码时,需要重新写template及参数,参数的数量必须相同,参数名称可变,而且类限制符“::”前面的类名必须用完整写法类模板规则要点52针对下面的要求设计类模板CArray:能够存放一批数据成员函数GetData能够根据输入情况确定存放多少个数据,并从键盘上读取这一批数据成员函数Display能够显示当前存放的数据成员函数Sort能够对当前存放的数据排序(升序)为了测试,安排主函数如下,并要求替换其中的double为int、float、char等常用类型多次测试

voidmain() {CArray<double>x;

x.GetData();

x.Display();

x.Sort();

x.Display(); }复杂一点的类模板53template<typenameT>classCArray{public:

CArray(){m_arr=NULL;m_count=0;};

~CArray(){if(m_arr)delete[]m_arr;};

int

GetData(); voidDisplay(); voidSort();private: T*m_arr; //根据需求申请存储空间

int

m_count;};定义类模板54template<typenameT>int

CArray<T>::GetData(){ int

i,n;

cout<<"Howmanynumbers:";

cin>>n;

if(n<=0) return0;//没有数据需要存储

if(m_arr) delete[]m_arr;//删除原数据

m_arr=newT[n];

if(!m_arr) return-1;//申请内存失败

m_count=n;

for(i=0;i<n;i++) { cout<<"X["<<i<<"]=";//操作提示

cin>>m_arr[i]; } returnn;}编写GetData55template<typenameT>voidCArray<T>::Display(){ inti;

if(m_count<=0)

cout<<"Arrayisempty.\n"; else {cout<<m_count<<"numbersinArray.\n";

for(i=0;i<m_count;i++) { cout.width(8);

cout<<m_arr[i]; }

cout<<"\n\n"; }}编写Display56template<typenameT>voidCArray<T>::Sort(){ int

i,j; Tt;

for(i=1;i<m_count;i++)

for(j=0;j<m_count-i;j++)

if(m_arr[j]>m_arr[j+1]) { t=m_arr[j];

m_arr[j]=m_arr[j+1]; m_arr[j+1]=t; }}//终于编写完了,测试一下吧编写Sort57高手编的软件不会死下面是一个常见现象:通常,要么等待数秒之后软件被关闭,要么就这么一直等下去。如何看待这一现象?58软件中的BUG什么原因造成了软件崩溃?

(1)非法命令 (2)错误的语序例,指出以下代码中存在的问题或者错误:voidmain(){int

i,n;double*x;x=newdouble[n];

cin>>n;

for(i=n;i>0;i--){ x[i]=i/(i-1); out<<i<<','<<x[i]<<endl;}}有借无还对不存在的变量赋值除以0次序错误还有两个可能的问题:输入到n的值不合适,比如负数;用new申请内存空间可能失败59错误种类这里的“非法命令”不是指语法错误(语法错误由编译器处理),而是指命令计算机执行一个不正确的操作,包括以下几种情况:非法访问内存。比如前例中对x[n]赋值非法操作。比如前例中可能存在的除以0非法访问硬件。连接到计算机的各种设备各有各的使用特点,有些只读,有些只写,有些要求先做A再做B,等等“非法语序”往往会造成变量中存储的数据不正确,从而使得后续语句达不到预期的执行效果60处理可能的错误代码中不允许出现语法错误和逻辑错误,比如前例中对x[n]赋值,这是编码、调试、测试阶段必须解决的对可能出现问题的命令要有应对措施,例如:

cin>>n;

if(n<MIN||n>MAX)cout<<"..."; else {x=newdouble[n];

if(x==NULL)cout<<"..."; else

for(i=n-1;i>=0;i--) {x[i]=...; out<<i<<','<<x[i]<<endl; } }61函数调用造成的困难func_A(){......}func_B(){......

func_A();......}func_C(){......

func_B();......}main(){......

func_C();......}一旦最底层函数调用时出了问题,比如问题在func_A中,通常需要以函数值的方式逐层“上报”,在某一层决定最终如何处理这个问题62异常处理机制出现问题逐级上报的处理方法代码较大,异常处理机制是解决该问题的另一种思路:不论哪一级出现问题,只需要“报告一下”(不一定是对直接上级),称为“抛出异常”。在某个函数中设置一段代码来统一处理这些报告。统一处理异常由try和一组catch构成,格式见下页;抛出异常则相对简单:

throw(参数);其中,表示异常情况的参数可以是任何类型的数据,也可以是某个类的对象,用途是向处理机制报告出现了什么样的异常。63处理异常的编写格式处理异常的代码部分由两段构成:try{可能抛出异常的代码}catch(形参A){处理A}catch(形参B){处理B}catch(形参C){处理C}......这一段可能有问题,没关系,做就是了,有问题就报告根据不同的错误报告采取相应的处理措施工作方式:在执行try后面的代码的过程中,只要有异常抛出,立即转到catch部分,根据抛出的异常的类型执行对应的处理代码,如果没有对应的代码则产生运行错误。处理结束后执行catch后面的语句64仍然可能造成软件崩溃voidfunca(){intn;

cout<<"InputinA:";

cin>>n;

if(n<0)throw("A");}voidfuncb(){doublex;

funca();

cout<<"InputinB:";

cin>>x;

if(x<0)throw(x);elseif(x==0)

throw("B");

funca();}voidmain(){try{ funcb(); }

catch(char*x){cout<<x<<endl;}

catch(intx){ cout<<"x="<<x<<endl;}

cout<<"==========\n";}看看以上代码在输入不同的数据时的运行结果65异常处理不是万能的异常处理机制仅仅是把编写代码时在各个函数中对出现的问题进行处理改为在一个函数中统一处理,如果代码中出现非法访问内存、除以0等现象仍将导致软件崩溃,另一种导致崩溃的原因是对抛出的异常没有安排相应的处理代码。66防止意外修改数据被意外修改是另一个安全隐患。以编写函数为例,为了减少参数传递的开销,有时以引用作为形参,但这样做的后果是函数内部对形参的修改将直接导致实际数据的改变。例如:doublefunc(double

&x){if(x>0) x=(int)(x*100+0.5)*0.01;else x=(int)(x*100-0.5)*0.01;returnx;}没有“&”是常规用法。如果用引用作为形参,在完成求值计算的同时会改变实际参数的值。这样的修改往往并非有意为之,而是一种疏忽。67const有关规则A用const可以限制数据被修改,前述“常变量”是const的最初应用。const的主要用法:(1)定义普通变量,限定不允许改变变量的值,即前述“常变量”:

const类型变量名=初值;或者 类型const变量名=初值;(2)定义普通对象,限定不允许访问对象的任何成员,不论是public、protected还是private:

const类对象名(初值);或者 类const对象名(初值);如此定义的对象又称“常对象”,用作初始化其它对象、给其它对象赋值等68const有关规则B(3)const用于定义指针变量时有所不同:① const类型*变量名=初值;不允许改变指针所指变量的值,如果指针指向对象,也不允许访问对象的任何成员,但可以令指针变量指向其它位置② 类型*const变量名=初值;不允许改变指针变量所指位置,但可以改变指针所指变量的值,如果指针指向对象,允许访问对象的public成员③ const

类型*const变量名=初值;两者都不允许69const有关规则C(4)const用于定义引用:

const类型&引用名=目标;不允许通过引用改变目标的值,如果是对象的引用,也不允许通过引用访问对象的任何成员(5)const用于函数形参这种用法除了是在参数传递时确定初值之外,与前述相应各种情况的规则对应相同70什么是“流”这里所说的“流”是以字节为单位进行串行数据传输的形式。计算机中还有一种是以“位”为单位的流,称“比特流”。对于一般的传输,总是需要有建立连接、传输、撤除连接三个步骤,这里的“流”加上一个特点:单向。对于面向对象的编程而言,最常见的“流”是文件和标准输入输出设备。71“流”在文件上的应用“文件”是存放在外存上的数据集合。最早的“文件”完全按照“流”的方式设计:(1)创建文件/打开文件。建立内存与文件的连接,并且要规定数据传输方向,因此有“为读打开”、“为写打开”等等。(2)读/写文件。在最早的文件系统中,读和写是分离的,“为读打开”的文件就只能读,“为写打开”的文件就只能写。以写为例,先在内存中把数据准备好,即内存缓冲区,然后把这些数据一字节一字节地送到文件中存放。(3)关闭文件。撤除第(1)步建立的连接。现在的文件系统除了允许同时为“读”和“写”建立一个连接之外,其它方面没有改变。72cin与cout

cin是标准输入设备流,一般直接对应键盘,流的方向是由键盘向内存;cout是标准输出设备流,一般直接对应显示器,流的方向是由内存向显示器。计算机开机后,由系统建立cin、cout和内存的连接,并且始终处于连接状态,使用完毕后不需要也不能关闭。73cin、cout与类型转换

cin和cout都按“文本形式”工作,并带有数据类型转换功能。以cout为例,cout<<x,不论变量x是int、char、double等哪一种类型,cout都能够把数据的内存形式转换成文本的ASCII形式,再逐个符号地传送到显示器上。

cin和cout具有可扩展性、对于类、结构体等不是基本数据类型的情况,可以通过对“<<”和“>>”进行运算符重载,使得cin、cout支持自定义类型。当然,类通常会设计输入、输出对应的方法成员完成相应的工作。74“在一批数据中找一个满足的”,这是非常常用的操作,可以设计出针对各种类型的数组进行查找的函数模板:

template<typenameT>

int

Find(Tx[],intn,Tkey) { inti;

for(

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论