C++ 异常处理

C++C++Beginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

在本实验中,你将学习 C++ 中异常处理的基本概念。你将从理解如何抛出和捕获基本异常开始,异常是处理程序中运行时错误或意外情况的一种方式。然后,你将探索 trycatchthrow 关键字的使用,以及标准异常类如 std::exception。此外,你还将学习如何定义自定义异常类,并为不同的异常类型实现多个 catch 块。最后,你将探索使用嵌套的 try-catch 块来处理程序中不同层次的异常。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL cpp(("`C++`")) -.-> cpp/OOPGroup(["`OOP`"]) cpp(("`C++`")) -.-> cpp/AdvancedConceptsGroup(["`Advanced Concepts`"]) cpp(("`C++`")) -.-> cpp/StandardLibraryGroup(["`Standard Library`"]) cpp/OOPGroup -.-> cpp/classes_objects("`Classes/Objects`") cpp/OOPGroup -.-> cpp/class_methods("`Class Methods`") cpp/OOPGroup -.-> cpp/constructors("`Constructors`") cpp/OOPGroup -.-> cpp/inheritance("`Inheritance`") cpp/AdvancedConceptsGroup -.-> cpp/exceptions("`Exceptions`") cpp/StandardLibraryGroup -.-> cpp/standard_containers("`Standard Containers`") subgraph Lab Skills cpp/classes_objects -.-> lab-446082{{"`C++ 异常处理`"}} cpp/class_methods -.-> lab-446082{{"`C++ 异常处理`"}} cpp/constructors -.-> lab-446082{{"`C++ 异常处理`"}} cpp/inheritance -.-> lab-446082{{"`C++ 异常处理`"}} cpp/exceptions -.-> lab-446082{{"`C++ 异常处理`"}} cpp/standard_containers -.-> lab-446082{{"`C++ 异常处理`"}} end

抛出和捕获基本异常

在这一步骤中,你将学习 C++ 中异常处理的基本概念,重点是如何抛出和捕获基本异常。异常是处理程序中运行时错误或意外情况的一种方式。

打开 WebIDE,在 ~/project 目录下创建一个名为 basic_exceptions.cpp 的新文件:

touch ~/project/basic_exceptions.cpp

将以下代码添加到 basic_exceptions.cpp 中:

#include <iostream>
#include <stdexcept>

int divide(int numerator, int denominator) {
    // Throw an exception if denominator is zero
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

int main() {
    try {
        // Attempt a normal division
        int result1 = divide(10, 2);
        std::cout << "10 / 2 = " << result1 << std::endl;

        // Attempt division by zero
        int result2 = divide(10, 0);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::exception& e) {
        // Catch and handle the exception
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

让我们分解关键组件:

  1. throw 关键字:

    • 用于在发生错误时生成异常
    • 在此示例中,当尝试除以零时,我们抛出一个 std::runtime_error
  2. try 块:

    • 包含可能生成异常的代码
    • 允许你尝试有风险的操作
    • 如果发生异常,程序控制权将转移到 catch
  3. catch 块:

    • 处理在 try 块中抛出的异常
    • 捕获特定类型的异常(此处为 std::exception
    • 使用 e.what() 获取错误消息

编译并运行程序:

g++ basic_exceptions.cpp -o basic_exceptions
./basic_exceptions

示例输出:

10 / 2 = 5
Error: Division by zero is not allowed!

关于基本异常处理的关键点:

  • 异常提供了一种优雅处理运行时错误的方式
  • throw 用于生成异常
  • trycatch 协同工作以管理异常情况
  • 通过提供受控的错误处理,防止程序崩溃

理解 try、catch 和 throw 关键字

在这一步骤中,你将深入探讨 C++ 异常处理的三个关键关键字:trycatchthrow。这些关键字协同工作,为你的程序创建一个健壮的错误处理机制。

打开 WebIDE,在 ~/project 目录下创建一个名为 exception_keywords.cpp 的新文件:

touch ~/project/exception_keywords.cpp

将以下代码添加到 exception_keywords.cpp 中:

#include <iostream>
#include <string>

// Function that might throw an exception
int processAge(int age) {
    // Throw an exception for invalid age
    if (age < 0) {
        throw std::string("Age cannot be negative");
    }
    if (age > 120) {
        throw std::string("Age is unrealistically high");
    }
    return age;
}

int main() {
    // First try block: handling age validation
    try {
        // Successful case
        int validAge = processAge(25);
        std::cout << "Valid age: " << validAge << std::endl;

        // This will throw an exception
        int invalidAge1 = processAge(-5);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        // Catch block to handle the thrown exception
        std::cout << "Error: " << errorMessage << std::endl;
    }

    // Second try block: another example
    try {
        // This will throw another exception
        int invalidAge2 = processAge(150);
        std::cout << "This line will also not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        std::cout << "Error: " << errorMessage << std::endl;
    }

    return 0;
}

让我们分解关键组件:

  1. throw 关键字:

    • 用于在满足特定条件时生成异常
    • 可以抛出不同类型的对象(字符串、整数、自定义对象)
    • 立即停止当前函数的执行
  2. try 块:

    • 包含可能生成异常的代码
    • 允许你尝试有风险的操作
    • 如果发生异常,程序控制权将转移到匹配的 catch
  3. catch 块:

    • 捕获并处理特定类型的异常
    • 可以为不同的异常类型设置多个 catch 块
    • 通过优雅地处理错误,防止程序崩溃

编译并运行程序:

g++ exception_keywords.cpp -o exception_keywords
./exception_keywords

示例输出:

Valid age: 25
Error: Age cannot be negative
Error: Age is unrealistically high

关于异常关键字的关键点:

  • throw 用于发出错误信号
  • try 定义可能生成异常的代码块
  • catch 处理异常并防止程序终止
  • 异常提供了一种结构化方式来管理运行时错误

使用标准异常类(std::exception)

在这一步骤中,你将学习 C++ 中的标准异常类,以及如何使用 std::exception 层次结构来处理不同类型的运行时错误。C++ 标准库提供了一组预定义的异常类,涵盖了各种错误场景。

打开 WebIDE,在 ~/project 目录下创建一个名为 standard_exceptions.cpp 的新文件:

touch ~/project/standard_exceptions.cpp

将以下代码添加到 standard_exceptions.cpp 中:

#include <iostream>
#include <stdexcept>
#include <limits>

double divideNumbers(double numerator, double denominator) {
    // Check for division by zero using std::runtime_error
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

void checkArrayIndex(int* arr, int size, int index) {
    // Check for out-of-range access using std::out_of_range
    if (index < 0 || index >= size) {
        throw std::out_of_range("Array index is out of bounds!");
    }
    std::cout << "Value at index " << index << ": " << arr[index] << std::endl;
}

int main() {
    try {
        // Demonstrate division by zero exception
        std::cout << "Attempting division:" << std::endl;
        double result = divideNumbers(10, 0);
    }
    catch (const std::runtime_error& e) {
        std::cout << "Runtime Error: " << e.what() << std::endl;
    }

    try {
        // Demonstrate array index out of range exception
        int numbers[] = {1, 2, 3, 4, 5};
        int arraySize = 5;

        std::cout << "\nAccessing array elements:" << std::endl;
        checkArrayIndex(numbers, arraySize, 2);  // Valid index
        checkArrayIndex(numbers, arraySize, 10); // Invalid index
    }
    catch (const std::out_of_range& e) {
        std::cout << "Out of Range Error: " << e.what() << std::endl;
    }

    return 0;
}

让我们探索标准异常类:

  1. std::exception

    • 所有标准异常的基类
    • 提供虚函数 what() 以获取错误描述
  2. 常见的派生异常类:

    • std::runtime_error:用于只能在程序执行期间检测到的运行时错误
    • std::out_of_range:当索引或迭代器超出有效范围时抛出
    • 其他常见类包括 std::logic_errorstd::invalid_argument

编译并运行程序:

g++ standard_exceptions.cpp -o standard_exceptions
./standard_exceptions

示例输出:

Attempting division:
Runtime Error: Division by zero is not allowed!

Accessing array elements:
Value at index 2: 3
Out of Range Error: Array index is out of bounds!

关于标准异常类的关键点:

  • 提供了一种结构化方式来处理不同类型的错误
  • 每个异常类都有特定的用途
  • what() 方法返回描述性错误消息
  • 有助于创建更健壮且信息丰富的错误处理机制

定义自定义异常类

在这一步骤中,你将学习如何在 C++ 中创建自定义异常类。自定义异常允许你定义特定于应用程序需求的错误类型。

打开 WebIDE,在 ~/project 目录下创建一个名为 custom_exceptions.cpp 的新文件:

touch ~/project/custom_exceptions.cpp

将以下代码添加到 custom_exceptions.cpp 中:

#include <iostream>
#include <string>
#include <stdexcept>

// Custom exception class for bank account errors
class InsufficientFundsException : public std::runtime_error {
public:
    // Constructor that takes account balance and withdrawal amount
    InsufficientFundsException(double balance, double amount)
        : std::runtime_error("Insufficient funds"),
          currentBalance(balance),
          withdrawalAmount(amount) {}

    // Method to get detailed error information
    double getCurrentBalance() const { return currentBalance; }
    double getWithdrawalAmount() const { return withdrawalAmount; }

private:
    double currentBalance;
    double withdrawalAmount;
};

class BankAccount {
private:
    double balance;

public:
    BankAccount(double initialBalance) : balance(initialBalance) {}

    void withdraw(double amount) {
        // Check if withdrawal amount exceeds current balance
        if (amount > balance) {
            throw InsufficientFundsException(balance, amount);
        }
        balance -= amount;
        std::cout << "Withdrawal successful. Remaining balance: $"
                  << balance << std::endl;
    }

    double getBalance() const { return balance; }
};

int main() {
    try {
        // Create a bank account with initial balance
        BankAccount account(100.0);

        // Attempt a valid withdrawal
        account.withdraw(50.0);

        // Attempt an invalid withdrawal
        account.withdraw(75.0);
    }
    catch (const InsufficientFundsException& e) {
        std::cout << "Error: " << e.what() << std::endl;
        std::cout << "Current Balance: $" << e.getCurrentBalance() << std::endl;
        std::cout << "Attempted Withdrawal: $" << e.getWithdrawalAmount() << std::endl;
    }

    return 0;
}

让我们分解自定义异常类:

  1. 继承自 std::runtime_error

    • 为自定义异常类提供基础
    • 允许使用 what() 方法获取错误描述
  2. 自定义异常特性:

    • 构造函数包含额外的错误上下文
    • 提供方法以获取特定的错误详细信息
    • 提供更丰富的错误处理信息

编译并运行程序:

g++ custom_exceptions.cpp -o custom_exceptions
./custom_exceptions

示例输出:

Withdrawal successful. Remaining balance: $50
Error: Insufficient funds
Current Balance: $50
Attempted Withdrawal: $75

关于自定义异常类的关键点:

  • 继承自标准异常类
  • 添加特定的错误上下文和方法
  • 提供更详细的错误信息
  • 改进错误处理和调试能力

为不同异常类型实现多个 catch 块

在这一步骤中,你将学习如何使用不同的 catch 块处理多种异常类型。这种方法允许你为程序中可能发生的各种异常类型提供特定的错误处理。

打开 WebIDE,在 ~/project 目录下创建一个名为 multiple_catch.cpp 的新文件:

touch ~/project/multiple_catch.cpp

将以下代码添加到 multiple_catch.cpp 中:

#include <iostream>
#include <stdexcept>
#include <string>

class InvalidAgeException : public std::runtime_error {
public:
    InvalidAgeException(int age)
        : std::runtime_error("Invalid age"), invalidAge(age) {}

    int getInvalidAge() const { return invalidAge; }

private:
    int invalidAge;
};

class Student {
private:
    std::string name;
    int age;

public:
    void setStudent(const std::string& studentName, int studentAge) {
        // Validate name length
        if (studentName.length() < 2) {
            throw std::length_error("Name is too short");
        }

        // Validate age range
        if (studentAge < 0 || studentAge > 120) {
            throw InvalidAgeException(studentAge);
        }

        name = studentName;
        age = studentAge;
    }

    void displayInfo() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

int main() {
    Student student;

    // First attempt: Short name
    try {
        student.setStudent("A", 25);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Second attempt: Invalid age
    try {
        student.setStudent("John Doe", 150);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Successful attempt
    try {
        student.setStudent("Alice", 20);
        student.displayInfo();
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    return 0;
}

让我们分解多个 catch 块:

  1. 多个 catch 块:

    • 允许处理不同类型的异常
    • 按从最具体到最一般的顺序执行
    • 每个块可以处理特定的异常类型
  2. 异常处理策略:

    • 第一个 catch 块处理 std::length_error
    • 第二个 catch 块处理 InvalidAgeException
    • 为不同场景提供特定的错误消息

编译并运行程序:

g++ multiple_catch.cpp -o multiple_catch
./multiple_catch

示例输出:

Length Error: Name is too short
Age Error: Invalid age (150)
Name: Alice, Age: 20

关于多个 catch 块的关键点:

  • 分别处理不同的异常类型
  • 为每种异常提供特定的错误处理
  • 捕获异常的顺序很重要
  • 允许更细粒度的错误管理

使用嵌套的 try-catch 块

在这一步骤中,你将学习如何使用嵌套的 try-catch 块来处理复杂的错误场景,并提供更细粒度的异常处理。嵌套的 try-catch 块允许你在代码的不同层次处理异常。

打开 WebIDE,在 ~/project 目录下创建一个名为 nested_exceptions.cpp 的新文件:

touch ~/project/nested_exceptions.cpp

将以下代码添加到 nested_exceptions.cpp 中:

#include <iostream>
#include <stdexcept>
#include <string>

class FileReadError : public std::runtime_error {
public:
    FileReadError(const std::string& filename)
        : std::runtime_error("Error reading file"), fileName(filename) {}

    std::string getFileName() const { return fileName; }

private:
    std::string fileName;
};

class DataProcessor {
public:
    void processFile(const std::string& filename) {
        try {
            // Simulate file reading
            if (filename.empty()) {
                throw FileReadError("Empty filename");
            }

            std::cout << "Reading file: " << filename << std::endl;

            try {
                // Simulate data processing
                validateData(filename);
            }
            catch (const std::runtime_error& e) {
                std::cout << "Inner try-catch: Data validation error" << std::endl;
                std::cout << "Error details: " << e.what() << std::endl;

                // Rethrow the exception to outer catch block
                throw;
            }
        }
        catch (const FileReadError& e) {
            std::cout << "Outer try-catch: File read error" << std::endl;
            std::cout << "Filename: " << e.getFileName() << std::endl;
        }
        catch (...) {
            std::cout << "Caught unknown exception" << std::endl;
        }
    }

private:
    void validateData(const std::string& filename) {
        // Simulate data validation
        if (filename == "corrupt.txt") {
            throw std::runtime_error("Corrupt file data");
        }
        std::cout << "Data validation successful" << std::endl;
    }
};

int main() {
    DataProcessor processor;

    // Scenario 1: Empty filename
    std::cout << "Scenario 1: Empty Filename" << std::endl;
    processor.processFile("");

    // Scenario 2: Corrupt file
    std::cout << "\nScenario 2: Corrupt File" << std::endl;
    processor.processFile("corrupt.txt");

    // Scenario 3: Valid file
    std::cout << "\nScenario 3: Valid File" << std::endl;
    processor.processFile("data.txt");

    return 0;
}

让我们分解嵌套的 try-catch 块:

  1. 外层 try-catch 块:

    • 处理文件级别的异常
    • 捕获 FileReadError 和其他潜在错误
  2. 内层 try-catch 块:

    • 处理数据处理的特定异常
    • 可以将异常重新抛出到外层的 catch 块
  3. 捕获所有异常的处理器 (catch (...)):

    • 捕获任何未预期的异常
    • 提供最后一层的错误处理

编译并运行程序:

g++ nested_exceptions.cpp -o nested_exceptions
./nested_exceptions

示例输出:

Scenario 1: Empty Filename
Outer try-catch: File read error
Filename: Empty filename

Scenario 2: Corrupt File
Reading file: corrupt.txt
Inner try-catch: Data validation error
Error details: Corrupt file data

Scenario 3: Valid File
Reading file: data.txt
Data validation successful

关于嵌套 try-catch 块的关键点:

  • 提供多层次的异常处理
  • 允许更详细的错误管理
  • 可以将异常重新抛出到外层的 catch 块
  • 适用于复杂的错误场景

总结

在本实验中,你学习了 C++ 中异常处理的基本概念。你从理解如何使用 throwtrycatch 关键字抛出和捕获基本异常开始。你探索了使用 std::runtime_error 异常类来处理除以零错误。此外,你还学习了如何在 catch 块中使用 e.what() 函数访问错误消息。这些技术提供了一种优雅处理运行时错误并防止程序崩溃的方式,使你能够编写更健壮和可靠的 C++ 应用程序。

您可能感兴趣的其他 C++ 教程