C应用与开发案例教程(下)238_第1页
C应用与开发案例教程(下)238_第2页
C应用与开发案例教程(下)238_第3页
C应用与开发案例教程(下)238_第4页
C应用与开发案例教程(下)238_第5页
已阅读5页,还剩233页未读 继续免费阅读

下载本文档

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

文档简介

C++应用与开发案例教程(下)第8章异常处理有时候,你可能面临并非所有的事情都是完美的事实,偶而,甚至最好的方案也会失败。不管怎么说,是人就会犯错。为了防范运行时的错误,C++环境给予程序员试运行的时机,如果运行失败,它提供了一个预防机制。异常处理就是这种机制。异常是发生在运行期间的诸如零作分母、数组的溢出以及空闲空间的耗尽这样的错误事件。假设这些错误没有被捕获,那么程序会突然地停止执行,而没有提供任何指示给使用者,告诉他发生了什么。处理这样的异常,不同的程序员有不同的风格,这导致了代码的多样性。这种多样性随着用户自定义类的使用而增长,因为每一种这样的类都带有其潜在的特定类的异常结构。当一个异常发生时,C++以以下方式做出反响:1.发生异常的函数可以产生一个系统定义的消息;8.1概述8.1概述2.函数可以完全终止执行;3.函数可以跳过中间层继续执行其他的局部。C++语言提供内建的对于意外情况处理的支持,这就是异常处理。通过C++语言的异常处理,你的程序能够使意外的事件与高层次的执行上下文进行通信,它能够从这种异常事件中恢复过来。C++处理异常的机制是灵活的,因此它能处理任何类型的异常。在C++中,产生一个异常被称为引发一个异常。

8.2何时使用异常处理在程序运行期间调用一个函数会产生三种结果。它们是:1.正常执行在执行过程中,函数正常地执行并返回到调用的程序中.一些函数把结果代码返回给调用者。返回的代码说明执行的结果。2.错误执行当调用者在变量传递过程中犯错或调用的函数超出上下文的范围时,会导致一个错误,这会终止程序的执行。3.异常执行在执行过程中出现异常的一种典型情况是内存缺乏;另一种典型情况是一个I/O操作的异常。例如,当翻开一个存在的文件时,存储媒介的设备驱动器可能会出错。这种情况不能通过简单的查找文件是否存在来处理,引发并捕捉异常是最正确的处理方法。

8.2何时使用异常处理在开发需要照顾意外情形的应用程序时,异常处理是必需的。发生在程序执行时的意外情形有:运行内存不够、资源分配错误、不能找到/翻开文件等。假设这些错误没有被捕获,那么,在没有给予用户任何有关发生什么情况的提示下,程序将被中断。异常可以被定义为在程序执行中意外情况的出现和正常指令流的中断。8.3异常处理的根本语法异常处理是C++的一个特征,它提供了处理运行期间异常的标准工具。一种一致性语法可以应用于这种目的,它能够被程序员很好的协调校正。一些常见的异常有分母为零、数组溢出以及NULL指针〔即一个包含0值的指针〕的引用。在C++语言中用try、throw和catch语句实现异常处理,在程序的某个函数中发现异常时使用throw表达式抛出这个异常,这个异常被抛给该函数的调用者,调用者用catch捕获该异常并进行相应的处理。当预测到某段程序将可能出现问题时,那么该段程序放在try语句之后,使该段程序得以有时机抛出异常。8.3.1异常处理的语法异常处理的语法如下:throw表达式语法:throw表达式try表达式语法:try受保护的程序段语句catch〔异常类型〕异常处理语句;catch〔异常类型〕异常处理语句;…其中,catch括号中异常类型与throw抛出的异常类型相匹配,当某个异常发生后,catch语句将逐个被检查,直到某个异常类型与throw抛出的异常类型相匹配,然后执行catch后面的8.3.1异常处理的语法异常处理程序。假设catch括号中是省略号〔……〕,那么该catch可以捕获所有的异常类型,所以这样的语句一般放在所有catch语句的最后一个,否那么其他catch语句就没有时机捕捉相应的错误类型了。当某个异常被捕捉并被相应的程序段处理后,系统将继续执行捕捉函数的其余局部。产生异常的函数和直到捕捉到异常的函数之间的所有调用链上的函数将从栈中删除。如果一个异常没有被任何函数捕捉到,那么运行函数terminate将被调用,该函数的默认功能是调用abort函数终止程序的运行。一个异常处理的过程可以如下表示:voidfun(){inti;doublef;

8.3.1异常处理的语法charc;…//程序的其他局部try//预期可能出现问题的地方,保护起来{throwi;//如果出现问题1,抛出一个整数〔只是假设〕throwf;//如果出现问题2,抛出一个双精度数〔只是假设〕throwc;//如果出现问题3,抛出一个字符〔只是假设〕}catch(intii){//相应处理}catch〔doubleff〕{

8.3.1异常处理的语法//相应处理}catch〔charcc〕{//相应处理}…//程序其他局部}【例8-1】#include<iostream.h>voidmain(){try{

8.3.1异常处理的语法intage;cout<<〞Entertheage:";cin>>age;if(age>100||age<1)throw"Invalidage!";cout<<"Afterthethrowstatement"<<endl;}catch(char*msg){cout<<"Error!"<<msg<<endl;}cout<<"Afterthecatchhandler"<<endl;}程序的输出结果为:8.3.1异常处理的语法

Entertheage::46AfterthethrowstatementAfterthecatchhandleEntertheage::116Error!Invalidage!Afterthecatchhandle8.3.2异常的类型

异常有两种类型:同步异常和异步异常。1.同步异常发生在运行期间,并且能够使用throw表达式产生。仅有这种类型的异常能够使用C++中的异常处理器处理。控制不必返回异常引发处。2.异步异常因为一个键盘或鼠标的中断而产生。C++程序不能使用try和catch语句直接处理这种类型的异常。一次不得不产生一个自动调用与键盘或鼠标中断相关的例程。8.4Try、catch和throw语句在C++语言中执行异常处理的是Try、catch和throw语句。通过C++的异常处理,你的程序能够探测到异常事件并从中恢复过来。一个包含try、catch和throw语句的程序的执行过程如下:1.控制通过正常的连续执行到达try语句,在try模块内的语句被保护执行;2.如果在被保护的执行期间没有异常被引发,try模块之后的catch语句不会执行。在引发异常的try模块后面的最后的catch语句之后继续执行;3.如果在被保护的执行过程中有一个异常被引发,程序搜寻一个能够处理被引发类型的异常的catch语句;4.如果没有找到一个类型匹配的处理器,那么调用预定义的运行期间的函数terminate();8.4Try、catch和throw语句5.如果找到了类型匹配的catch处理器,初始化它的参数并执行catch处理器中的指令。如果程序终止,释放堆栈中的所有值。一个基类的处理器也能够处理它的子类的异常。如果在catch表达式中遇到了一个“…〞,它将处理所有的异常而不管它们的数据类型。【例8-2】#include<iostream.h>voidmain(){cout<<"beforethethrowstatement"<<endl;try{throw25.6f;//引发一个浮点值}8.4Try、catch和throw语句catch(char*str) { cout<<"Caughtastring"<<endl; } catch(inti) { cout<<"CaughtanInteger"<<endl; } catch(...) {cout<<"Unknowndatatype"<<endl; }cout<<"Afterthethrowstatement"<<endl;}8.4Try、catch和throw语句在上面的代码中,一个浮点值从try模块中被引发。然而,没有为接受一个浮点值而特意设计的catch处理器。因此程序的输出是:beforethethrowstatementUnkonwndatatypeAfterthethrowstatement新的运算符new引发了一个bad_alloc异常。新的头文件声明了bad_alloc类,它从异常类继承而来。What()函数在异常类中被声明。【例8-3】检查内存分配操作。#include<iostream.h>#include<new>voidmain()8.4Try、catch和throw语句{char*buff;try{buff=newchar[1000000000];//声明在新的头文件中的新的运算符bad_alloc引发了一个类的对象}catch(std::bad_alloc&obj){ cout<<":"<<obj.what()<<endl;}}该程序是关于一个try模块和与它相联系的catch处理器的8.4Try、catch和throw语句一个实例。该实例检查一个内存分配操作的错误,如果内存分配操作成功,那么catch处理器不执行。在一个内存分配错误的情况下,程序的输出是:Exceptionraised:bad_alloc如果在一个try模块执行过程中没有引发异常,在try模块后的catch语句不被执行。程序将在引发异常的try模块之后的最后一条catch语句继续执行。控制器只能够通过一个引发的异常来输入一个catch处理器,而不能通过一个switch语句中的一个case标号来进行。在前面提到的实例中,在try模块后仅存在一个catch处理器。然而,并不是一直这样的。也会存在其他的catch处理器。如果throw语句准备仅引发内建的数据类型〔在这种情况下使用字符型〕,catch处理器的数量不能超过语言支持8.4Try、catch和throw语句的内建数据类型的数量。为了解决这个问题,一个用户定义类型的不同的对象能够引发给处理器。如下例:【例8-4】#include<iostream.h>#include<string.h>classMyException{public:charcause[21];MyException(char*in=0){strcpy(cause,in);}8.4Try、catch和throw语句voidprintMessage(){cout<<endl<<"Error:"<<cause<<endl;}};classAclass{intage;public:voidget(){cout<<"Enteryourage:";cin>>age;if(age<1||age>100)8.4Try、catch和throw语句{MyExceptione("Problemwithage");throwe;}else{cout<<"CORRECTAGE!"<<endl;}}};voidmain(){Aclassobject;8.4Try、catch和throw语句try{ object.get();}catch(MyException&x){ x.printMessage();}}【例8-5】异常的抛出和捕捉在同一函数中。#include<iostream.h>#include<string.h>classPerson{8.4Try、catch和throw语句protected:charName[20];//姓名intOld;//年龄unsignedlongID;//身份证号charSex;//性别public:Person(char*theName,inttheOld,unsignedlongtheId,chartheSex);//类Person的构造函数};Person::Person(char*theName,inttheOld,unsignedlongtheId,chartheSex){if((Name==""))//有效性检查8.4Try、catch和throw语句throw"InvalidPerson'sname!";if((Old<0)||(Old>200))//有效性检查throwOld;if((Sex!='M')&&(Sex!='m')&&(Sex!='F')&&(Sex!='f'))//有效性检查throwSex;}voidfun(){try{cout<<"funline1"<<endl;Personp1("zhang",43,1234566L,'M');//类Person的对象p1cout<<"funline2"<<endl;Personp2("",-1,22222222L,'F');//类Person的对象p2cout<<"funline3"<<endl;8.4Try、catch和throw语句Personp3("",-1,33333333L,'M');//类Person的对象p3cout<<"funline4"<<endl;Personp4("",-1,44444444L,'F');//类Person的对象p4 }catch(char)//捕捉错误并进行处理{ cout<<"InvalidSex!"<<endl;}catch(int)//捕捉错误并进行处理{ cout<<"InvalidOld!"<<endl;}catch(char*Msg)//捕捉错误并进行处理8.4Try、catch和throw语句{ cout<<"InvalidName!"<<endl;}}voidmain(){cout<<"beforefun..."<<endl;fun();cout<<"endfun..."<<endl;cout<<"inthemainfunction!"<<endl;}在本程序中,异常的抛出和捕捉都在函数fun中,在实际应用中,异常的引发和处理一般在不同函数中,这样低层函数可以专注于具体问题,上层调用函数可以在恰当的位置设计对不同类型8.4Try、catch和throw语句的异常进行各种处理而不考虑低层函数是如何产生这个异常的。【例8-6】对【例8-5】稍加改动,使异常的抛出和捕捉在不同函数中进行。【例8-6】异常的抛出和捕捉在不同函数中。#include<iostream.h>#include<string.h>classPerson{protected:charName[20];//姓名intOld;//年龄unsignedlongID;//身份证号charSex;//性别8.4Try、catch和throw语句public:Person(char*theName,inttheOld,unsignedlongtheId,chartheSex);//类Person的构造函数};Person::Person(char*theName,inttheOld,unsignedlongtheId,chartheSex){if((Name==""))//有效性检查throw"InvalidPerson'sname!";if((Old<0)||(Old>200))//有效性检查throwOld;if((Sex!='M')&&(Sex!='m')&&(Sex!='F')&&(Sex!='f'))//有效性检查8.4Try、catch和throw语句throwSex;}voidfun(){cout<<"funline1"<<endl;Personp1("zhang",43,1234566L,'M');//类Person的对象p1cout<<"funline2"<<endl;Personp2("",-1,22222222L,'F');//类Person的对象p2cout<<"funline3"<<endl;Personp3("",-1,33333333L,'M');//类Person的对象p3cout<<"funline4"<<endl;Personp4("",-1,44444444L,'F');//类Person的对象p4}8.4Try、catch和throw语句voidmain(){try{cout<<〞beforefun……〞<<endl;fun();cout<<〞endfun……〞<<endl;}catch(char)//捕捉错误并进行处理{ cout<<"InvalidSex!"<<endl;}catch(int)//捕捉错误并进行处理{ 8.4Try、catch和throw语句cout<<"InvalidOld!"<<endl;}catch(char*Msg)//捕捉错误并进行处理{ cout<<"InvalidName!"<<endl;}cout<<endl;cout<<"inthemainfunction!"<<endl;}下面这个例子演示了如何用异常处理纠正程序运行期错误。【例8-7】纠正如下程序运行期错误。#include<iostream.h>#include<string.h>classString8.4Try、catch和throw语句{private: char*str;public:String(){str=0;}String(char*inString){str=newchar[strlen(inString)+1];strcpy(str,inString);}voidreplace(charsearch,charrepl)8.4Try、catch和throw语句{intcounter;for(counter=0;str[counter]!='\0';counter++){if(str[counter]==search){str[counter]=repl;}}}voiddisplay(){cout<<str;}8.4Try、catch和throw语句};voidmain(){ StringstrObject;//该对象不包含任何东西 strObject.replace('+',''); strObject.display();}上面的代码产生下面的运行期错误:Segmentationfault(coredumped)分析:要想纠正如上程序运行期错误,首先要找到出现异常的原因,然后采用异常处理机制去处理异常。因此,下面分两步进行介绍。1.标识异常的原因根据分析知道,在main()函数中,String类的一个空对象被8.4Try、catch和throw语句创立。replace()函数将遍历字符串直到空字符〔‘\0’〕出现,但如果字符串为空,那么操作失败。2.标识异常处理的机制采用try、throw、catch语句进行处理。修改上面程序得如下程序:#include<iostream.h>#include<String>classString{private: char*str;intcheck(){if(str==NULL)8.4Try、catch和throw语句return0;elsereturn1;}public:String(){str=0;}String(char*inString){str=newchar[strlen(inString)+1];strcpy(str,inString);}8.4Try、catch和throw语句voidreplace(charsearch,charrepl){if(check()==0){throw"NULLpointerexception";}intcounter;for(counter=0;str[counter]!='\0';counter++){if(str[counter]==search){str[counter]=repl;}}8.4Try、catch和throw语句}voiddisplay(){cout<<str;}};voidmain(){StringstrObject;//对象不能包含任何的trytry{strObject.replace('+','');//引起replace函数异常strObject.display();}8.4Try、catch和throw语句catch(char*message){cout<<"Exception:"<<message<<endl;}}程序的输出结果如下:Exception:NULLpointerexception8.5标准C++库中的异常类标准C++库中包含9个异常类,它们可以分为运行时异常和逻辑异常:length_error//运行时异常,长度异常domain_error//运行时异常,域异常out_of_range_error//运行时异常,越界异常invalid_argument//运行时异常,参数异常range_error//逻辑异常,范围异常overflow_error//逻辑异常,溢出〔上〕异常underflow_error//逻辑异常,溢出〔下〕异常标准C++库中的这些异常类并没有全部被显式使用,因为C++标准库中很少发生异常,但是这些标准C++库中的异常类可以为编程人员,特别是自己类库的开发者提供一个指导。下面程序简单说明C++标准异常类的使用。【例8-8】演示标准异常类的使用。8.5标准C++库中的异常类#include<iostream>#include<exception>usingnamespacestd;voidmain(){try{exceptiontheError;//声明一个C++标准异常类的对象throw(theError);//抛出该异常类的对象}catch(constexception&theError)//捕捉C++标准异常类的对象{cout<<theError.what()<<endl;//用what()成员函数显示出错原因}8.5标准C++库中的异常类

try{logic_errortheLogicError("LogicError!");//声明一个C++标准异常类logic_error的对象throw(theLogicError);//抛出该异常类的对象}catch(constexception&theError)//捕捉C++标准异常类的对象{cout<<theError.what()<<endl;//用what()成员函数显示出错原因}}8.6程序实例

【例8-9】设计一个异常Exception抽象类,在此根底上派生一个OutOfMemory类响应内存缺乏,派生一个RangeError类响应输入的数不在指定范围内,实现并测试这两个类。程序如下://EG8_8.CPP#include<iostream.h>classException{public: Exception(){}virtual~Exception()

8.6程序实例

{}virtualvoidPrintError()=0;};classOutOfMemory:publicException{public:OutOfMemory(){}~OutOfMemory(){}8.6程序实例

virtualvoidPrintError();};voidOutOfMemory::PrintError(){cout<<"OutofMemory!!"<<endl;}classRangError:publicException{public:RangError(unsignedlongnumber){BadNum=number;}8.6程序实例

~RangError(){}virtualvoidPrintError();virtualunsignedlongGetNumber() { returnBadNum; }virtualvoidSetNumber(unsignedlongnumber) { BadNum=number; }8.6程序实例

private:unsignedlongBadNum;};voidRangError::PrintError(){cout<<"NumberOutofrange.Youused"<<GetNumber()<<"!\n";}voidfn1();unsignedint*fn2();voidfn3(unsignedint*);voidmain(){try8.6程序实例

{fn1();}catch(Exception&theException){theException.PrintError();}}unsignedint*fn2(){unsignedint*n=newunsignedint;if(n==0)throwOutOfMemory();8.6程序实例

returnn;}voidfn1(){unsignedint*p=fn2();fn3(p);cout<<"Thenumberis:"<<*p<<endl;deletep;}voidfn3(unsignedint*p){longNumber;cout<<"Enteraninteger(0---1000):";cin>>Number;8.6程序实例

if(Number>1000||Number<0)throwRangError(Number);*p=Number;}程序的输出结果为:Enteraninteger(0---1000):78Thenumberis:78Enteraninteger(0---1000):1500Numberoutofrange.Youused1500!下面给出一个接近实用的例子,假设开发一个人员管理系统,并且声明了人这个根本类Person,它包含姓名、性别、年龄、证件号码等数据,我们可以事先估计一下可能出现的异常情况,如用户忘记输入姓名,年龄不在正常范围内、证件号码不符合要求、性别不符合要求等等,这要求有一个可以报告这类错误8.6程序实例

的类InvalidPerson。同时,用户输入数据进行处理完成后需要存盘,下一次可以从磁盘中调出这些数据,这个过程中可能的异常是输入输出文件翻开出错、输入数据被破坏等,要求有一个可以报告错误的类IOError。看下面的例子并体会异常处理的用法:【例8-10】人员管理系统中常用的异常处理。//EG8_9.H#ifndefEG8_9.H#defineEG8_9.Husingnamespacestd;classPerson;classError//声明出错类8.6程序实例

{public:Error(constchar*theWhere,constchar*theWhy):why(theWhy),where(theWhere){}virtualvoiddisplay(ostream&out)const;//显示出错内容protected:conststringwhy;//错误原因conststringwhere;//错误地点};#endif//EG8_9.CPP#include<iostream.h>#include<string.h>8.6程序实例

#include"#EG8_9.H"classInvalidPerson:publicError//从类Error派生类InvalidPerson{public:InvalidPerson(constPerson*thePerson,constchar*theWhy):Error("Person::InvalidCheck",theWhy),pPerson(thePerson){}//InvalidPerson类构造函数virtualvoiddisplay(ostream&out)const;//派生类中的出错内容显示protected:constPerson*pPerson;//派生类中新增数据成员};classIOError:publicError//从类Error派生类IOError8.6程序实例

{public:IOError(constchar*theWhere,constchar*theWhy,constchar*theFileName): //IOError构造函数Error(theWhere,theWhy),FileName(theFileName){}virtualvoiddisplay(ostream&out)const; //派生类中的出错内容显示protected:StringFileName; //文件名};classPerson{public:8.6程序实例

Person(conststring&theName,inttheOld,unsignedlongtheId,chartheSex); //Person类构造函数voiddisplay(ostream&out)const;//类Person的显示函数voidoutput(ostream&out)const;//类Person的输出函数staticPerson*input(istream&in);//类Person的输出函数voidInvalidCheck()const;//类Person的有效性检查protected:stringName; //姓名intOld; //年龄unsignedlongId; //身份证号码charSex; //性别};ostream&operator<<(ostream&out,constPerson&thePerson);//重载<<运算符8.6程序实例

voidError::display(ostream&out)const//Error::display函数的实现{out<<"!!!"<<"ErrorRepurt--"<<where<<":"<<why<<endl;//显示错误原因和地点}voidInvalidPerson::display(ostream&out)const//InvalidPerson::display函数的实现{Error::display(out);//调用基类的显示函数out<<"Personobject="<<(*pPerson)<<endl;//显示本类的新增特性}voidIOError::display(ostream&out)const//IOError::display函数的实现8.6程序实例

{Error::display(out);//调用基类的显示函数out<<"File="<<FileName<<endl;//显示本类的新增特性}Person::Person(conststring&theName,inttheOld,unsignedlongtheId,chartheSex):Name(theName),Sex(theSex),Old(theOld),ID(theId){}//Person类构造函数voidPerson::InvalidCheck()const//Person::InvalidCheck函数的实现{if(Name.length()==0)//如果姓名为空,人员信息无效{throwInvalidPerson(this,"Invalidperson'sname");8.6程序实例

//抛出InvalidPerson}if(Old<0||Old>200)//如果人员年龄不在0-200之间,该人员信息无效{throwInvalidPerson(this,"Invalidperson'sOld");}//抛出InvalidPersonif(Id<0L)//如果身份证号码小于0,该人员信息无效{throwInvalidPerson(this,"Invalidperson'sId");//抛出InvalidPerson}if((Sex!='M')&&(Sex!='m')(Sex!='F')&&(Sex!='f'))//如果性别不是字符“M、m、F、f〞,该人员信息无效8.6程序实例

{throwInvalidPerson(this,"Invalidperson'sSex");//抛出InvalidPerson}}voidPerson::display(ostream&out)const//Person::display函数的实现{out<<Name<<"-"<<Old<<"-"<<ID<<"-"<<Sex;//输出人员信息if(out.fail())//如果输出文件出错,那么抛出IOError{throwIOError("Person::display"),"Outputfile","displayerror");}8.6程序实例

}voidPerson::output(ostream&out)const//Person::output函数的实现{InvalidCheck();//首先进行有效性检查,该函数在对象无效时可以抛出InvalidPersonout<<"<"<<Name<<">"<<Old<<""<<ID<<""<<Sex<<"\n";//输出人员信息if(out.fail()) //如果输出文件出错,那么抛出IOError{throwIOError("Person::output"),"Outputfile","outputerror");}}8.6程序实例

voidPerson::input(istream&in)const//Person::input函数的实现{Person*TempPerson=0;//用于保存从in流中输入的对象stringTempPersonName;//保存从in流中输入的对象数据intTempPersonOld;unsignedlongTempPersonID;charTempPersonSex;try{if(in.get()!='<'//提取的数据不正确或者到了文件尾{if(in.eof())8.6程序实例

{return(Person*)0;}throwError("Person::input","Invalidinitialchar");//如果数据不正确,那么抛出Error}getline(in,TempPersonName,'>');//将到>为止的流in中内容保存到TempPersonName中 in>>TempPersonOld>>TempPersonID>>TempPersonSex;//从流in中依次输入到各个变量中in.get();//略过本行中剩余局部,到达下一行TempPerson=newPerson(TempPersonName,TempPersonOld,TempPersonID,TempPersonSex);8.6程序实例

//用于输入数据构造临时对象TempPerson->InvalidCheck();//有效性检查catch(Error&theError)//捕捉错误进行初步处理{cout<<"Errorininput:"<<endl;theError.display(cout);//显示错误deleteTempPerson;//删除该临时对象TempPerson=(Person*)0;throwError("Person::input","inputerror");//抛出Error类的对象}returnTempPerson;}8.6程序实例

//EG8_9.CPP#include<iostream.h>#include<fstream.h>#include"#EG8_9.H"ostream&operator<<(ostream&out,constPerson&thePerson);voidOutputPersons(char*theFileName);voidInputPersons(char*theFileName);voidmain(){try{OutputPerson("d:\\C++\\temp\\person.txt");//将类Person的假设干个对象输出到文件中,可放于自己指定的目录下8.6程序实例

InputPerson("d:\\C++\\temp\\person.txt");//从文件中输入类Person对象并显示}catch(Error&theError)//捕捉错误类型,由于InvalidPerson和类IOError都是类Error的//子类,所以在此处也可以捕捉到这两种错误类型{cout<<"Fatalerrorinprogram:"<<endl;theError.display(cout);//错误信息显示}}ostream&operator<<(ostream&out,constPerson&thePerson);//重载<<运算符的实现{8.6程序实例

thePerson.dispaly(out);returnout;}voidOutputPersons(char*theFileName)//将人员信息输出到文件的函数{ofstreamout(theFileName);if(out.fail())//如果文件翻开出错,抛出IOError类型的对象{throwIOError("OutputPersons","can'topenoutputfile",theFileName);}Personwang("wang",25,750308206L,'M');//类Person的对象wangwang.output(out);//输出,可能抛出类IOError的对象,以下同8.6程序实例

Personzhang("zhang",35,700308205L,'F');zhang.output(out);Personyang("yang",26,800808107L,'F');yang.output(out);Personzhao("zhao",25,40600211004L,'M');zhao.output(out);}voidInputPersons(char*theFileName)//从文件中获得类Person对象并进行显示的函数{inti=0;ifstreamin;in.open(theFileName,ios::in);if(in.fail())//如果文件翻开出错,抛出IOError类型的对象8.6程序实例

{throwIOError("InputPersons","can'topeninputfile",theFileName);}Person*TempPerson;while(!in.eof())//从文件中输入对象并显示该对象{if(TempPerson=Person::input(in)){cout<<"PersonNo."<<++i<<":"<<(*TempPerson)<<endl;deleteTempPerson;}}}//源程序结束第9章模板大多数人或多或少都看过或听说过印刷的过程。不管是印在布上还是在报纸上,通常要用到一个样本。印刷的一种方法是为每一页纸或每一米布一遍遍地重复制作样本。另一种更常见也是更广泛使用的方法是使用印版或模子。创立一个这种样本的模子或模板,用墨水或颜料对它染色,然后把这个样本印到所需要的媒质上。因此,做一个模子可以防止重复制作样本的麻烦。这个模子可以用来印刷任意屡次。在C++中,模板就相当于这里说的模子。模板的实质是函数和类根据要求在运行时的实例化。这意味着程序员在编写代码时不必了解确切的应用环境。类的最终用户能够在执行的时候创立自定义的类。模板的概念可以应用于函数和类。模板是C++支持参数化多态的工具,使用模板可以使用户或者函数声明一种一般模式,使得类中的某些数据成员或者9.1概述9.1概述成员函数的参数、返回值取得任意类型。9.1概述9.2.1函数模板和模板函数在C++中,当一个函数被重载时必须创立它的多个拷贝,每一个拷贝对应它所作用的一种数据类型。看一下max()函数的实例,它返回传递给它的两个数中较大的一个。对每一种要用到的数据类型都要编写函数代码。因此,你得为每一种数据类型编写同样的函数代码,例如int型,float型,char型,double型。下面是max()函数的几个对应不同数据类型的代码版本:intmax(inta,intb){returna>b?a:b;}charmax(chara,charb){returna>b?a:b;9.2.1函数模板和模板函数doublemax(doublea,doubleb){returna>b?a:b;}floatmax(floata,floatb){returna>b?a:b;}可以看出,这样的四个函数的函数体都是相同的,其功能几乎完全一样,只是返回值或者参数不同。对不同的数据类型,相同的代码必须重写屡次,而执行的是相同的功能。这是对时间和精力的浪费,为防止这种浪费,C++为我们提供了一个方便的语法规那么:函数模板。利用函数模板,可以建立一个具有通用功能的函数,支持不同的函数参数和返回值。

9.2.1函数模板和模板函数函数模板的语法形式如下:template<classT>…//函数定义…其中T代表在函数模板中要使用的通用类型,在该函数的调用过程中,T被具体化。例如对上面的重载函数,我们只要声明一个函数模板就可以了:template<classT>Tmax(Ta,Tb){returna>b?a:b}

9.2.1函数模板和模板函数关键字template说明正在声明一个模板,数据类型参数T由模板参数<classT>给出。该模板的含义为:无论模板参数T实例为int、float、char或任意其他类型,包括类类型时,函数max()就为实例化了的参数求最大值。这样定义的max()函数在进行求最大值的操作时,首先必须将模板参数T实例化,从这个意义上说,这里定义的max()函数并不是一个完全的函数,我们称它为函数模板。因此,函数模板代表了一类函数,但是它不是一个完全的函数,必须将其模板参数T实例化后,才能完成具体函数的功能。将T实例化的参数常常称为模板实参。用模板实例化的函数称为模板函数,例如:voidfun(){inti;Mclassx,y;

9.2.1函数模板和模板函数intj=max(i,0);//模板实参为整数类型Mclassm=max(x,y);//模板实参为Mclass类型}这里生成了两个模板函数max(i,0)和max(x,y)。max(i,0)用模板实参int将类型参数T实例化,而max(x,y)将T实例化为Mclass类类型。一个函数模板提供一类函数的抽象,它以任意类型T为参数。由一个函数模板产生的函数称为模板函数,它的函数模板的具体实例。就象类与对象一样,函数模板将具有相同程序正文的一类函数抽象出来,可以适应任意类型T。函数模板对某一特定类型的实例就是模板函数。函数模板代表了一类函数,模板函数表示某一具体的函数。图9-1给出了函数模板和模板函数的关系。

9.2.1函数模板和模板函数实例化实例化实例化函数模板max(Ta,Tb)模板函数max(inta,intb)模板函数max(doublea,doubleb)模板函数max(Xa,Xb)……图9-1

9.2.1函数模板和模板函数【例9-1】编写函数模板max()使得能找出任意类型的两个数中的最大数。#include<iostream.h>template<classtype>typemax(typea,typeb){returna>b?a:b;}voidmain(){cout<<"max('A','a'):"<<max('A','a')<<endl;cout<<"max(30,40):"<<max(30,40)<<endl;cout<<"max(45.67f,12.32f):"<<max(45.67f,12.32f)<<endl;}

9.2.1函数模板和模板函数程序的输出结果为:max('A','a'):amax(30,40):40max(45.67f,12.32f):45.67【例9-2】编写能完成计算任意类型数的平方的函数模板。#include<iostream.h>template<classtype>typesquare(typea){typeb;b=a*a;returnb;}voidmain()

9.2.1函数模板和模板函数

{cout<<"square(23.45f):"<<square(23.45f)<<endl;cout<<"square(43):"<<square(43)<<endl;}程序的输出结果为:square(23.45f):549.903square(43):1849

9.2.2重载函数模板在有些特殊情况下需要对函数模板进行重载,C++允许函数模板被一个或多个同名的非模板函数重载。考虑下面的例子:【例9-3】template<classT>Tmax(Ta,Tb){return(a>b)?a:b;}voidf(inti,charc){max(i,i);max(c,c);max(i,c);//错误:max(Int,char)无法匹配

9.2.2重载函数模板

max(c,i);//错误}这里出现的错误在于:模板类型并不知道int和char之间能进行隐式类型转换。但是,这样的转换在C++中是很普通的。为了解决这个问题,C++允许函数模板可以参与重载。用户可以用一个非模板函数重载一个同名的函数模板,例如,可以这样定义:template<classT>Tmax(Ta,Tb){return(a>b)?a:b;}intmax(int,int);//显式地声明函数max(int,int),不是模板函数

9.2.2重载函数模板voidf(inti,charc){max(i,i);//调用函数max(int,int)max(c,c);//调用模板max(char,char)max(i,c);//调用函数max(Int,int)max(c,i);//调用函数max(int,int)}这里,非模板函数max(int,int)重载了上述的函数模板max(Ta,Tb),当出现调用语句max(i,c);和max(c,i);时,它执行的是重载的非模板函数版本max(int,int)。还可以定义如下函数:

9.2.2重载函数模板charmax(char*x,char*y){return(strcmp(x,y)>0)?x:y;}非模板函数max(ichar*x,char*y)也重载了上述的函数模板,当出现调用语句max(a,b);时,它执行的是这个重载的非模板函数的版本,其中a和b都是字符串变量。在C++中,函数模板与同名的非模板函数的重载方法遵循以下约定:1.寻找一个参数完全匹配的函数,如果找到了,就调用它;2.寻找一个函数模板,将其实例化产生一个匹配的模板函数,如果找到了,就调用它;3.试一试低一级的对函数的重载方法,如通过类型转换可

9.2.2重载函数模板产生参数匹配等,如果找到了,就调用它。如果1、2、3均未找到匹配的函数,那么这个调用是一个错误。如果在第一步有多于一个的选择,那么这个调用是意义不明确的,也会产生一个错误。以上重载模板函数的规那么,可能会引起许多不必要的函数定义的产生,但一个好的实现应该是充分利用这个功能的简单性来抑制不合逻辑的答复。9.3类模板和模板类一个类模板〔也称类属类或类生成类〕允许用户为类定义一种模式。考虑如下向量类〔一维数组〕的例子。不管整形向量还是任何其他类型的向量,在所有类型上进行的根本操作是相同的〔如插入、删除、检索等〕。由于将每个元素的类型当做一个类的类型参数来对待,系统可快速生成类型平安的类定义。类模板定义语法:template<classtype1,……>classclass_name{public:…type1var1;9.3类模板和模板类…};【例9-4】#include<iostream.h>template<classT>classVector{T*data;intsize;public:Vector(int);~Vector(){delete[]data;9.3类模板和模板类}T&operator[](inti){returndata[i];}};其中,关键字template表示正在声明一个模板,其类属参数由模板参数<classT>给出。这时,类Vector声明了一个数组类,当T被实例化为int、char、float、string或complex甚至任意类型Myclass时,Vector被实例化为整形数组、字符数组、浮点数数组、字符串数组或复数数组甚至任意类型Myclass的数组。从这个意义上说,Vector是一个不完全的类。这时Vector被称为类模板,Vector<T>是该类模板的名字。将模板参数实例化的参数常称为模板实参,例如:int、char等。用9.3类模板和模板类模板实参生成的类称为模板类。例如,Vector<int>是一个模板类。因此,类外成员函数定义的语法为:template<classT>Vector<T>::Vector<intn>{data=newT[n];size=n;}模板外的成员函数定义都由template<classT>开始,表示是一个类模板的成员函数,其类模板的类名为Vector<T>。下面的main程序说明了如何使用类模板:voidmain(){9.3类模板和模板类Vector<int>x(5);//产生一个整形向量for(inti=0;i<5;++i)x[i]=i;for(inti=0;i<5;++i)cout<<x[i]<<"";cout<<"\n";}程序的输出结果为:01234语句Vector<int>x(5)由模板类Vector<int>声明一个对象x,它是具有5个元素的整形向量。由于在Vector<int>中重载了运算符“[]〞,所以与一般数组的使用方法没有什么区别。Vector还可以这样用:Vector<int>v1〔10〕;9.3类模板和模板类Vector<complex>v2〔20〕;TypedefVector<complex>cvec;cvecv3(30);v1[3]=7;v2[3]=complex(7,8);这里Vector<int>和Vector<complex>都是模板类。它说明了一个整形向量v1[10],两个复数类型的向量v2[20]和v3[30]。图9-2给出了类模板与模板类的关系。9.3类模板和模板类实例化实例化实例化类模板Vector<T>模板类Vector<int>模板类Vector<char>模板类Vector<double>……图9-29.3类模板和模板类尽管上述例子都只使用了一个模板参数,实际上允许使用多个参数。模板参数可以是类型,也可以是非类型的数据。综上所述,一个类模板说明了单个类怎样建立,就好似类型声明说明了单个对象是怎样建立的一样。当用户需要编写几乎相等的代码时,可以使用模板。可以看出,定义一个模板是一件简单的事。不管是函数模板还是类模板,必须明确规定其模板参数。因此,模板说明语句template<classT>中的关键字template表示正在声明一个模板,模板参数<classT>规定了类属参数。模板参数可以由一个或多个由逗号隔开的参数组成,参数可以是类型,如classT〔class后跟一个标志符组成〕,也可以是非类型参数〔即一般的参数〕。9.3类模板和模板类从语法上讲,要说明一个函数模板,只需将模板说明语句放在一个函数说明的前面,并将相应的参数改为模板参数即可。要说明一个类模板,除了将模板说明语句冠以类说明之前以及设置类中相应的模板参数之外,还要将类模板的名字与模板参数〔用尖括号括起来的参数串〕一起使用。例如:Vector<T>代表类模板名Vector<int>代表模板类名假设需要对同一数据类型定义两个不同的类,下面代码可以实现:【例9-5】#include<iostream.h>template<classT,intz>classw9.3类模板和模板类{public:Ta;w(Tq){a=z+q;cout<<"a="<<a<<endl;}};voidmain(){w<int,10>one(100);w<int,10>*twoptr=&one;//没有创立对象w<int,10*3>three=200;9.3类模板和模板类w<float,40>four=100.45;}程序的输出结果为: a=110 a=230 a=140.45注意:在该实例中,给出了一个根本数据类型作为模板参数。在模板类中,这是允许的。在main()函数的第一和第三行创立的对象属于同一个类。第四行用了一个float型的参数。使用这种表达式,根本数据类型值可以用在对象声明中。9.4程序实例用模板进行程序设计对许多人是陌生的,【例9-6】从一个简单问题入手,介绍了怎样将系统的各个局部按概念特性的不同而划分成块〔pieces〕。当研究这些块时,应该考虑如何将这里所用的技术也应用到更大的程序中。【例9-6】将一个数组的元素进行累加。intsum(int*p,intn){intresult=0;for(inti=0;i<n;i++)result+=p[i];returnresult;}可以使用这样的函数:7.2.4用友元函数重载运算符#include<iostream.h>intsum(int*p,intn){intresult=0;for(i

温馨提示

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

评论

0/150

提交评论