第九章模板和异常处理9.1模板在实际程序设计过程中,往往会发现这样的现象:程序中所定义的两个或多个函数的函数体完全一样,所不同的只是它们的参数类型不一样。求两个整型数的最小值intiMin(intx,inty){intmv;mv=(xy)?x:y;returnmv;}求两个浮点型数的最小值floatfMin(floatx,floaty){floatmv;mv=(xy)?x:y;returnmv;}由上述两个函数的定义不难看出,除了所要处理的数据类型不同以外,两个函数体中的语句部分完全相同。结论在C++语言中,对这样的两个函数可以先给出其通用的框架定义,而将其参数类型作为可变化的部分来进行处理,当需要调用这样的函数时,再将具体的参数及其类型传送给它,这就是模板的概念。模板的概念模板的定义所谓模板,就是首先定义作为原型的函数或类的框架,然后在需要时再根据已定义的原型来生成具体的函数或类。模板的类型在C++语言中,模板分为两种:函数模板类模板9.1.1函数模板函数模板的定义形式如下:template模板参数表函数值类型函数名(参数表){函数体}这里,template为定义模板的关键字,模板参数表用于指定在实际调用时可以进行替换的参数类型名,其格式为:classT1,classT2,…,classTn这里的class为关键字,T1、T2、…、Tn为用户自己命名的以后需要替换的类型名。说明说明“函数值类型”是模板函数的返回值类型,既可以是已有的类型名,如int、double等,也可以是在模板参数表中给出的类型名,如T1、T2等。同样,“参数表”和“函数体”中所使用的数据类型名既可以是已有的类型名,也可以是在模板参数表中给出的类型名。求a、b最小值的函数模板的定义templateclassTTmindt(Tx,Ty){Tmvmv=(xy)?x:y;returnmv;}在上面函数模板的描述中,T为类型参数,它表示在以后要用指定的数据类型名来进行替换。这也就是说,有了上面的模板定义之后,我们就可以利用它来求任何基本类型数据的最小值了。【例9.1】编一程序,利用模板来求两个数的最小值。#includeiostream.htemplateclassTTmindt(Tx,Ty){Tmv;mv=(xy)?x:y;returnmv;}voidmain(){intidt;doubleddt;idt=mindt(1000,2000);//生成int类型的mindt()函数,并调用之ddt=mindt(3.33,4.44);//生成double类型的mindt()函数,并调用之cout“idt=”idt’\n’;cout“ddt=”ddt‘\n’;}程序的执行结果如下:idt=1000ddt=3.33说明(1)从此例不难看出,同一个模板函数,可以适用于不同的数据类型。另外,由于函数重载是每重载一个函数都要定义其函数实体,所以模板同函数重载也是不同的。(2)具有多个参数的模板可定义如下:templateclassT1,classT2T1func(T1a,T2b){…}(3)在调用模板函数时,系统能自动根据所给定的参数类型来生成相应的函数,并进行调用。9.1.2类模板在C++语言中,不但可以定义函数模板,而且还可以定义类模板。下面,我们看一下能够保存100个整数的堆栈类的设计,通过对这样一个简单例子的学习,来了解一下使用类模板的意义。【例9.2】编一程序,用于实现能够管理整型数据的堆栈类。#includeiostream.hclasscStack{intct;intdata[100];public:cStack(void){ct=0;}voidpush(intdt){if(ct100)data[ct++]=dt;}intpop(void){if(ct0)returndata[--ct];elsereturn0;}};voidmain(){inta,b;cStackist;ist.push(100);ist.push(200);a=ist.pop();b=ist.pop();cout“a=”a‘\n’“b=”b‘\n’;}程序的执行结果如下:a=200b=100说明这里设计的cStack类,只能用于管理整型数据,这是由于类中的每一个数据成员的类型都是固定的,这样,要想处理其它类型的数据就必须重新设计一个新类。为了使cStack类能够处理各种基本数据类型,我们可以使用类模板来定义类的框架,在需要时再根据所提供的类型参数来生成相应的类。类模板的定义形式template模板参数表class类名{数据成员成员函数};其中,template为定义模板的关键字,模板参数表用于指定在实际调用时可以进行替换的参数类型名,其格式为:classT1,classT2,…,classTn这里的class为关键字,T1、T2、…、Tn为用户自己命名的以后需要替换的类型名。既可以利用已有的类型名来定义类中的数据成员,也可以利用在模板参数表的T1、T2等来定义类中的数据成员。【例9.3】编一程序,利用类模板来实现堆栈数据结构的管理。#includeiostream.htemplateclassTclasscStack{intct;Tdata[100];cStack(void){ct=0;}voidpush(Tdt){if(ct100)data[ct++]=dt;}Tpop(void){if(ct0)returndata[--ct];elsereturn0;}};voidmain(){inta,b;doublec,d;cStackintist;cStackdoubledst;ist.push(100);ist.push(200);a=ist.pop();b=ist.pop();dst.push(33.33);dst.push(44.44);c=dst.pop();d=dst.pop();cout“a=”a‘\n’“b=”b‘\n’;cout“c=”c‘\n’“d=”d‘\n’;}程序的执行结果如下:a=200b=100c=44.44d=33.33说明(1)需要注意的是,在上面的例子中不能使用下面的方式来定义对象:cStackist;cStackdst;这是因为与函数模板不同,在上述定义中没有包含类型信息,这样,在编译时系统就不知道要使用何种类型来生成cStack模板类的对象。(2)当模板类中的成员函数要在类的外部定义时,函数的写法要稍微复杂一些,以上面的程序为例,若push()函数和pop()函数在类的外部定义时,其形式如下:说明templateclassTvoidcStackT::push(Tdt){if(ct100)data[ct++]=dt;}templateclassTTcStackT::pop(void){if(ct0)returndata[--ct];elsereturn0;}在实际程序设计过程中经常会使用模板,正确地使用模板来进行程序设计能够减少程序设计工作量,提高程序的正确性和可维护性。9.2异常处理异常处理又被称为例外处理。所谓异常处理是指出现非正常情况时的处理,绝大部分情况是指错误处理。在C语言中,一般是将错误处理代码同一般的程序代码混在一起,这样,在结构不好的C语言程序中,往往到处都可以看到exit()函数。在C++语言中,对异常处理是通过一个特定的结构来进行的。下面的C语言程序是用来判断命令行参数的个数是否为1,如果不为1则显示错误信息。intmain(intargc,char*argv[]){……if(argc!=2){printf(“Error:parametererror!\n”);exit(1);}……return0;}上述程序若用C++语言来编写,则可改为如下形式:intmain(intargc,char*argv[]){……try{if(argc!=2){throw“Error:parametererror!\n”;}}catch(char*err){couterr‘\n’;exit(1);}……return0;}9.2.1try关键字的使用由try关键字括起来的部分主要用于检查是否出现错误。其用法如下:try{//进行错误检查的程序部分}需要注意的是,没有用try关键字括起来的部分不作为错误检查的部分。9.2.2throw关键字的使用当由try所括起来的程序段中检测出错误信息时,在程序中并不是立即进行处理,而是产生一个用于表示发生某种错误的字符串或数值,此字符串或数值通过throw关键字传递出去,以便在下面介绍的catch段中进行处理。throw关键字的用法try{……if(出错1)throw“Error1”;//字符串if(出错2)throw“Fileopenerror!”;if(出错3)throw1;//可以是错误号……}如果try程序段中的程序太长,也可以把错误判断部分写在一个函数里。try{tryfunc();}说明voidtryfunc(){……if(出错1)throw“Error1”;if(出错2)throw“Fileopenerror!”;if(…)throw1;……}9.2.3catch关键字的使用在try程序段中由throw关键字抛出的错误信息,将由catch关键字段捕获。这里的catch实际上相当于错误处理器,catch将根据由throw抛出的信息类型,来分别进行相应的处理。如果需要的话,可以根据错误信息的数据类型来使用多个catch语句,如果throw只抛出一种错误信息类型,则只要准备一个catch语句即可。catch语句是紧接在try语句的后面来使用的。例如try{if(错误a)throw“Error1”;if(错误b)throw1;if(错误c)throw2.34;}catch(char*errstr){//具体错误处理exit(1);}catch(interrno)//对应错误b{//具体的错误处理exit(1);}catch(…)//对应错误c{//具体的错误处理exit(1);}在上面的程序段中,throw语句可以抛出三种类型的数据:字符串、整数和浮点数。为了处理这三种类型的数据,程序中提供了三个catch语句,每个catch语句将捕获一种类型的错误信息。其中,catch(…)相当于switch语句中的default语句,一般被用于对发生原因不确定的错误的处理。【例9.4】编一程序,用于说明字符串类型错误信息的处理过程。#includeiostream.hintmain(){intmonth;cout“Inputmonth:”;cinmonth;try{if(month1)throw“Error:Monthislessthan1”;if(month12)throw“Error:Monthisbiggerthan12”;}catch(char*errstr){couterrstr‘\n’;return1;}cout“End”;return0;}程序的执行结果1:Inputmonth:0error:monthislessthan1程序的执行结果2:Inputmonth:13Error:monthisbiggerthan12程序的执行结果3:Inputmonth:8End说明由此程序不难看出,同一种类型的错误信息都可以在一个catch语句中进行处理,这样整个程序的结构就显得非常整齐。【例9.5】编一程序,用于说明整型错误信息的处理过程。#includeiostream.hintmain(){intmonth;cout“Inputmonth:”;cinmonth;try{if(month1)throw1;if(month12)throw2;}catch(interr