这两天在学习 Java,学到 Java 的异常机制时发现有些概念不理解,异常类,捕获异常的执行流程等,于是决定去复习一下 C++ 里面的异常机制,触类旁通吧,有 C++ 的基础学习 Java 语法还是很容易理解的,同样,学习 Java 也会对于 C++ 的面向对象有更深的理解,毕竟 Java 一切皆是类,下面说正题。C++ 中的异常机制,我是翻看了以前的 C++ 学习视频,西北工业大学 – C++程序设计,主讲老师魏英,个人觉得很不错的一门 C++ 学习课,很系统,由浅入深,好好看一遍保证能学到一些东西,就是那种看完以后觉得哎呀,讲的真好那种感觉,当然也可以遇到问题时用来复习查阅。
一、异常处理的基本概念
1、异常的概念
异常是指程序运行时出现的不正常。程序运行过程中可能会出现下列异常:
- CPU 异常,例在计算过程中,出现除数为零的情况
- 内存异常:
使用 new 或者 malloc 申请动态内存但存储空间不够;
数组下标越界;
使用野指针、迷途指针读取内存 - 设备异常:
无法打开文件,或能够打开文件但文件有损坏,从而无法读取数据;
正在读取磁盘文件时挪动了文件或者磁盘;
正在使用打印机但设备被断开;
正在使用的网络断线或阻塞 - 用户数据异常:
scanf 输入时数据格式错误或类型有错误
正在处理的数据库有错误
程序假定的数据环境发生变化
2、异常处理的方法
- 终止模型
在终止模型中,将假设错误非常关键,以至于程序无法返回异常发生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,程序不能继续执行了。 - 恢复模型
恢复模型是指异常处理程序的工作是修正错误,然后重新尝试调动出问题的过程。
3、异常处理的机制
抛出异常(throw
)、检查异常(try
)、捕获异常(catch
)
基本原理: 把需要检测的程序放到 try 块中,把异常处理的程序放到 catch 块中。如果执行一个函数时出现异常,可以不在该函数中立即处理,而是抛出异常信息,传递给它的上一级调用函数,上一级函数捕获到这个信息后在进行处理。如果上一级函数也不处理,就逐级向上传递,如果传递到了最高一级(main函数)还不处理,最后只能异常终止程序的执行。
C++ 异常处理的机制使异常和处理可以不由同一个函数来完成,这样做的优点是使深层次的函数专注于问题求解,而不必承担处理异常的任务,减轻了深层次函数的负担,而把处理异常对的任务集中到某一层次的函数中专门来解决。
二、异常处理的实现
1、抛出异常
可以使用throw 表达式
抛出异常,将异常抛掷给主调函数去处理,异常通常以类似于实参传递给函数的方式(由 throw)抛出和(被 catch)捕获,throw 表达式的类型决定了所抛出的异常类型。由于 C++ 是根据类型来区分不同的异常的,因此在抛出异常时,throw 表达式的指没有实际意义,而表达式的类型则非常重要。如果程序中有多处要抛出异常,应该用不同的表达式类型来相互区别。
if (test == 0) throw test; //抛出 int 型异常
if (test == 1) throw 'a'; //抛出 char 型异常
if (test == 2) throw 333.23;//抛出 double 型异常
关于 throw 的说明
- 执行 throw 的时候,不会执行跟在 throw 后面的语句,而是将程序从 throw 转移到匹配的 catch,该 catch 可以是同一函数中的 catch,也可以在直接或间接调用发生异常函数的上一级函数中。
- 被抛出的对象是一个用 throw 表达式初始化的“异常对象”,异常对象将传递给对应的 catch,并在异常处理完成后撤销,因此异常对象必须是可以复制的类型(具有复制构造函数)。
- 如果抛出的是数组,被抛出的对象自动转化为指向该数组首元素的指针,如果抛出的是一个函数,函数被转换为指向该函数的指针。
- 如果抛出一个指针,该指针是一个指向派生类对象的基类指针,则那个对象将被分割,只抛出基类的部分。
- 抛出指向局部对象的指针使错误的,因为在抛出指针的时候,必须确保进入异常处理程序时指针所指向的对象仍然存在。
2、检测捕获异常
检测捕获异常的一般形式为:
try
{
... //检测程序块(可能抛出异常的代码)
}
catch(异常说明符 1)
{
... //处理程序(当异常说明符 1 被抛出时执行的程序)
}
catch(异常说明符 2)
{
... //处理程序(当异常说明符 2 被抛出时执行的程序)
}
... //更多的 catch
一个 try 块可以紧跟一个或多个 catch 块,在 try 中执行程序块所抛出的异常,通常会被其中的一个 catch 子句处理,一旦 catch 子句执行结束,程序流程继续执行紧随最后一个 catch 子句后面的语句。
catch 子句中的异常说明符是一个有形参的形参列表,有三种形式:
catch(类型名) //catch只需要了解异常的类型
catch(类型名 形参名) //catch需要了解异常的类型之外的信息
catch(...) //捕获所有异常
因为不可能知道可能被抛出的所有异常,这时便可使用 catch(...)
,如果catch(...)
和其它 catch 子句结合使用,那么它必须是最后一个,否则跟在它后面的 catch 子句都得不到匹配检测。
3、异常处理的执行过程
(1)程序流程到达 try 块,然后执行 try 块内的程序块,如果没有引起异常,那么跟在 try 块后的 catch 子句都不执行,程序从最后一个 catch 子句后面的语句继续执行下去;
(2)抛出异常的时候,将暂停当前函数的执行,开始查找匹配的 catch 子句;
(3)首先检查 throw 是否在 try 内部,如果是,检查 catch 子句,看是否其中之一与抛出对象相匹配,如果找到匹配的 catch,就处理异常;如果找不到,就退出当前函数并释放局部对象,然后继续在调用函数中查找;
(4)如果找到匹配的 catch,就处理异常,如果找不到,则退出调用函数,然后继续在调用这个函数的函数中查找;
(5)沿着嵌套函数调用链继续向上,直到为异常找到一个 catch 子句,只要找到能够处理异常的 catch 子句,就进入该 catch 子句,并在它的处理程序中继续执行,当 catch 结束时,跳转到该 try 块的最后一个 catch 子句之后的语句继续执行;
(6)如果始终未找到与该被抛异常匹配的 catch 子句,最终 main 函数会结束执行,则运行库函数terminate
将被自动调用,terminate
函数的默认功能是终止程序。
异常处理实例:
//==========================================
// Filename : 异常处理实例
// Time : 2019年6月26日
// Author : 柚子树
// Email : [email protected]
//==========================================
#include <iostream>
using namespace std;
void Fun(int test)
{
if (test == 0) throw test; //抛出 int 型异常
if (test == 1) throw 1.5; //抛出 double 型异常
if (test == 2) throw "abc"; //抛出 char* 型异常
cout << "Fun调用正常结束!" << endl;
}
void Call_A(int test)
{
try //检测异常发生
{
Fun(test);
}
catch (int)
{
cout << "Call_A捕获异常int -> "; //捕获异常
}
cout << "Call_A调用正常结束!" << endl; //Call_A 正常结束时输出
}
void Call_B(int test)
{
try
{
Call_A(test);
}
catch (double)
{
cout << "Call_B捕获异常double -> ";
}
catch (...)
{
cout << "Call_B捕获所有未知异常 -> ";
}
cout << "Call_B调用正常结束!" << endl;
}
int main()
{
for (int i = 3; i >= 0; i--)
{
Call_B(i);
}
system("pause");
return EXIT_SUCCESS;
}
运行结果:
Fun调用正常结束!
Call_A调用正常结束!
Call_B调用正常结束!
Call_B捕获所有未知异常 -> Call_B调用正常结束!
Call_B捕获异常double -> Call_B调用正常结束!
Call_A捕获异常int -> Call_A调用正常结束!
Call_B调用正常结束!
4、重抛异常
在 catch 子句中,可以再次抛出异常,例如:
try
{
throw "Hello"; //抛出 char* 异常
}
catch (const char*) //捕获 char* 异常
{
throw; //重新抛出 char* 异常至上一级函数
}
- 其中 throw 不加表达式,表示再次抛出 try 块中检测到的异常表达式
(throw "Hello")
- 重抛异常不能被
try - catch
捕获,只能传到上一级函数
5、异常接口声明
为了加强程序的可读性,使函数的用户能够方便地知道所使用的的函数会抛掷哪些异常,可以在函数的声明中列出这个函数可能抛掷的所有异常类型。例如:
void Fun() throw (A, B, C, D);
这表明函数Fun()
能够且只能抛掷类型 A,B,C,D 及其子类型的异常,如果在函数的声明中没有包括异常接口说明,则次函数可以抛掷任何类型的异常。例如:
void Fun();
一个不抛掷任何类型异常的函数可进行如下形式的声明:
void Fun() throw ();
注: 如果一个函数抛出了它的异常接口声明所不允许的抛出的异常时,unexpected
函数会被调用,该函数的默认行为是调用terminate
函数中止程序。