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> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,标准异常类的继承关系如下图所示:

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

异常

描述

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;
}

运行之,参见下图:

 代码中,尝试通过 new 运算符分配非常大的内存块(0xfffffffff),这实际上是一个非常巨大且超出可用内存范围的值。因此,这段代码会导致 std::bad_alloc 异常被抛出。
 
 

抛出用户自定义类型异常
虽然 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;
}

运行之,参见下图:

将上面的代码改为:
#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++为什么抓不到除0错“异常”? 参见C++为什么抓不到除0错“异常”?_c++ double类型除以0 不会报错_南郁的博客-CSDN博客
什么事也没发生一样,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;
}

运行之,参见下图:

下面例子,实现用户输入两数进行相除的计算,并保证用户输入的除数不能为零,当用户输入除数0时不中断程序,而是提示用户输入正确的除数。源码如下:

#include <iostream>
#include <stdexcept>
using namespace std;

double divideNumbers(double dividend, double divisor) {
    if (divisor == 0) {
        throw invalid_argument("除数不能为零!");
    }
    return dividend / divisor;
}

int main() {
    double num1, num2;
    
    cout << "请输入被除数:";
    cin >> num1;
    
    bool validInput = false;
    while (!validInput) {
        cout << "请输入除数:";
        cin >> num2;

        try {
            double result = divideNumbers(num1, num2);
            cout << "结果:" << result << endl;
            validInput = true;  // 输入有效,退出循环
        } catch (const invalid_argument& e) {
            cout << "发生异常: " << e.what() << endl;
            cout << "请重新输入除数!" << endl;
        }
    }

    return 0;
}

运行之,参见下图:

在上述代码中,我们使用了一个 while 循环来保持用户输入除数直到输入有效的除数(即除数不为零)。如果用户输入的除数为零,在 catch 块中会抛出invalid_argument 异常​

【invalid_argument<stdexcept>头文件中的异常类。关于<stdexcept>头文件详情可见https://cplusplus.com/reference/stdexcept/ 】 ​,并提示用户重新输入除数。

通过设置 validInput 变量来控制循环的终止条件,只有当用户输入的除数有效时,才会将其设置为 true,退出循环。这样,即使用户输入除数为零,程序也不会中断,而是会提示用户重新输入正确的除数。 

进一步理解可参见:C++异常捕捉与处理的深入讲解 C++异常捕捉与处理的深入讲解_C语言_软件编程 - 编程客栈

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐