330 likes | 569 Vues
大学 C++ 程序设计教程. 西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn. 第 12 章 模板与异常处理. 本章目标 掌握函数模板概念和使用 掌握类模板概念和使用 了解异常处理机制. 授课内容. 12.1 模板 12.2 异常处理机制 12.3 友元 程序设计举例 实例编程 矩阵类. 一、模板. 函数模板 template << 模板参数表 >> < 类型 > < 函数名 >(< 参数表 >) { … } 类模板. 函数模板. 定义一个函数模板的形式为:
E N D
大学C++程序设计教程 西安交通大学 计算机教学实验中心http://ctec.xjtu.edu.cn
第12章 模板与异常处理 • 本章目标 掌握函数模板概念和使用 掌握类模板概念和使用 了解异常处理机制
授课内容 • 12.1 模板 • 12.2 异常处理机制 • 12.3 友元 • 程序设计举例 • 实例编程 矩阵类
一、模板 • 函数模板 • template <<模板参数表>> • <类型> <函数名>(<参数表>) • { • … • } • 类模板
函数模板 • 定义一个函数模板的形式为: • 其中<模板参数表>中的模板参数的形式为class <类型参数>,这里关键字class与一般所讲的类无关,而是与<类型参数>一起说明这是一个内部类型或用户自己定义的数据类型。 • 在使用模板函数时,模板中的类型参数可用一个实际类型替换,从而达到了类型通用的目的。 • 当类型参数的含义确定后,编译器将以函数模板为样板,生成一个函数。
[例12-1]定义求两个数据最大值的函数模板 • #include <iostream> • #include <string> • using namespace std; • template <class T> • T Max(T a, T b) • { • return a>b?a:b; • }
int main() • { int i1 = 3, i2 = 5; • double d1 = 3.3, d2 = 5.2; • string str1("xjtu"), str2("xian"); • cout << "Type int: " << Max(i1, i2) << endl; • cout << "Type double: " << Max(d1, d2) << endl; • cout << "Type string: " << Max(str1, str2) << endl; • return 0; • }
函数模板的几点注意事项 • (1)在函数模板的参数表中,至少有一个参数的类型为模板的类型参数。另外,函数的返回值的类型也可以是该类型参数。 • (2)模板中可以带有多个参数类型。例如: • template <class T1, class T2, class T3> • void func1(T1 arg1,T2 arg2, T3 arg3) • { • … • }
(3)函数可以带有模板参数表中未给出的、已存在的数据类型的参数。例如: • template <class T> • T func2(T arg1,int arg2) • { • … • }
类模板 • 类是对问题空间的抽象,而类模板则是对类的抽象,即更高层次上的抽象。与函数模板相似,程序中可以通过高度抽象首先定义一个类模板,然后通过使用不同的实参生成不同的类。 • 类模板的定义方法为: template <class <类型参数>> class <类名> { …… };
[例12-2] 定义一个通用的栈类 template <class T, int n> bool AnyStack <T, n>::Pop(T &elem) { if(m_nTop > 0) { m_nTop--; elem = m_tStack[m_nTop]; return true; } else return false; }
template <class T, int n = 10> • class AnyStack • { T m_tStack[n];int m_nMaxElement; • int m_nTop; • public: • AnyStack() : m_nMaxElement(n), m_nTop(0){} • int GetTop() {return m_nTop;} • bool Push(T);//入栈函数 • bool Pop(T&);//出栈函数 • };
template <class T, int n> • bool AnyStack <T, n>::Push(T elem) • { if(m_nTop<=m_nMaxElement) • { m_tStack[m_nTop] = elem; m_nTop++; • return true; • } • else return false; • }
[例12-2] 定义一个通用的栈类(续) int main() { int n; char * s1; AnyStack <int> iStack;//定义一个整数栈 iStack.Push(5); iStack.Push(6); iStack.Pop(n); cout << "第一个出栈整数= " << n << endl; iStack.Pop(n); cout << "第二个出栈整数= " << n << endl; AnyStack <char *> strStack;//定义一个字符串栈 strStack.Push("It's first string"); strStack.Push("It's second string"); strStack.Pop(s1); cout << "第一个出栈字符串=" << s1 << endl; strStack.Pop(s1); cout << "第一个出栈字符串=" << s1 << endl; return 0; }
二、异常处理机制 • C ++的异常处理使程序可以向更高的层次传递意想不到的事件,从而使程序能更好地从这些异常事件中恢复过来。下面具体介绍异常处理的语法。 • 1. 异常处理的语法 • ① throw表达式用来检测是否产生异常,若是,则抛掷异常。语法如下: • throw 表达式; • ② try 是一个复合语句块,它将那些有可能产生异常的语句框定在try块中,并根据异常的情况使用不同的throw表达式抛出异常。语法如下:
try{//复合语句 • …… • throw 表达式1; • …… • throw 表达式2; • …… • throw 表达式n; • }
③ 紧接try块,使用catch块,将异常处理的语句放在catch块中,以便异常被传递过来时就处理它。每个catch块是一个复合语句,处理相应的异常。语法如下:
catch(异常类型说明1) • { ......//复合语句} • catch(异常类型说明2) • { ......//复合语句} • …… • catch(异常类型说明n) • { ......//复合语句}
如果某程序中发现了自己不能处理的异常,就可以使用throw表达式抛掷这个异常,将它抛掷给调用者。如果某程序中发现了自己不能处理的异常,就可以使用throw表达式抛掷这个异常,将它抛掷给调用者。 • throw的操作数在表示异常类型语法上与return语句的操作数相似,如果程序中有多处要抛掷异常,应该用不同的操作数类型来相互区别,操作数的值不能用来区别不同的异常。
try块中的复合语句是代码的保护段。如果预料某段程序代码(或对某个函数的调用)有可能发生异常,就将它放在try块中。try块中的复合语句是代码的保护段。如果预料某段程序代码(或对某个函数的调用)有可能发生异常,就将它放在try块中。 • 如果这段代码(或被调用的函数)运行时真的遇到异常的情况,其中的throw表达式就会抛掷这个异常。 • 紧接try块的catch块复合语句是异常处理程序,“捕获”由throw表达式抛掷的异常。
2. 异常处理的执行过程 • ① 控制通过正常的顺序执行到达try语句,然后执行try块的保护段。 • ② 如在保护段执行期间没有引起异常,就跳过try块后的所有catch块,继续执行紧接最后一个catch块后面的程序。
异常规格说明 • 异常规格说明存在于函数说明中,位于参数列表之后。函数的所有潜在异常类型均随着关键字throw而插入函数说明中。如: • void SetElem(int i, int j, double val) throw(InvalidIndex); • 而传统的函数声明,如: • void SetElem(int i, int j, double val); • 可能抛出任何一种异常。 • 好的编程习惯:对于每一个有异常抛出的函数都应当加入异常规格说明。
自学内容 • 友元 :允许类外部的函数或者类具有该类私有成员的特权。 • 友元函数 • 友元类
友元函数 • 一个类的友元函数是在该类中说明的、用关键字friend修饰的函数,该函数有权访问类中所有的成员。 • 说明一个友元的一般形式为 • friend <类型> <函数名>(<参数表>);
例如: • class Person • { ...... • public: • friend void FriFunc(Person& person); • ... ... • }; • void FriFunc(Person &person) • { ...... • } • 一个类的友元可以是类外的任何函数,包括不属于任何类的函数和属于某个类的成员函数。
友元类 • 当一个类成为另一个类的友元时,就构成了友元类。这意味着该类的每一个成员函数都是另一个类的友元函数。如: • class Person • { • ...... • public: • friend class Government; • ... ... • };
程序设计举例 • [例12-4] 定义一个求幂函数的函数模板。 • [例12-5] 用函数模板实现顺序查找算法。 • [例12-6] 除0异常。 • [例12-7]求一元二次方程ax2+bx+c=0的根,其中系数a,b,c为实数,由键盘输入。要求使用异常机制。
实例编程---矩阵类 • 实现一个实数矩阵类: • 可以进行加、减、乘和赋值运算,并且要重载运算符“()”,用来返回矩阵元素的值。同时,该类还要支持异常处理机制,能够处理可能的非法矩阵操作。 • 算法 • 一个Matrix对象是具有row和col列double型元素,将它们以行顺序存放在elems中。 • 两个矩阵可以相乘的前提条件是前一个矩阵的列数等于后一个矩阵的行数,具体算法见例4-4。矩阵的加减和赋值运算是每个对应元素的加减和赋值运算,所以进行该类运算的矩阵必须维数相同。
小 结 • 使用模板能够用一个代码段来指定一组相关的函数或类,从而可以帮助程序员写出通用的代码。 • C++中提供了两种模板类型:函数模板是一种抽象通用的函数,用它可生成一批具体的函数;类模板是一种抽象通用的类,用它可生成一批具体的类。
函数模板的定义方法为: • template < class <类型参数>> • <类型> <函数名>(<参数表>) • { … } • 类模板的定义方法为 • template <class <类型参数>> • class <类名> • { • …… • };
习 题 • 1.编写一个求绝对值的函数模板,并测试。 • 2.请将例4-5的冒泡排序函数改写成为模板函数并编写一个程序进行测试。 • 3.在12.1.2类模板一节中,我们定义了一个任意类类型AnyType,请编写一个程序来使用该AnyType类模板。 • 4.例12-2中所定义的通用栈类实际上是不完善的,如无法根据用户需求改变栈的大小,没有提供栈满溢出无法压入和空栈无法弹出提示等,请改进该程序。
5.C++中的数组类型比较简单,它的下标只能从0开始,没有负数下标,而且没有数组越界检查。请用类模板设计一个newArray类,该类的对象可以是整型、浮点型、字符型等任何元素类型的数组,而且当访问数组成员时,如果下标越界,程序可以报错并终止。如下是一些例子:5.C++中的数组类型比较简单,它的下标只能从0开始,没有负数下标,而且没有数组越界检查。请用类模板设计一个newArray类,该类的对象可以是整型、浮点型、字符型等任何元素类型的数组,而且当访问数组成员时,如果下标越界,程序可以报错并终止。如下是一些例子: • newArray <int> A1(3) //同传统类型的整型数组 • //包含5个元素的浮点型数组,其成员为A2[-2], A2[-1], A2[0], A2[1], A2[2] • newArray <float> A2(-2, 3) • 请编写一个测试程序。
6.例6-1给出的求阶乘n!的函数,当用户的输入太大时(如51),会出现错误,请编写一个程序,使用异常处理机制来解决这一问题。6.例6-1给出的求阶乘n!的函数,当用户的输入太大时(如51),会出现错误,请编写一个程序,使用异常处理机制来解决这一问题。 • 7.编程并观察当库函数sqrt()的参数为负数,log()的参数为0时,系统会出现什么情况,请解决之。