CC++ & Algorithm

C++异常处理基础:try-catch

较难27
语言版本:C++Python
概述:用try-catch捕获程序运行中的错误,就像给程序系上安全带,防止意外崩溃。

好的,我将为你扩展这篇关于C++异常处理的文章,让它更详细、更贴近中小学生的理解。


给 C++ 程序系上“安全带”——异常处理 try-catch 入门

想象一下,你正开开心心地走在放学的路上,突然被一块凸起的地砖绊了一下。如果你没做准备,很可能摔个“狗啃泥”,疼得哇哇哭。但如果提前系好了“虚拟安全带”——比如总是小心看路,或者身边有个朋友及时扶你一把——你就不会受伤,还能继续往前走。

在 C++ 程序里,也会遇到类似的“绊脚石”。比如:

  • 除数为 0(数学上不允许)
  • 访问不存在的数组元素(比如数组只有 5 个元素,你却想拿第 10 个)
  • 打开不存在的文件
  • 内存空间不够用

这些意外情况在编程里叫做 异常(exception)。如果不处理,程序就会“摔倒”——直接崩溃退出,就像你摔跤后走不动了。而 try-catch 就是给程序准备的“安全带”和“朋友的手”,能让程序在遇到错误时安全地“扶住”自己,继续运行下去。

怎么用?——三步走:try → throw → catch

  1. try:把可能出错的代码放进 try { } 里面,表示“这里我要小心走,可能要摔”。
  2. throw:当我们判断出错误时,主动用 throw 抛出一个“警告信号”(异常)。这就像你快要摔倒时大喊一声“哎呀,我要倒了!”。
  3. catchcatch 块跟在 try 后面,专门抓住 throw 抛出的信号,然后处理它,就像朋友听到你的喊声后伸手扶住你。

让我们看一个最简单的例子:除法运算,分母可能为零。

#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 0;          // b是0,除法会出问题
    try {
        if (b == 0) {
            throw "除数不能为0!";  // 主动抛出异常,像大喊一声“我要摔倒了”
        }
        int result = a / b;       // 如果b不是0,这里会正常计算
        cout << "结果是:" << result << endl;
    }
    catch (const char* msg) {     // 捕获抛出的字符串类型异常
        cout << "错误:" << msg << endl;   // 扶住程序,输出友好提示
    }
    cout << "程序继续运行,没有崩溃!" << endl;
    return 0;
}

运行这段代码,会输出:

错误:除数不能为0!
程序继续运行,没有崩溃!

如果 b 不是 0,比如改成 int b = 5;,那么就不会进入 throw 语句,程序会正常计算并输出结果,然后跳过 catch 块直接执行后面的代码。

关键点throw 抛出的东西是什么类型,catch 后面就要用对应的类型来接住。上面例子抛出了一个字符串常量,类型是 const char*,所以 catch 也写 const char*

不只是字符串——可以抛出各种类型

throw 后面可以跟任何类型的数据,比如整数、浮点数、甚至一个对象。你可以利用这一点,让 catch 根据不同的错误类型做出不同的反应。

比如,用整数代表错误代码:

int age = -5;
try {
    if (age < 0) {
        throw 1001;  // 抛出一个整数,表示“年龄错误”代码
    }
    cout << "年龄:" << age << endl;
}
catch (int err_code) {   // 捕获整数异常
    cout << "错误代码:" << err_code << ",年龄不能为负数!" << endl;
}

再比如,抛出一个浮点数表示错误级别:

double score = 120.5;
try {
    if (score > 100) {
        throw 1.5;    // 抛出一个 double 类型
    }
}
catch (double level) {
    cout << "分数异常,危险等级:" << level << endl;
}

一个 catch 不够?那就多来几个 catch

现实中的绊倒原因有很多种:可能是石头、可能是香蕉皮、也可能是没关好的井盖。程序也会遇到各种不同的异常,我们希望给每种异常不同的处理方法。

try 后面可以跟 多个 catch,每个 catch 捕获一种类型。捕获顺序很重要:越具体的类型要放在前面,越通用的类型放在后面(比如 ... 表示“任何其他异常”,要放在最后)。

看例子:

#include <iostream>
using namespace std;

int main() {
    int choice;
    cout << "请输入一个数字(1或2): ";
    cin >> choice;

    try {
        if (choice == 1) {
            throw 404;          // 抛出一个整数
        }
        else if (choice == 2) {
            throw "网络错误";    // 抛出一个字符串
        }
        else {
            throw 3.14;         // 抛出一个 double 类型
        }
    }
    catch (int code) {               // 第一个 catch,专门处理整数
        cout << "整数错误,代码:" << code << endl;
    }
    catch (const char* msg) {        // 第二个 catch,处理字符串
        cout << "字符串错误:" << msg << endl;
    }
    catch (...) {                    // 第三个 catch,处理所有其他类型(三个点)
        cout << "未知异常,请检查输入" << endl;
    }

    cout << "程序结束" << endl;
    return 0;
}

注意catch (...) 中的三个点是 C++ 的省略号语法,表示“匹配任何未匹配的异常类型”。它必须放在所有 catch 的最后,否则其他 catch 就没机会了。

新手常常犯的错误

  1. 忘记写 throw,或者 throw 写错了地方 有些同学以为只要在 try 里写代码,程序就会自动处理错误。不对!你必须自己检查条件,然后手动 throw 抛出异常。计算机不会自动知道“这个除数是0需要报错”,需要你用 if 判断并抛出。

    错误示例

    int a = 10, b = 0;
    try {
        int c = a / b;   // 这里直接除,b=0会导致崩溃,但不会触发 catch
    }
    catch (...) {
        cout << "出错啦";  // 这个根本不会执行,因为没 throw
    }
    

    正确做法是在除法前先判断 b==0,然后 throw。

  2. catch 的类型和 throw 的类型不匹配 比如你 throw "错误" (类型是 const char*),却写了 catch (int e),那么异常不会被捕获,程序依然会崩溃(除非有更上层的 catch 或没处理)。

    错误示例

    try {
        throw 42;            // 整数
    }
    catch (const char* s) {  // 类型不匹配,抓不住
        cout << s;
    }
    

    程序会直接终止(默认会调用 terminate 函数),不会输出任何内容。

  3. 忘记在 catch 中使用捕获到的异常变量 有时你只想处理异常,不需要具体信息,可以只写类型而不写变量名,比如 catch (int)。但建议还是写变量名,方便调试时打印信息。

  4. try-catch 当作万能安全网,滥用 try-catch 用来处理预料中的“异常情况”,而不是所有代码错误。比如你写了一个语法错误(少写分号),那不是异常,是 bug,编译器会直接报错。不要用异常来掩盖程序逻辑的不合理。

一个完整的示例:模拟零花钱发放

想象你帮老师发零花钱,每个同学应该发 10 元。但有时老师给你的钱不够,或者名单里有人要发负数?我们用异常处理来应对。

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

int main() {
    int total_money = 30;        // 老师给了30元
    int student_count = 10;      // 有10个同学,每人10元需要100元,不够!
    string name = "小明";

    try {
        if (student_count <= 0) {
            throw "学生人数不能为零或负数";
        }
        if (total_money < student_count * 10) {
            throw 500;           // 抛出错误代码500,表示“钱不够”
        }
        cout << name << "领到零花钱10元" << endl;
    }
    catch (const char* msg) {
        cout << "错误:" << msg << endl;
    }
    catch (int code) {
        if (code == 500) {
            cout << "错误代码500:老师给的钱不够,总共只有" 
                 << total_money << "元!" << endl;
        }
    }
    catch (...) {
        cout << "发生了未预料的错误" << endl;
    }

    cout << "程序运行完毕,没有崩溃!" << endl;
    return 0;
}

运行输出:

错误代码500:老师给的钱不够,总共只有30元!
程序运行完毕,没有崩溃!

相关知识点指引

  • 异常类型:除了基本类型,还可以抛出自定义类对象(比如 class NotEnoughMoney {};),这样能携带更丰富的错误信息。
  • 栈展开:当异常被抛出时,程序会沿着函数调用链向上寻找匹配的 catch,沿途的局部变量会被自动销毁,有点像“退路清理”。
  • 标准异常类:C++ 标准库提供了一组异常类(如 std::runtime_error),你可以直接 throw std::runtime_error("错误信息");,然后 catch (std::exception& e) 捕获。
  • 异常安全:设计代码时,要保证即使抛出异常,资源也能正确释放(比如用智能指针或 RAII)。

现在你可以试着在自己的程序里用上 try-catch 这个“安全带”了。遇到错误时,程序不会慌张崩溃,而是会优雅地告诉你哪里出了问题,然后继续跑完后面的路。这样写出来的代码更可靠,也更不容易被用户骂“你的程序又崩了”!

加油,小程序员!

例题精讲

1单选题

关于C++异常处理中try-catch语句,以下哪个语法是正确的?

Atry { //代码 } catch (int e) { //处理 }
Btry { //代码 } catch { //处理 }
Ctry { //代码 } catch (int e); { //处理 }
Dtry { //代码 } finally { //清理 }
2判断题

一个try块可以对应多个catch块,用于捕获不同类型的异常。

3填空题
在C++异常处理中,当try块内的代码抛出异常时,程序会跳转到对应的___块执行。
4单选题

关于catch(...)的含义,以下描述正确的是?

A捕获所有类型的异常
B捕获int类型的异常
C捕获类型为‘...’的异常
D只能捕获自定义类型的异常
5填空题
C++中,try块后面必须至少有一个___块,否则编译器会报错。