原文:异常机制(throw、try、catch)
概念:异常处理是一种允许两个独立开发的程序组件在程序执行时遇到不正常的情况相互通信的工具
try{
...
}
catch(){
...
}
catch(){
...
}
catch的参数
- 若catch的参数为类对象,则:
- 若参数为非引用类型,在catch语句块中实际上改变的是局部副本,不改变传入的异常对象本身。相反,如果参数为引用类型,则在语句块内改变参数,也就是改变对象本身
- 如果catch的参数为基类类型,则我们可以使用派生类类型的异常对象对其进行初始化。如果是非引用类型,则异常对象将被切掉一部分,若是引用类型,则以常规的方式绑定到异常对象上。如果传入的参数与某个继承有关,最好将参数定义为引用类型
- 重点:catch参数是基类类型,catch无法使用派生类特有的成员
catch的书写顺序
- 若多个catch与句之间存在着继承关系,则:
- 继承链最低端的类放在前面,继承链最顶端的类放在后面
catch语句重新抛出
- 概念:有时,一条单独的catch语句不能完整地处理某个异常,会将传递的异常继续传递给外层try、catch组合或者上一层的函数处理
- 语法格式:throw; //只是一条throw,不包含任何表达式
- throw;只能出现在catch语句或者由catch语句直接或间接调用的函数之内
- 如果catch参数是引用类型,在catch语句中改变参数值,下一条catch接受的是改变后的参数。代码如下
try
{
try{
}
catch(A &a){
a.data=100;
throw; //将异常抛出给外层处理,因为a为引用,因此抛出后a.data=100
}
}
catch(A a){
a.data=666;
throw; //将异常抛出给外层处理,因为a不是引用,因此抛出后a.data依然等于100
}
捕获所有异常
- 概念:有时候,我们希望将所抛出的异常不论是什么类型,都将其捕获,但是又不知道其类型。为了解决这个问题,我们使用省略号作为异常参数声明
- 格式:catch(...){}
- 注意:catch(...)可以与其它catch组合使用,但是必须放在最后面,因为后面的catch永远都不会执行
- 捕获所有异常通常与重新抛出配合使用,但不是必须
try
{
}
catch(...)
{
//处理某些操作后
throw;//重新抛出异常
}
- 我们知道,在进入构造函数的函数体之前,我们要先执行初始化列表。但是如果try、catch语句块放在构造函数体内,初始化列表如果出现异常,函数体内的try语句块还未生效,所以无法捕获异常。为了解决这种情况,我们必须将构造函数写成函数try语句块,也称为函数测试体
- 函数try语句块既能处理初始化列表,也能处理构造函数体
- 格式:
- try跟在构造函数的值初始化列表的冒号之前,catch跟在构造函数后
class A
{
char* stuName;
public:
A(int len)try:stuName(new char[len])
{
if(len<=0)
throw length_error("长度过低");
}
catch(length_error error)
{cout<
- 概念:try中throw抛出的异常,后面若没有相对应匹配的catch语句块,则将异常传递给外层try匹配的catch语句处理,如果还是找不到匹配的catch,则退出当前的函数,将异常传递给当前函数的外层函数继续寻找。(外层函数指调用此try、catch组合的所在函数的函数),若一直传到main函数,main函数也处理不了,则程序就会调用标准库函数terminate,此函数将终止程序的执行
- 演示案例
下面的代码,若我们分别输入:
- 输入0:inDate中将throw抛出的"value == 0"传递给main函数中的try语句,有相对应的catch匹配,执行printf("main::char*异常---%s\n", str);。
- 输入-60:因为<-50,inDate函数里面的try语句抛出throw me;后面没有相对应的catch语句块相匹配,所以将异常传递到main函数中,有相对应的catch匹配,执行 printf("main::MyExcp异常---%s\n", m.getMyExcpStr());
- 输入22:调用f函数,f函数中throw 3.13;抛出后在inDate中处理,inDate中没有catch语句可以处理,再传递给main函数处理,main函数也处理不了。程序最终调用标准库函数terminal终止程序执行
class MyExcp
{
char srcArr[128];
public:
MyExcp() { strcpy(srcArr, "this is myExcp class\n"); }
char const * getMyExcpStr() const { return srcArr; }
};
void inDate();
void main()
{
try{
inDate();
}
catch (char *str){
printf("main::char*异常---%s\n", str);
}
catch (MyExcp m){
printf("main::MyExcp异常---%s\n", m.getMyExcpStr());
}
}
void f(int v)
{
if (v == 11)
throw v;
if (v == 22)
throw 3.13;
}
void inDate()
{
int val;
scanf("%d",&val);
if (val == 0)
throw "value == 0";
try{
if (val < -50){
MyExcp me;
throw me;
}
if (val > 50){
throw "value > 50";
}
else{
f(val);
}
}
catch (int i){
printf("inDate::int异常---%d\n",i);
}
catch (char *str){
printf("inDate::char*异常---%s\n", str);
}
}
- 1.概念:函数可以在函数体的参数列表圆括号后加上throw限制,用来说明函数可以抛出什么异常
- 2.书写格式
- 建议函数的声明、定义都写上
- 我们可以在函数指针的声明和定义中指定throw
- throw异常说明应该出现在函数的尾指返回类型之前
- 在类成员函数中,应该出现在const以及引用限定符之后,而在final、override、虚函数=0之前
void fun();//可以抛出所有异常(函数的正常形式)
void fun()throw(int);//可以抛出int类型的异常
void fun()throw(int,double);//可以抛出int、double类型的异常
void fun()throw();//不可以抛出异常
- 4.与异常指定说明不符合的情况
- 即使函数指定了throw异常说明,但是函数体内如果还是抛出异常,或是抛出与throw异常说明中不对应的异常,程序不会报错
- 编译器在编译时不会检查throw异常说明,尽管说明了,但抛出了还是不会出错
void fun()throw(int)
{
throw "Hello";//抛出字符串异常,不报错
}
void fun()noexcept; //该函数不会抛出异常
void fun(); //该函数可能会抛出异常
//尽管该函数显式地调用了noexcept,但是该函数仍然可以编译通过
void f()noexcept
{
throw exception(); //违反了noexcept的异常说明
}
//下面两者具有相同的作用,都是声明函数不会出异常
void fun()noexcept;
void fun()throw();
void recoup()noexcept(true); //该函数不会抛出异常
void alloc()noexcept(false); //该函数是否会抛出异常不确定
void fun()noexcept
{}
void main()
{
cout << noexcept(fun())<< endl;//打印true
}
void fun()noexcept(noexcept(gun()))//gun函数与fun函数的异常说明一致
void recoup()noexcept(true); //该函数不会抛出异常
void alloc()noexcept(false); //该函数可能会抛出异常
void (*pf1)(int) noexcept=recoup; //正确,pf1与recoup都不会抛出异常
void (*pf2)(int) =recoup; //正确,recoup不会抛出异常,pf2可能抛出异常,两者互不干扰
pf1 = alloc; //错误,alloc可能抛出异常,但是pf1已经说明了它不会抛出异常
pf2 = alloc; //正确,pf2和alloc都可能抛出异常
class A
{
public:
virtual double f1(double)noexcept; //不会抛出异常
virtual int f2()noexcept(false); //可能会抛出异常
virtual void f3(); //抛出异常
};
class B :public A
{
public:
double f1(double); //错误,Base::f1承若不会抛出异常
int f2()noexcept(false);//正确,与Base::f2的异常说明一致
void f3()noexcept; //正确,Derived的f3做了更严格的限定,是允许的
};
1.栈展开过程中局部对象自动销毁
int main()
{
vector v(1,100);
throw ...;//抛出异常
}
2.析构函数与异常的关系
3.不可抛出局部对象的指针
class A{...省略}
int main()
{
try{
A* a=new A;
throw a;//错误
}
}
4.栈展开过程中的内存泄漏
int main()
{
int *p=new int[5];
throw ...;//抛出异常
}
shared_ptr p(new int[v.size()], [](int *p) { delete[] p; });
5.throw表达式解引用基类指针
exception | 最常见的问题 |
untime_error | 只有在运行时才能检测出的问题 |
range_error | 运行时错误:生成的结果超出了有意义的值域范围 |
overflow_error | 运行时错误:计算上溢 |
underflow_error | 运行时错误:计算下溢 |
logic_error | 程序逻辑错误 |
domain_error | 逻辑错误:参数对应的结果值不存在 |
invalid_argument | 逻辑错误:无效参数 |
length_error | 逻辑错误:试图创建一个超出该类型最大长度的对象 |
out_of_range | 逻辑错误:使用一个超出有效范围的值 |
what(); | 无参数,返回值为类初始化时传入的const char*类型的字符串(代表错误的信息)。该函数一定不会抛出异常 |
int main()//此事例,简单地判断用户输入的数字小于0之后,如何选择
{
int num;
while (cin >> num)
{
try {
if (num < 0)
throw runtime_error("cin num <0 ");//初始化并抛出
}
catch (runtime_error error)//接收runtime_error类型的对象
{
cout <<"the exception is "<> select;
if (!cin || select == 'n')
break;
}
}
}
class CMyArr :public range_error//继承
{
string Cstr;
public:
CMyArr(const string& str):range_error(str),Cstr(str){}
virtual const char* what(){//实现虚函数
return Cstr.c_str();//string转const char*
}
};
void main()
{
try{
int arr[3] = { 1,2,3 };
int index;
cin >> index;
if (index < 0)
throw CMyArr("数组下标错误,请重新输入");//抛出异常
}
catch (CMyArr m){
cout <