c/c++ 异常处理

 

c++ 提供一套强大的异常处理机制, 用于管理程序运行过程中发生的错误和意外情况

异常处理机制通过 trycatchthrow 关键词来实现, 旨在帮助开发者捕捉程序中的异常并进行有效的错误处理

同步异常

c++ 的 try-catch 机制只能处理同步异常(Synchronous Exceptions)

同步异常是指程序内部产生的错误, 通常是在程序运行过程中由操作引发的异常

最常见的同步异常是运行时错误, 例如除零错误、数组越界等

同步异常通常是在函数内部触发, 并且可以通过 throw 语句显式抛出

  • 示例, 输入验证异常

输入字符串, 若其为数字且小于10000且为偶数则为合法状态, 其他为非法状态

#include <iostream>
#include <cmath>

bool judge_num(std::string s) {
    if (s.size() > 5) {
        throw "长度超长";
    }

    int sum = 0;
    for (int i = 0, size = s.size(); i < size; i++) {
        int v = s[i] - '0';
        if (v < 0 || v >9) {
            throw "该字符串非数字";
        }
        sum += (v * pow(10, size - i - 1));
    }

    if (sum > 10000) {
        throw "数字值大于10000";
    }
    if (sum & 1) {
        throw "该数字不为偶数";
    }
    return true;
}

int main() {
    std::string s = "1";
    try {
        if (judge_num(s)) {
            std::cout << s << "是一个合法数字字符串" << std::endl;
        }
    } catch (const char* msg) {
        std::cout << s << "不是一个合法数字字符串" << std::endl;
        std::cerr << "错误原因:" << msg << std::endl;
    }
    return 0;
}

运行结果

1001 is not a valid number string.
Error: The number is not an even number
  • 示例, 除法异常

在除法运算中, 如果遇到除数为 0 的情况, 需要抛出异常并进行处理

#include <iostream>
#include <cstdlib>

double divide(double x, double y) {
    if (y == 0) {
        // 除数为0, 抛出异常
        throw y;
    }
    return x / y;
}

int main() {
    double res;
    try {
        res = divide(2, 3);
        // The result of x/y is:0.666667
        std::cout << "The result of x/y is:" << res << std::endl;

        res = divide(4, 0);
    } catch (double) {
        // Error of dividing zero.
        std::cerr << "Error of dividing zero." << std::endl;
        exit(1);
    }
    return 0;
}

运行显示

The result of x/y is:0.666667
Error of dividing zero.

基本语法

c++ 异常处理通过 try、catch 和 throw 三个关键字实现

#include <iostream>
#include <stdexcept>

void divide(int a, int b) {
    if (b == 0) {
        // 1. 抛出异常对象(推荐抛出标准异常类的实例)
        throw std::runtime_error("Division by zero!");
    }
    std::cout << "Result: " << a / b << std::endl;
}

int main() {
    try {
        // 2. 保护可能抛出异常的代码块
        divide(10, 0);
    } 
    // 3. 捕获异常(务必使用 const 引用捕获)
    catch (const std::runtime_error& e) {
        std::cerr << "Runtime error: " << e.what() << std::endl;
    } 
    // 兜底捕获所有标准异常
    catch (const std::exception& e) {
        std::cerr << "Standard exception: " << e.what() << std::endl;
    }
    return 0;
}

捕获顺序原则

当有多个 catch 块时, 必须将派生类异常的捕获放在基类异常之前

因为 catch 块是按顺序匹配的, 如果把基类(如 std::exception)放在前面, 它将拦截所有派生类异常, 导致后面的 catch 块永远无法执行

c++ 标准异常类

c++ 在 <stdexcept> 头文件中提供一套标准的异常类层次结构

最佳实践是永远抛出继承自 std::exception 的类对象, 并按引用捕获

std::exception
 ├── std::logic_error       (逻辑错误, 如违反前置条件)
     ├── std::invalid_argument (无效参数)
     ├── std::domain_error     (定义域错误, 如数学函数参数越界)
     ├── std::length_error     (长度错误, 如超出最大长度)
     └── std::out_of_range     (越界错误, 如数组下标越界)
 
 └── std::runtime_error     (运行时错误, 如超出程序控制范围)
      ├── std::range_error      (范围错误)
      ├── std::overflow_error   (溢出错误)
      └── std::underflow_error  (下溢错误)