模板和异常处理.ppt
《模板和异常处理.ppt》由会员分享,可在线阅读,更多相关《模板和异常处理.ppt(59页珍藏版)》请在三一办公上搜索。
1、第9章 模板和异常处理,9.1 函数模板,9.2 类模板,9.3 标准模板库(STL),9.4 异常及其传统处理方法,9.5 使用C+异常处理,9.6 综合应用实例:栈类模板设计,9.1 函数模板,在C+中,函数重载不仅方便用户对函数名的记忆,而且更主要的是完善了同一个函数的代码功能,给调用带来了方便。但函数重载并不能适应所有的数据类型,且同名函数过多,必然会造成代码量增加,程序可读性也会变差。为了解决这个问题,C+引入了模板来使函数中的数据类型进行参数化,使之成为函数模板,这样不仅能适应所有的数据类型,而且函数的代码也大为简化。在C+中,定义一个函数模板是按下列一般格式进行:template
2、/模板声明部分函数声明或定义/函数声明或定义部分从格式可以看出,函数模板的定义包括2个部分:一是模板(类型参数)声明部分,二是函数自身声明或定义部分。其中:(1)template是C+关键字,表示声明的是模板。(2)由一对“”尖括号构成的是该模板的类型参数表,类型参数表中可以有1个或多个类型参数,但多个类型参数之间需用逗号“,”分隔。(3)每个类型参数可以是由C+关键字class和类型名来组成,也可以是由一般具体类型和类型名来组成,其中的类型名一定要符合C+命名规则。例如:template/AT sum(T x,T y)return x+y;,9.1.1 函数模板定义,9.1.1 函数模板定义
3、,则将函数sum定义成了一个函数模板。在该函数模板的模板声明部分中,声明了一个通用类型名T,T又称为模板类型参数。需要说明的是:(1)在类型参数声明中,由于class又是类声明的关键字,为了避免混淆,在ANSI/ISO C+中,往往将class关键字用typename关键字来代替。也就是说,A可写成下列代码:template(2)函数模板的定义可像函数那样按声明和实现两个部分分开进行:template/第1部分:函数模板原型声明T1 sum(T1 x,T1 y);template/第2部分:函数模板实现T2 sum(T2 x,T2 y)return x+y;从中可以看出,函数模板声明和实现的格
4、式与普通函数的声明和实现基本相同。所不同的是,函数模板的声明和实现时必须在函数头前面加上模板声明部分。(3)对于在模板声明部分中声明的类型名来说,若模板声明部分是在函数模板原型声明前面,则类型名的作用域仅在函数模板原型范围中;若模板声明部分是在函数模板定义或实现前面,则类型名的作用域是该函数的作用域范围。正因为如此,函数模板原型声明中的所声明的类型名可以和函数模板实现时声明的类型名不一样,如前面的T1和T2。,9.1.2 函数模板实例化,一旦函数模板定义后,就可以用它来生成各种具体的函数,称为模板函数。在函数调用时,用函数模板生成模板函数的过程,实际上就是将模板参数表中的参数化类型根据实参实例
5、化成具体类型的过程,这个过程称为函数模板的实例化(Instantiation)过程。函数模板的实例化可分为隐式实例化和显式实例化,下面分别说明。1.隐式实例化先来看一个示例:定义的函数模板sum用来求任何合法类型数据之和,T1和T2是该函数模板的两个参数化类型。具体程序如下:例Ex_FunTemplate 使用函数模板#include using namespace std;template/定义函数模板T1 sum(T1 x,T2 y)return x+y;int main()coutsum(2,a)endl;/结果为99coutsum(a,2)endl;/结果为ccoutsum(2.2,5
6、.5)endl;/结果为7.7coutsum(2,5.5)endl;/结果为7return 0;,9.1.2 函数模板实例化,程序运行的结果如下:,分析和说明:(1)代码中,“template”声明2个通用类型名称T1和T2,其中T2作为函数模板中形参的一个类型,而T1既是用于定义形参时的类型,又是用于函数返回值的类型。也就是说,实例化函数返回值的数据类型和第1个实参类型相同。(2)当sum(2,a)时,因实参2是整型int,因此编译将T1=int,返回值也是整型int,结果为99。类似的,当sum(a,2)时,因实参a是char型,因此编译将T1=char,返回值也是char,结果为字符c。
7、可见,sum(2,a)和sum(a,2)返回的结果是不一样的,前者为整型,而后者为字符型。同样可以分析,sum(2.2,5.5)返回的结果是double型,值为7.7。而sum(2,5.5)返回的结果是int型,值为7。可见,当函数模板实际调用时,编译会根据指定的实参类型自动将模板函数的形参数据类型转换成实际参数的类型。这种方式称为函数模板的隐式实例化。,9.1.2 函数模板实例化,2.显式实例化但对于带数据参数的函数模板来说,函数模板则必须使用显式实例化,而不能用隐式实例化,因为隐式实例化无法将模板中的数据参数进行初始化。例如:template T1 sum(T1 x,T2 y)return
8、 x+y+ZZZ;其中,ZZZ是在模板参数中声明的一个int形参,它不是类型名。此时,若有隐式实例化模板函数调用:cout(实参表)其中,函数名后面一对尖括号中的类型名、常量表达式等与模板声明部分的一对尖括号中的内容一一匹配。例如:sum(2,a);,9.1.2 函数模板实例化,就是带参函数模板的一个显式实例化的调用,其匹配关系如图9.1所示,它使得类型T1、T2和形参ZZZ一一实例成int型、char型和值3。,图9.1 函数模板实例化的参数匹配,9.1.2 函数模板实例化,具体代码如下:例Ex_FTExplicit 函数模板的显式实例化#include using namespace st
9、d;template/定义函数模板T1 sum(T1 x,T2 y)return x+y+ZZZ;int main()cout(2,a)(2,5.5)endl;/结果为12.5return 0;代码中,函数模板具有2个通用类型名T1、T2和1个int形参ZZZ。其中,ZZZ与函数形参一起参与求和运算。T1类型既是用于定义形参时的类型,又是用于函数返回值的类型。程序运行的结果如下:可见,在C+中,模板是实现类型参数化的工具,它把类型定义为参数,即类型参数化,这样当参数具体实例化时,可指定C+任意合法的数据类型。因此,参数化类型又称为通用类型。,9.1.3 函数模板具体化,函数模板实例化的目的是指
10、定实际调用时的实际类型以及给带有数据参数指定初值,但对于有些实参,如带有new连续内存空间的类对象,则会因为内存空间的不良操作而导致实例化函数调用终止。例如:,上述程序中,当执行到A的模板实例化函数时,程序异常终止。这是因为此时代码“myswap(尽管它们的类型是一致的,但由于temp、one和two的内存空间长度不等,从而导致对象拷贝失败,程序异常终止。因此对于像这种类型(CName)的myswap模板函数操作应重写其具体化代码,称为函数模板的具体化(Specialization)。,9.1.3 函数模板具体化,函数模板的具体化版本定义的格式与显式实例化的格式非常相似,只是函数头前面需要用“
11、template”来引导,且在函数体中需添加具体实现的代码。例如:template void myswap(CName*a,CName*b)char*temp=new charstrlen(a-GetName()+1;strcpy(temp,a-GetName();a-SetName(b-GetName();b-SetName(temp);delete temp;这样,代码“myswap(”执行的就是上述具体化版本定义的代码。可见,函数模板的具体化版本是解决特殊化类型的重要手段。,9.1.4 函数模板重载,与普通函数一样,函数模板也可以重载。也就是说,函数模板重载能允许多个同名的函数模板存在,
12、但同名的各个函数模板的模板声明部分的形参或函数头中的形参必须有区别:要么形参的个数不同,要么形参的个数相同但参数的通用类型名有所不同。需要说明的是:(1)要注意函数模板重载后,其实例化不能出现“二义性”。例如:template/定义第1个函数模板T1 sum(T1 x,T2 y)return x+y;template/定义第2个函数模板T2 sum(T2 x,T1 y)return x+y;int main()coutsum(10,10)endl;return 0;代码中,定义了两个同名的函数模板,由于它们的模板类型一样,但函数头的类型名不一样,因此它们是合法的声明,都可称为sum的重载函数模
13、板。但在main函数中进行实例化调用时,“coutsum(10,10)endl;”中的sum(10,10)因无法确定是对第1个函数模板实例化还是对第2个函数模板实例化,从而出现了“二义性”。,9.1.4 函数模板重载,(2)当同名普通函数和同名函数模板同时出现在代码中时,要注意在这种情况下的调用优先级。在C+中,每当遇见函数调用时,编译首先检查是否存在重载函数,若匹配成功则调用该函数,否则再去匹配函数模板,即优先调用函数,而不是优先调用函数模板。另外,显式模板函数的优先级高于隐式模板函数。下面来看一个示例:,分析和说明:(1)代码中,定义了2个重载函数模板myswap,它们的区别在于函数头中的
14、形参一个是引用,另一个是指针。当显式实例化调用时,“myswap(a,b);”调用是对第1个函数模板的实例化,而“myswap(”是第2个函数模板的实例化。(2)代码中还显式定义了2个函数模板的具体化(Specialization)版本,其中A版本是对第1个函数模板的具体化,B版本是对第2个函数模板的具体化。如果没有这2个具体化的函数模板,则程序运行会异常终止(原因前面已分析过)。(3)由于函数模板实质上是实现操作的一种框架或一种方案,因而隐式或是显式实例化以及具体化都是对这种方案的具体体现,因此它们都是该函数模板的具体化版本。,程序运行的结果如下:,9.2 类模板,函数模板定义格式基本相同,
15、类模板是按下列一般格式定义的:template/模板声明部分类声明或定义/类声明或定义部分从中可以看出,与函数模板相比,类模板的定义也是包括2个部分:一是模板声明部分,二是类自身声明或定义部分。同样:(1)template是C+关键字,表示声明的是模板。(2)由一对“”号构成的是该模板的类型参数表,类型参数表中可以有1个或多个类型参数,但多个类型参数之间需用逗号“,”分隔。(3)每个类型参数可以是由C+关键字class或typename和类型名来组成,也可以是由一般具体类型和形参名来组成。,9.2.1 类模板的定义,9.2.1 类模板的定义,(4)类模板中的成员函数可以是一般成员函数,也可以是
16、函数模板。例如:template/模板声明部分class CSumpublic:CSum()result=0;CSum()void show(void)cout结果=resultendl;void sum(T1 x,T1 y)result=x+y;void sum(T1 x,T2 y)result=x+y;private:T1result;/结果;,9.2.1 类模板的定义,则是定义了一个CSum类模板,模板声明部分指定了2个通用类型名T1和T2。在类CSum中,数据成员result指定的类型名为T1,而成员函数sum的形参类型有的是T1、有的是T2。同一般类的成员函数一样,类模板中的成员函数
17、的声明和定义可以合并一起在类中进行,也可将成员函数在类中声明而在类外实现。此时,因模板声明部分的类型名只对后跟的类声明有效,因此当类模板中的成员函数在类外实现时必须重新指定模板声明部分,且按下列格式进行:template/模板声明部分类型 类名:函数名(参数表)函数体;其中:(1)类型参数表应与类模板声明时指定的类型参数表相同。(2)模板参数名表列出的应是类型参数表中的类型名,且顺序应与类型参数表中的顺序一致。例如,若类模板中还有一个成员函数sum,在类中声明的代码如下:void sum(T2 x,T2 y);则该成员函数在类外的实现可以是:templatevoid CSum:sum(S2 x
18、,S2 y)result=x+y;,9.2.1 类模板的定义,尽管这里模板声明部分的类型名与类模板声明指定的类型名不一样,但都是通用类型,在实例化时会自动转换成一致的类型,因此上述成员函数的实现代码是合法的。但要注意:(1)在类外实现成员函数时,模板声明部分的类型名虽可与类模板声明指定的类型名不一样,但类型名的个数和性质要一致。就如同“函数原型声明与函数实现时指定的形参名不一样,但形参类型和个数要一致”一样。(2)成员函数所属的类名后面尖括号中的类型名和函数形参的类型名一定是在模板声明部分指定了的类型名,且应和类模板中相应的部分在类型名的个数和性质上保持一致。,9.2.2 类模板的实例化,类模
19、板定义后就可以对其进行实例化,实例化后的类模板称为模板类,它必须用显式实例化方式来指定类模板的具体类型。例如:,在main函数中,“CSum one;”和“CSum two;”都是用了两部分操作:一是将类模板进行实例化,前者将“T1=char,T2=double”,后者使“T1=int,T2=double”,从而使类模板变成具体的模板类;二是用模板类进行实例化,即定义对象。程序运行的结果如下:需要说明的是,在对类模板进行实例化,一定要考虑类中的成员是否有重复定义的情况产生。例如,当“CSum”时,T1=double,T2=double,此时类的成员重载函数都变成了:void sum(doubl
20、e x,double y);从而出现了成员函数sum的重复定义。若是“CSum”时,则类中的成员函数sum依次变成了:void sum(char x,char y);void sum(char x,double y);void sum(double x,double y);,9.2.2 类模板的实例化,因它们的形参类型不一样,故上述同名函数都是合法的重载函数。另外,在类模板声明中,还可指定默认的类型名或形参值。例如:template/指定默认类型class CSum/;则实例化时,可使用默认的具体类型,如下列代码:CSum three;/使用默认类型,9.3 标准模板库(STL),在STL中,
21、迭代器是一种“特殊”的指针,用来指定或引用容器中元素的位置。那么,什么是迭代器?它的类型有哪些呢?1.迭代器的由来这里来以查找操作(find)来说明迭代器的含义。若定义一个findVal函数,用来在double数组中查找一个指定值val,则可有下列代码:double*findVal(double*buf,int n,const double val)double*it;for(it=buf;it buf+n;it+)if(*it=val)return it;return 0;/或return null;如果数组buf中存在这样的值val,则返回该值在数组中的地址,否则返回一个空指针。若查找的是
22、一个单向链表结构,其结点类型Node定义如下:struct Nodedouble data;Node*next;,9.3.1 迭代器(Iterator),9.3.1 迭代器(Iterator),其中,结点指针域next指向下一个结点,最后一个结点的next设为null,则可以编写这样的findNode函数代码,用来在链表中查找值为val的结点:Node*findNode(Node*head,const double val)Node*it;for(it=head;it!=0;it=it-next)if(it-data=val)return it;return 0;对于上述两个函数,虽然用于不同
23、的数据模型(数组、链表)的查找算法,但它们却可写成相似的代码。若设:typedef double*iterator;,9.3.1 迭代器(Iterator),对于findVal函数来说,将形参指针iterator表示在指向数组区间的两个边界的指针begin和end之间。其中,begin指针指向数组的起始位置,end指针指向数组的最后界限位置begin+n,由于这个位置所在的内存空间不是数组的有效空间,因此把这个位置称为“past-the-end”(译为:越尾、超尾、越界等)。这样,findVal函数可写成下列代码:iterator findVal(iterator begin,iterator
24、 end,const double 由于end是越尾指针,因此it指针的结束判断条件是“it end”,而不能是“it=end”。由于数组是一个顺序(连续)存储的数据结构,因此it的结束判断条件还可以是“it!=end”。可见,若将iterator看成是一种迭代器的话,则它必须具有“+”和“*”(引用)运算符操作。因此,对于findNode函数的修改,可先构造一个iterator类,用来重载与结点相关的“+”和“*”(引用)运算符操作。如下面的代码:,9.3.1 迭代器(Iterator),对于链表结构来说,若最后的空指针用越尾指针end来表示,则findNode和findVal函数具有几乎一
25、致的代码。因此,STL遵照上述思路,使每个不同容器类具有各自迭代器,从而使查找、遍历等操作具有极为相似的操作代码,方便用户使用。并且,对于不同容器类的迭代器都还提供了begin和end成员函数用来指向容器中的第一个元素的位置和最后一个元素的越尾位置(past-the-end)。这样,对于所有的容器,其遍历操作都可以有下列统一的代码形式:typedef X XV;XV:iterator it;XV cc;for(it=cc.begin();it!=cc.end();it+)cout*itendl;其中,X表示容器类,如vector、list等,V表示容器类值类型的实例化类型,如double、in
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 模板 异常 处理
链接地址:https://www.31ppt.com/p-6302660.html