异常处理机制
程序运行时常会碰到一些异常情况,例如:
- 做除法的时候除数为 0;
- 用户输入年龄时输入了一个负数;
- 用 new 运算符动态分配空间时,空间不够导致无法分配;
- 访问数组元素时,下标越界;
- 打开文件读取时,文件不存在。
这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃。
所谓“处理”,可以是给出错误提示信息,然后让程序沿一条不会出错的路径继续执行;也可能是不得不结束程序,但在结束前做一些必要的工作,如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等。
一发现异常情况就立即处理未必妥当,因为在一个函数执行过程中发生的异常,在有的情况下由该函数的调用者决定如何处理更加合适。尤其像库函数这类提供给程序员调用,用以完成与具体应用无关的通用功能的函数,执行过程中贸然对异常进行处理,未必符合调用它的程序的需要。
此外,将异常分散在各处进行处理不利于代码的维护,尤其是对于在不同地方发生的同一种异常,都要编写相同的处理代码也是一种不必要的重复和冗余。如果能在发生各种异常时让程序都执行到同一个地方,这个地方能够对异常进行集中处理,则程序就会更容易编写、维护。
鉴于上述原因,C++ 引入了异常处理机制。其基本思想是:
- 函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者(假定为函数 B),然后 A 立即中止;
- 在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。
- 如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。如果main函数也不处理异常,那么程序就会立即中止。
异常抛出与捕获
要处理异常,就需要捕获异常。C++ 通过 try...catch
语句实现对异常的捕获和处理。
try {
语句组
}
catch(异常类型) {
异常处理代码
}
...
catch(异常类型) {
异常处理代码
}
// catch 可以有多个,但至少要有一个
try…catch 语句的执行过程是:
- 如果在 try 块执行的过程中没有异常拋出,那么执行完后就略过所有 catch 块中的语句,继续向下执行;
- 如果 try 块执行的过程中中拋出了异常,那么拋出异常后立即跳转到第一个和拋出的异常类型相匹配的 catch 块中执行,称作异常被该 catch 块“捕获”,在其中执行完异常处理程序后,再跳转到最后一个 catch 块后面继续向下执行。因为异常而未执行的语句组将不再执行。
除了C++运行时等底层自身抛出异常外,也可以通过throw
主动抛出异常。
throw 表达式; // 表达式的值可以是基本类型,也可以是类
异常类型
C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如下所示。
C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what()
的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 <exception>
。
代码示例:抛出并捕获异常
捕获通用异常
#include <exception>
try
{
// do something
}
catch (std::exception & e)
{
std::cerr <<"ERROR: "<< e.what() << std::endl;
}
捕获特定异常 *
#include <exception>
try
{
// do something
}
catch (std::bad_cast & e)
{
std::cerr <<"ERROR: "<< e.what() << std::endl;
}
捕获throw异常
#include <string>
try
{
if(判断语句) throw std::string("Stack is empty!");
if(判断语句) throw -1;
// do something
}
catch (std::string & e)
{
std::cerr <<"ERROR: "<< e << std::endl;
}
catch (int & e)
{
std::cerr <<"ERROR CODE: "<< e << std::endl;
}
注意:这种throw
抛出的是基本类型,不能使用 std::exception
类来捕获。
捕获任何异常 *
try
{
// do something
}
catch (...)
{
std::cerr << "ERROR!" << std::endl;
}
捕获自定义异常 *
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception
{
const char * what () const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
}
catch(std::exception& e)
{
//其他异常处理
}
}