一、C++异常处理基本语法
1.C++通过throw语句和try...catch语句实现对异常的处理
throw 表达式;
该语句抛出一个异常。异常是一个表达式,类型可以是基本类型,也可以是类。
2.try...catch语句的语法如下:
try {
执行语句组(包含throw语句)
}
catch(异常类型) {
异常处理代码
}
...
catch(...) { //捕获所有异常
异常处理代码
}
try...catch语句的执行过程是:
- 执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完跳过所有的catch块;
- 如果 try 块执行的过程中拋出了异常,try块中throw之后的语句不执行,拋出异常后立即跳转到第一个与拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。
3.抛出异常时使用字符串描述异常信息的两种方式:
①
- 执行try块中的语句,如果执行的过程中没有异常抛出,那么执行完后会跳过所有的catch块然后执行之后的语句。
- 如果try块中抛出了异常,那么try块中throw之后的语句不会执行,然后根据throw语句中抛出异常的类型,跳转到第一个类型匹配的catch块中执行,执行完后再跳转到最后一个catch块后的语句继续执行
int main()
{
double m, n;
cin >> m >> n;
try {
if (n == 0)
throw string("The denominator is 0");
cout << m / n << endl;
}
catch (string s) {
cout << s << endl;
}
return 0;
}
输出结果:
②
#include<iostream>
#include<string>
using namespace std;
int main()
{
double m, n;
cin >> m >> n;
try {
if (n == 0)
throw "The denominator is 0";
cout << m / n << endl;
}
catch (const char *msg) {
cout << msg << endl;
}
return 0;
}
输出:
4.能够处理任何异常的语句:catch(...) { 异常处理代码 }
(由于可以捕获所有类型的异常,它之后的catch块实际上就不起作用,因此不要将它写在其他的catch块之后)
int main()
{
double m, n;
cin >> m >> n;
try {
if (n == 0)
throw string("The denominator is 0");
else if (m == 0)
throw - 1;
cout << m / n << endl;
}
catch (string s) {
cout << s << endl;
}
catch (...) //捕获所有类型的异常
{
cout << "catch(...)" << endl;
}
return 0;
}
输出结果:
二、异常的再抛出
- 如果一个函数在执行过程中抛出的异常在本函数就被catch块捕获并处理,那么该异常就不会抛给这个函数的调用者(或者成为上一层函数);
- 如果异常在本函数中没有被处理,那么就会被抛上一层函数
#include <iostream>
#include <string>
using namespace std;
double Devide(double x, double y)
{
if (y == 0)
throw string("devided by zero");
cout << "in Devide" << endl;
return x / y;
}
int CountTax(int salary)
{
try {
if (salary < 0)
throw - 1;
cout << "counting tax" << endl;
}
catch (int) {
cout << "salary < 0" << endl;
}
cout << "end of CountTax function" << endl;
return salary * 0.15;
}
int main()
{
double f = 1.2;
try {
CountTax(-1);
f = Devide(3, 0);
cout << "end of try block" << endl;
}
catch (CException e) {
cout << e.msg << endl;
}
catch (string s){
cout << s << endl;
}
cout << "f = " << f << endl;
return 0;
}
输出:
① CountTax函数抛出异常后自行处理,这个异常就不会被继续抛给调用者,即main函数。因此main函数的try块中CountTax之后的语句 f=Devision(2,0) 还能正常执行。
②Devide函数抛出了异常却不处理,该异常就会被抛给Devide函数的调用者,即main函数。抛出异常后,devide函数立即结束,之后的 cout << "end of try block" << endl;也不会执行,函数也不会返回值,所以后面 f 的值未被改变。
catch块中异常的再抛出
#include<iostream>
#include<string>
using namespace std;
int CountTax(int salary)
{
try {
if (salary < 0)
throw string("zero salary");
cout << "counting tax" << endl;
}
catch (string s) {
cout << "CountTax error : " << s << endl;
throw; //继续抛出捕获的异常,后面的语句不会执行
}
cout << "tax counted" << endl; //不执行
return salary * 0.15;
}
int main()
{
double f = 1.2;
try {
CountTax(-1);
cout << "end of try block" << endl;
}
catch (string s) {
cout << s << endl;
}
cout << "finished" << endl;
return 0;
}
三、C++标准异常类
C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如图 1 所示
ad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 stdexcept。
下面分别介绍以上几个异常类。本节程序的输出以 Visual Studio 2010为准,Dev C++ 编译的程序输出有所不同。
1) bad_typeid
使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常。
2) bad_cast
在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。程序示例如下:
#include <iostream>
#include <stdexcept>
using namespace std;
class Base
{
virtual void func() {}
};
class Derived : public Base
{
public:
void Print() {}
};
void PrintObj(Base & b)
{
try {
Derived& rd = dynamic_cast <Derived &>(b);
//此转换若不安全,会拋出 bad_cast 异常
rd.Print();
}
catch (bad_cast & e) {
cerr << e.what() << endl;
}
}
int main()
{
Base b;
PrintObj(b);
return 0;
}
输出:
3) bad_alloc
在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。程序示例如下:
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
try {
char* p = new char[0x7fffffff]; //无法分配这么多空间,会抛出异常
}
catch (bad_alloc & e) {
cerr << e.what() << endl;
}
return 0;
}
输出:
4) out_of_range
用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。例如:
int main()
{
vector<int> v(10);
try {
//v[100] = 100; 使用下标编译不通过
v.at(100) = 100; //拋出 out_of_range 异常
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
string s = "hello";
try {
char c = s.at(100); //拋出 out_of_range 异常
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
return 0;
}
输出:
如果将v.at(100)
换成v[100]
,将s.at(100)
换成s[100]
,程序就不会引发异常(但会导致程序崩溃)。因为 at 成员函数会检测下标越界并拋出异常,而 operator[] 则不会。operator [] 相比 at 的好处就是不用判断下标是否越界,因此执行速度更快。
-----------------------------------------------------------------------------------
主要参考:http://c.biancheng.net/view/422.html