C++异常处理机制(非常详细)
在 C++ 中,如果函数在调用时发生异常,异常通常会被传递给函数的调用者进行处理,而不在发生异常的函数内部处理。
如果函数调用者也不能处理异常,则异常会继续向上一层调用者传递,直到异常被处理为止。如果最终异常没有被处理,则 C++ 运行系统就会捕捉异常,终止程序运行。
C++ 的异常处理通过
通常情况下,被调用的函数如果发生异常,就通过 throw 关键字抛出异常,而函数的上层调用者通过 try…catch 语句检测、捕获异常,并对异常进行处理。
throw 关键字抛出异常的语法格式如下所示:
函数调用者通过 try…catch 语句捕获、处理异常,try…catch 语句的语法格式如下所示:
如果这段代码抛出了异常,则 catch 语句会依次对抛出的异常进行类型匹配;如果某个 catch 语句中的异常类型与抛出的异常类型相同,则该 catch 语句就捕获异常并对异常进行处理。
在使用
在使用 try…catch 语句处理异常时,如果 try 语句块中的某一行代码抛出了异常,则无论异常是否被处理,抛出异常的语句后面的代码都不再被执行。例如,有如下代码:
由运行结果可知,程序输出了“文件不存在”的错误提示信息,这表明在调用 readFile() 函数时,由于文件 log.txt 不存在,readFile() 抛出了异常。
通过第 54 行代码的 catch 语句捕获了该异常,在 catch 语句块中通过对象 fex 调用 printErr() 函数输出了异常信息。同时,catch 语句捕获异常之后,程序直接执行了第 66 行代码,并没有返回执行第 52 行代码的 divide() 函数。
从进入 try 语句块开始到异常被抛出之前,在栈上创建的所有对象都会被析构,析构的顺序与构造的顺序相反,这一过程称为栈解旋或栈自旋。
由运行结果可知,程序抛出了异常,catch 语句捕获并输出了异常提示信息:“纸张画不下啦!!”程序在运行时:
在抛出异常之前,程序会将 try 语句块中创建的对象(num和rectangle)都释放。因此,本例异常信息输出之前调用了一次 Shape 析构函数,用来析构对象 rectangle。
在 try 语句块之外创建的对象 circle,待异常处理完成之后才析构。因此,本例异常信息输出之后又调用了一次 Shape 析构函数。
如果函数调用者也不能处理异常,则异常会继续向上一层调用者传递,直到异常被处理为止。如果最终异常没有被处理,则 C++ 运行系统就会捕捉异常,终止程序运行。
异常处理方式
C++ 的异常处理机制使得异常的引发和处理不必在同一函数中完成,函数的调用者可以在适当的位置对函数抛出的异常进行处理。这样,底层的函数可以着重解决具体的业务问题,而不必考虑对异常的处理。C++ 的异常处理通过
throw
关键字和try…catch
语句结构实现。通常情况下,被调用的函数如果发生异常,就通过 throw 关键字抛出异常,而函数的上层调用者通过 try…catch 语句检测、捕获异常,并对异常进行处理。
throw 关键字抛出异常的语法格式如下所示:
throw 表达式;
在上述格式中,throw 后面的表达式可以是常量、变量或对象。如果函数调用中出现异常,就可以通过 throw 将表示异常的表达式抛给它的调用者。函数调用者通过 try…catch 语句捕获、处理异常,try…catch 语句的语法格式如下所示:
try
{
… //可能会出现异常的代码
}
catch (异常类型1)
{
… //异常处理代码
}
catch (异常类型2)
{
… //异常处理代码
}
…
catch (异常类型n)
{
… //异常处理代码
}
catch (...)
{
… //异常处理代码
}
如果这段代码抛出了异常,则 catch 语句会依次对抛出的异常进行类型匹配;如果某个 catch 语句中的异常类型与抛出的异常类型相同,则该 catch 语句就捕获异常并对异常进行处理。
在使用
try…catch
语句时,有以下几点需要注意:
-
一个 try…catch 语句中只能有一个 try 语句块,但可以有多个 catch 语句块,以便与不同的异常类型匹配。catch 语句必须有参数,如果 try 语句块中的代码抛出了异常,无论抛出的异常的值是什么,只要异常的类型与 catch 语句的参数类型匹配,异常就会被 catch 语句捕获。最后一个 catch 语句参数为
…
符号,表示可以捕获任意类型的异常。 - 一旦某个 catch 语句捕获到了异常,后面的 catch 语句将不再被执行,其用法类似 switch…case 语句。
-
try 和 catch 语句块中的代码必须使用大括号
{}
括起来,即使语句块中只有一行代码。 - try 语句和 catch 语句不能单独使用,必须连起来一起使用。
在使用 try…catch 语句处理异常时,如果 try 语句块中的某一行代码抛出了异常,则无论异常是否被处理,抛出异常的语句后面的代码都不再被执行。例如,有如下代码:
try { func(); add(); cout << 3/0<< endl; } catch (int) { cout << "异常处理" << endl; } cout << "异常处理完毕,程序从此处开始向下执行" << endl;在上述代码中,如果 try 语句块中的 func() 函数调用抛出了异常,并且 catch 语句成功捕获到了异常,则异常处理结束之后,程序会执行 try…catch 语句后面的代码,而不会执行 try 语句块中的 add() 函数。
示例1
下面通过案例演示 C++ 的异常处理,代码如下:#include<iostream> #include<fstream> using namespace std; class AbstractException //定义抽象异常类AbstractException { public: virtual void printErr() = 0; //纯虚函数printErr() }; //定义文件异常类FileException公有继承AbstractException class FileException : public AbstractException { public: virtual void printErr() //实现printErr()函数 { cout << "错误:文件不存在" << endl; } }; //定义整除异常类DivideException公有继承AbstractException class DivideException:public AbstractException { public: virtual void printErr() //实现printErr()函数 { cout << "错误:除零异常" << endl; } }; void readFile() //定义readFile()函数 { ifstream ifs("log.txt"); //创建文件输入流对象ifs并打开log.txt文件 if(!ifs) //如果文件打开失败 { throw FileException(); //抛出异常 } ifs.close(); //关闭文件 } void divide() //定义divide()函数 { int num1 = 100; int num2 = 2; if(num2 == 0) //如果除数num2为0 { throw DivideException(); //抛出异常 } int ret = num1/num2; cout << "两个数相除结果:" << ret << endl; } int main() { try { readFile(); //检测readFile()函数调用 divide(); //检测divide()函数调用 } catch(FileException& fex) //捕获FileException&类型异常 { fex.printErr(); //调用相应函数输出异常信息 } catch(DivideException& dex) //捕获DivideException&类型异常 { dex.printErr(); } catch(...) //捕获任意类型异常 { cout << "处理其他异常" << endl; } cout << "程序执行结束" << endl; return 0; }运行结果:
错误:文件不存在
程序执行结束
- 第 4~8 行代码定义了异常类 AbstractException,AbstractException 类是一个抽象类,该类声明了纯虚函数 printErr()。
- 第 10~17 行代码定义了文件异常类 FileException,该类公有继承 AbstractException 类,并实现了 printErr() 函数,用于输出“文件不存在”的错误提示信息。
- 第 19~26 行代码定义异常类 DivideException,该类公有继承 AbstractException 类,并实现了 printErr() 函数,用于输出除数为 0 的错误提示信息。
- 第 27~35 行代码定义了 readFile() 函数,在该函数中,创建了文件输入流对象 ifs 用于读取文件,如果文件不存在,就抛出 FileException 类型的异常。
- 第 36~46 行代码定义了 divide() 函数,在该函数中,定义两个整数相除,如果除数为 0,就抛出 DivideException 类型的异常。
- 第 49~65 行代码在 main() 函数中使用 try…catch 语句检测并捕获异常。在 try 语句块中,检测 readFile() 函数和 divide() 函数调用,如果有异常抛出,则通过 catch 语句捕获异常。第一个 catch 语句捕获 FileException& 类型的异常,第二个 catch 语句捕获 DivideException& 类型的异常,第三个 catch 语句捕获任意类型的异常。
由运行结果可知,程序输出了“文件不存在”的错误提示信息,这表明在调用 readFile() 函数时,由于文件 log.txt 不存在,readFile() 抛出了异常。
通过第 54 行代码的 catch 语句捕获了该异常,在 catch 语句块中通过对象 fex 调用 printErr() 函数输出了异常信息。同时,catch 语句捕获异常之后,程序直接执行了第 66 行代码,并没有返回执行第 52 行代码的 divide() 函数。
栈解旋
C++ 不仅能够处理各种不同类型的异常,还可以在异常处理前释放所有局部对象。从进入 try 语句块开始到异常被抛出之前,在栈上创建的所有对象都会被析构,析构的顺序与构造的顺序相反,这一过程称为栈解旋或栈自旋。
示例2
下面通过案例演示栈的解旋过程,C++ 代码如下:#include<iostream> using namespace std; class Shape //定义形状类Shape { public: Shape(); //构造函数 ~Shape(); //析构函数 static int count; //静态成员变量count }; int Shape::count = 0; //count初始值为0 Shape::Shape() //实现构造函数 { count++; if(Shape::count == 3) throw "纸张画不下啦!!"; cout << "Shape构造函数" << endl; } Shape::~Shape() //实现析构函数 { cout << "Shape析构函数" << endl; } int main() { Shape circle; //画圆形 try //try语句块检测可能抛出异常的代码 { int num = 2; //定义int类型变量num,表示纸张可画两个图形 cout << "纸张可画图形个数:" << num << endl; Shape rectangle; //画长方形 Shape triangle; //画三角形 } catch(const char* e) //捕获异常 { cout << e << endl; } return 0; }运行结果:
Shape构造函数
纸张可画图形个数:2
Shape构造函数
Shape析构函数
纸张画不下啦!!
Shape析构函数
- 第 3~9 行代码定义了形状类 Shape,该类声明了一个静态成员变量 count,用于记录 Shape 类对象的个数。此外,Shape 类还声明了构造函数和析构函数;
- 第 10~21 行代码,在类外初始化 count 的值为 0,并实现类的构造函数与析构函数。在实现构造函数时,如果 count 值为 3,就抛出一个异常,提示纸张画不下的异常信息;
- 第 24 行代码创建 Shape 类对象 circle,表示画了一个圆形;
- 第 25~31 行代码,在 try 语句块中检测可能抛出异常的代码;
- 第 27 行代码定义 int 类型变量 num 为 2,表示纸张可画两个图形;
- 第 29~30 行代码,创建 Shape 类对象 rectangle 和 triangle;
- 第 32~35 行代码通过 catch 语句捕获 char* 类型异常并输出异常信息。
由运行结果可知,程序抛出了异常,catch 语句捕获并输出了异常提示信息:“纸张画不下啦!!”程序在运行时:
- 首先调用 Shape 类的构造函数创建了对象 circle;
- 然后进入 try 语句块,定义num变量并输出;
- 最后创建 Shape 类对象 rectangle,对象 rectangle 创建成功之后,内存中有两个 Shape 类对象,当程序再创建对象 triangle 时,count 值为 3,就抛出了异常,对象 triangle 并没有创建成功。
在抛出异常之前,程序会将 try 语句块中创建的对象(num和rectangle)都释放。因此,本例异常信息输出之前调用了一次 Shape 析构函数,用来析构对象 rectangle。
在 try 语句块之外创建的对象 circle,待异常处理完成之后才析构。因此,本例异常信息输出之后又调用了一次 Shape 析构函数。
注意,栈解旋只能析构栈上的对象,不会析构动态对象。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。