C++ 异常处理:try-catch-throw 的基本用法

C++ 异常处理:try-catch-throw 的基本用法

大家好~ 今天我们来聊聊 C++ 里最基础也最常用的异常处理机制------try-catch-throw。不管是日常写小demo,还是开发中处理边界情况,异常处理都能帮我们避免程序"崩溃闪退",让错误处理更优雅、代码更健壮。

在正式讲用法之前,先搞懂一个核心问题:什么是异常?

简单说,异常就是程序运行时出现的"意外情况"------比如除以零、数组越界、内存分配失败、文件打开失败等等。这些情况如果不处理,程序就会异常终止,给用户带来不好的体验,也不利于我们排查问题。

而 try-catch-throw 三者配合,就形成了 C++ 异常处理的"铁三角":throw 负责"抛出"异常,try 负责"监控"可能出现异常的代码,catch 负责"捕获"并处理异常。三者各司其职,缺一不可。

一、核心语法:try-catch-throw 怎么写?

先看最基础的语法结构,记住这个框架,就能应对大部分简单异常场景:

cpp 复制代码
// 1. 监控可能出异常的代码块
try {
    // 可能抛出异常的代码(比如运算、函数调用、文件操作等)
    代码逻辑;
    // 2. 抛出异常(遇到问题时,主动抛出错误信息)
    throw 异常值; // 异常值可以是int、string、自定义类型等
} 
// 3. 捕获并处理异常(try块中抛出的异常,会被对应的catch块捕获)
catch (异常类型 异常变量) {
    // 异常处理逻辑(比如打印错误信息、恢复程序状态、退出等)
    处理代码;
}
// 可选:捕获所有类型的异常(兜底处理,防止有未被捕获的异常导致程序崩溃)
catch (...) {
    // 通用异常处理(比如打印"未知错误")
    兜底处理代码;
}

二、逐一看懂"铁三角":各自的作用

1. throw:主动"报告"错误

throw 是"抛出异常"的关键字,相当于我们在程序里设置的"警报器"------当检测到不合理的情况时,主动抛出一个"异常信号",告诉程序"这里出问题了!"。

注意点:

  • throw 必须写在 try 块内部,或者在 try 块中调用的函数内部(后续会讲);

  • throw 后面可以跟任意类型的值(int、string、自定义结构体等),比如 throw 1、throw "除数不能为0"、throw MyError();

  • 一旦执行 throw,程序会立刻跳出当前的正常代码流程,去匹配对应的 catch 块。

2. try:监控"风险区域"

try 关键字用来包裹"可能出现异常的代码段",相当于给这段代码装了一个"监控器",时刻盯着代码运行是否正常。

注意点:

  • try 块不能单独存在,必须紧跟至少一个 catch 块(否则编译报错);

  • try 块内部的代码正常执行时,不会触发任何异常处理逻辑,只有当出现 throw 时,才会进入异常处理流程;

  • try 块的作用范围仅限于它包裹的代码,超出 try 块的代码,不会被监控。

3. catch:捕获并"解决"错误

catch 关键字用来"捕获"try 块中抛出的异常,并执行对应的处理逻辑,相当于"异常处理器",收到警报后进行处理。

注意点:

  • catch 后面的括号里,需要指定"要捕获的异常类型",只有当 throw 的异常类型和 catch 的类型匹配时,才能捕获到;

  • 可以有多个 catch 块,用来处理不同类型的异常(按顺序匹配,匹配到一个就执行,不会再匹配后续的);

  • catch (...) 是"万能捕获",可以捕获所有类型的异常,通常放在所有 catch 块的最后,作为兜底(防止有未被匹配的异常导致程序崩溃);

  • catch 块执行完后,程序会跳出 try-catch 结构,继续执行后续的正常代码(不会回到 throw 之前的位置)。

三、实战示例:从简单到复杂

光看语法太抽象,我们用几个示例,一步步理解三者的配合,所有代码都可以直接复制运行,方便大家测试。

示例1:最基础的异常处理(除以零)

场景:计算两个数的除法,当除数为0时,抛出异常并处理。

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int a, b;
    cout << "请输入两个整数(a/b):";
    cin >> a >> b;

    // 监控可能出异常的除法运算
    try {
        if (b == 0) {
            // 除数为0,抛出异常(字符串类型)
            throw "错误:除数不能为0!";
        }
        // 正常逻辑:计算并输出结果
        cout << "结果:" << a / b << endl;
    }
    // 捕获字符串类型的异常
    catch (const char* errMsg) {
        // 处理异常:打印错误信息
        cout << "捕获到异常:" << errMsg << endl;
    }
    // 兜底捕获(防止其他未预料到的异常)
    catch (...) {
        cout << "捕获到未知异常!" << endl;
    }

    // 异常处理完后,继续执行后续代码
    cout << "程序正常结束~" << endl;
    return 0;
}

运行测试:

  • 输入 10 2 → 正常输出结果 5,程序正常结束;

  • 输入 10 0 → 抛出异常,被第一个 catch 捕获,打印错误信息,然后程序正常结束(不会崩溃)。

示例2:多个catch块(处理不同类型异常)

场景:模拟用户输入年龄,要求年龄在 0~120 之间,否则抛出不同类型的异常(int 类型表示错误码,string 表示错误信息)。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    int age;
    cout << "请输入你的年龄:";
    cin >> age;

    try {
        if (age < 0) {
            // 抛出int类型异常(错误码:-1 表示年龄为负)
            throw -1;
        } else if (age > 120) {
            // 抛出string类型异常(错误信息)
            throw string("年龄超出合理范围(0~120)");
        }
        cout << "你的年龄是:" << age << "岁,符合要求~" << endl;
    }
    // 先捕获int类型异常(错误码)
    catch (int errCode) {
        cout << "捕获到错误码:" << errCode << ",错误信息:年龄不能为负数!" << endl;
    }
    // 再捕获string类型异常(错误信息)
    catch (string errMsg) {
        cout << "捕获到异常:" << errMsg << endl;
    }
    // 兜底捕获
    catch (...) {
        cout << "未知异常!" << endl;
    }

    cout << "输入结束~" << endl;
    return 0;
}

关键点:catch 块的顺序很重要!如果把 catch (...) 放在最前面,后面的 catch 块永远不会被执行(因为万能捕获会捕获所有异常)。

示例3:在函数中抛出异常

实际开发中,异常很少直接在 try 块中抛出,更多是在函数内部抛出,然后在调用该函数的地方(try 块中)捕获异常。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 自定义函数:计算除法,除数为0时抛出异常
double divide(double a, double b) {
    if (b == 0) {
        // 函数内部抛出异常,不需要直接写在try块中
        throw string("divide函数:除数不能为0");
    }
    return a / b;
}

int main() {
    double x = 10.5, y = 0;
    try {
        // 调用可能抛出异常的函数
        double result = divide(x, y);
        cout << "除法结果:" << result << endl;
    }
    // 捕获函数中抛出的异常
    catch (string errMsg) {
        cout << "捕获到异常:" << errMsg << endl;
    }

    return 0;
}

原理:当 divide 函数中执行 throw 时,程序会立刻跳出 divide 函数,回到 main 函数的 try 块中,然后匹配对应的 catch 块处理异常。

四、常见注意事项(避坑重点)

这部分一定要记好,否则很容易写出"看似有异常处理,实则无效"的代码。

  1. try 块必须紧跟 catch 块,不能单独存在;多个 catch 块按"具体类型在前,万能捕获在后"的顺序排列。

  2. throw 的异常类型必须和 catch 的类型匹配(注意:字符串常量的类型是 const char*,不是 string,所以 throw "xxx" 要对应 catch (const char*))。

  3. catch 块执行完后,程序不会回到 throw 之前的位置,而是继续执行 try-catch 之后的代码。

  4. 如果 try 块中抛出的异常,没有对应的 catch 块捕获(包括没有写 catch (...)),程序会调用 terminate() 函数,直接崩溃(相当于没做异常处理)。

  5. 异常处理的开销比普通的条件判断(if-else)略大,所以不要滥用------只用来处理"意外情况",不要用异常代替正常的逻辑判断。

五、总结

其实 try-catch-throw 的核心逻辑很简单:用 try 监控风险代码,用 throw 抛出异常警报,用 catch 捕获并处理警报。

掌握了今天讲的基本用法,就能应对大部分简单的异常场景。后续我们还可以聊聊更高级的用法,比如自定义异常类、异常的传递、noexcept 关键字等。

最后,建议大家把上面的示例代码复制到编译器中运行一遍,亲手测试不同的输入,感受异常处理的流程------实践比死记语法更重要哦~

相关推荐
没有bug.的程序员2 小时前
分布式配置深潜:Spring Cloud Config 与 Git 集成内核、版本回滚机制与多环境治理实战指南
java·分布式·git·spring cloud·分布式配置·版本回滚
好家伙VCC2 小时前
# 发散创新:基于ARCore的实时3D物体识别与交互开发实战 在增强现实(
java·python·3d·ar·交互
清水白石0082 小时前
函数签名内省实战:打造通用参数验证装饰器的完整指南
java·linux·数据库
知识分享小能手2 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019 事务和锁 — 语法知识点及使用方法详解(13)
数据库·学习·sqlserver
only-qi2 小时前
Spring Boot 异步任务深度解析:从入门到避坑指南
java·spring boot·线程池·async
EXI-小洲2 小时前
2025年度总结 EXI-小洲:技术与生活两手抓
java·python·生活·年度总结·ai开发
白太岁2 小时前
C++:(3) 线程的关联、条件变量、锁和线程池
开发语言·c++
小钻风33662 小时前
Knife4j 文件上传 multipart/data 同时接受文件和对象,调试时上传文件失效
java·springboot·knife4j
~央千澈~2 小时前
抖音弹幕游戏开发之第7集:识别不同类型的消息·优雅草云桧·卓伊凡
java·服务器·前端