C++异常处理

C++异常处理

 

程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:

1) 语法错误在编译和链接阶段就能发现,语法错误是最容易发现、最容易定位、最容易排除的错误,例如关键字输错了、分号括号等需要在英文输入法输入的误用中文输入法输入。

2) 逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。

3) 运行时错误是指程序在运行期间发生的错误,如内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。

程序运行时常会碰到一些错误,这些错误如果不能发现并加以处理,很可能会导致程序崩溃。C++ 异常处理涉及到三个关键字:try、catch、throw。

C++ 异常处理机制提供了一种转移程序控制权的方式。运行时错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。C++ 提供了异常机制,让程序捕获运行时错误,并处理这个问题,或者至少告诉用户发生了什么再终止程序。

可以借助 C++ 异常机制来捕获上面的异常,避免程序崩溃。捕获异常的语法为:

try

    包含可能抛出异常的语句; 

catch(类型名 [形参名]) // 捕获特定类型的异常 

     处理异常的语句

catch(类型名 [形参名]) // 捕获特定类型的异常 

     处理异常的语句

catch(...)    // 三个点则表示捕获所有类型的异常 

     处理异常的语句

}

try区段:这个区段中包含了可能发生异常的代码,在发生了异常之后,需要通过throw抛出。

catch子句:每个catch子句都代表着一种异常的处理。catch子句用于处理特定类型的异常。

throw子句:throw 子句用于抛出异常,被抛出的异常可以是C++的内置类型(例如: throw int(1);),也可以是自定义类型。

 

异常的处理规则

throw抛出的异常类型与catch抓取的异常类型要一致;

☆throw抛出的异常类型可以是子类对象,catch可以是父类对象;

☆catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常捕获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获;

☆如果使用catch参数中,使用基类捕获派生类对象,一定要使用传递引用的方式,例如catch (exception &e);

☆异常是通过抛出对象而引发的,该对象的类型决定了应该**哪个处理代码;

☆被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个;

☆在try的语句块内声明的变量在外部是不可以访问的,即使是在catch子句内也不可以访问;

☆栈展开会沿着嵌套函数的调用链不断查找,直到找到了已抛出的异常匹配的catch子句。如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止。

 

C++ 标准异常

C++ 提供了一系列标准异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,标准异常类的继承关系如下图所示:

C++异常处理

表是对上面层次结构中出现的每个异常的说明:

异常

描述

exception

该异常是所有标准 C++ 异常的父类。

bad_alloc

该异常可以通过 new 抛出。

bad_cast

该异常可以通过 dynamic_cast 抛出。

bad_exception

这在处理 C++ 程序中无法预期的异常时非常有用。

bad_typeid

该异常可以通过 typeid 抛出。

logic_error

理论上可以通过读取代码来检测到的异常。

domain_error

当使用了一个无效的数学域时,会抛出该异常。

invalid_argument

当使用了无效的参数时,会抛出该异常。

length_error

当创建了太长的 string 时,会抛出该异常。

out_of_range

该异常可以通过方法抛出,如vector和bitset<>::operator[]()。

runtime_error

理论上不可以通过读取代码来检测到的异常。

overflow_error

当发生数学上溢时,会抛出该异常。

range_error

当尝试存储超出范围的值时,会抛出该异常。

underflow_error

当发生数学下溢时,会抛出该异常。

 

例1、捕捉标准异常的例子

#include <iostream>
using namespace std;

int main()
{
try {
char* p = new char[0xfffffffff]; //抛出异常
}
catch (exception &e){
cout << e.what() << endl; //捕获异常,然后程序结束
}
return 0;
}

运行之,参见下图:

C++异常处理


 
当使用new进行开空间时,申请内存失败,系统就会抛出异常,不用用户自定义异常类型,此时捕获到异常时,就可告诉使用者是哪里的错误,便于修改。

抛出自定义类型异常
虽然 C++ 加入了异常机制来处理很多运行时错误, 但是异常机制的功效非常受限, 很多错误还没办法用标准异常(原生异常)手段捕捉, 比如整数除 0 错误——C++标准没有把除0错当成标准异常。先看下面这段代码:
#include <iostream>
using namespace std;

int main()
{
   float x, y;
   cout << "请输入两个数:" << endl;
   cin >> x >> y;
   cout << "x/y="<< x/y << endl;
  
   return 0;
}

运行之,参见下图:

C++异常处理

 

将上面的代码改为:
#include <iostream>
using namespace std;

int main()
{
   float x=10, y=0;
   cout << "x = 10 , y = 0"<< endl;
try
{
    cout << "x/y = "<< x/y << endl;
}
catch (...)
{
    cout << "未知异常" << endl;
}

   return 0;
}

运行之,参见下图:

C++异常处理

 

C++为什么抓不到除0错“异常”? 参见https://blog.csdn.net/nanyu/article/details/6475555
什么事也没发生一样,c++对除数为0没有捕获到,对于这种情况,怎么办?添加一个判断除数是否为0的条件,使用 throw 语句抛出异常——抛出自定义类型异常。
例2、抛出自定义类型异常并捕捉的例子:
#include <iostream>
using namespace std;
 
double division(float a, float b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}
 
int main ()
{
   float x=10.0, y=0;
   cout << "x = " << x << " " << "y = " << y << endl;
 
   try {
     cout << "x/y = "<< division(x, y) << endl;
   }catch (const char* msg) {
     cerr << msg << endl;
   }
 
   return 0;
}

运行之,参见下图:

C++异常处理

 

 

进一步理解可参见:C++异常捕捉与处理的深入讲解 http://www.cppcns.com/ruanjian/c/357727.html