跳到主要内容

C++ catch块

什么是catch块

在C++中,异常处理是通过try-catch机制实现的。其中,catch块是用来捕获并处理在相关联的try块中发生的异常的代码块。当try块中抛出异常时,程序会寻找匹配的catch块来处理该异常。

catch块的基本语法如下:

cpp
try {
// 可能引发异常的代码
} catch (exceptionType parameter) {
// 处理异常的代码
}

catch块的工作原理

当程序执行到try块中的代码时,如果发生了异常,程序会立即停止try块中的执行,并开始查找匹配的catch块。匹配规则基于异常的类型,如果找到匹配的catch块,则执行该块中的代码;如果没有找到匹配的catch块,异常会继续向上传播。

基本示例

下面是一个简单的示例,展示了catch块的基本用法:

cpp
#include <iostream>
using namespace std;

int main() {
try {
cout << "尝试执行可能引发异常的代码" << endl;
throw 20; // 抛出int类型异常
} catch (int e) {
cout << "捕获到int类型异常,值为: " << e << endl;
}
cout << "程序继续执行" << endl;
return 0;
}

输出:

尝试执行可能引发异常的代码
捕获到int类型异常,值为: 20
程序继续执行

在这个例子中:

  1. 程序首先执行try块中的代码
  2. 当遇到throw 20语句时,抛出一个int类型的异常
  3. 程序查找匹配的catch块,找到接受int类型参数的catch
  4. 执行该catch块中的代码,打印异常值
  5. 异常处理完成后,程序继续在try-catch结构后执行

catch块的多种形式

1. 捕获特定类型异常

cpp
try {
// 可能抛出不同类型异常的代码
} catch (int e) {
// 处理int类型异常
} catch (double e) {
// 处理double类型异常
} catch (char e) {
// 处理char类型异常
}

2. 使用省略号捕获所有异常

cpp
try {
// 可能抛出异常的代码
} catch (...) {
// 处理任何类型的异常
}
警告

虽然catch(...)可以捕获任何异常,但它无法获取关于异常的具体信息,因此应该谨慎使用。通常它被用作最后一个catch块,作为处理未预期异常的保底方案。

3. 捕获对象引用

cpp
try {
// 可能抛出异常的代码
} catch (const exception& e) {
// 通过引用处理异常对象
cout << e.what() << endl;
}

异常对象和异常层次结构

C++标准库提供了一个层次化的异常类结构,基类是std::exception。捕获异常时,可以利用这种层次结构捕获特定类或其子类的异常。

cpp
#include <iostream>
#include <exception>
#include <stdexcept>

using namespace std;

int main() {
try {
throw runtime_error("发生运行时错误");
} catch (const runtime_error& e) {
cout << "捕获到runtime_error: " << e.what() << endl;
} catch (const exception& e) {
cout << "捕获到其他exception: " << e.what() << endl;
}
return 0;
}

输出:

捕获到runtime_error: 发生运行时错误
备注

catch块的匹配顺序是从上到下的,应该将更具体的异常类型(子类)放在前面,基类放在后面,否则基类会"屏蔽"子类异常的捕获。

异常的重新抛出

在某些情况下,catch块可能需要在处理异常后重新抛出,以便让外层catch块也能处理这个异常。这可以通过不带参数的throw语句实现:

cpp
#include <iostream>
using namespace std;

void function() {
try {
throw "异常发生";
} catch (const char* e) {
cout << "在function()中捕获异常: " << e << endl;
throw; // 重新抛出当前异常
}
}

int main() {
try {
function();
} catch (const char* e) {
cout << "在main()中捕获异常: " << e << endl;
}
return 0;
}

输出:

在function()中捕获异常: 异常发生
在main()中捕获异常: 异常发生

实际应用案例

文件操作异常处理

cpp
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

void readFile(const string& fileName) {
ifstream file;
try {
file.open(fileName);
if (!file) {
throw runtime_error("无法打开文件");
}

string line;
while (getline(file, line)) {
cout << line << endl;
}

file.close();
} catch (const runtime_error& e) {
cout << "文件读取错误: " << e.what() << endl;
// 可以执行一些清理工作
if (file.is_open()) {
file.close();
}
} catch (...) {
cout << "发生未知异常" << endl;
if (file.is_open()) {
file.close();
}
}
}

int main() {
readFile("不存在的文件.txt");
return 0;
}

输出:

文件读取错误: 无法打开文件

自定义异常类

cpp
#include <iostream>
#include <exception>
#include <string>

using namespace std;

// 自定义异常类
class DatabaseException : public exception {
private:
string errorMessage;

public:
DatabaseException(const string& message) : errorMessage(message) {}

virtual const char* what() const noexcept override {
return errorMessage.c_str();
}
};

void connectToDatabase(const string& connectionString) {
if (connectionString.empty()) {
throw DatabaseException("连接字符串不能为空");
}

// 假设这里有连接数据库的代码
throw DatabaseException("数据库连接失败");
}

int main() {
try {
connectToDatabase("");
} catch (const DatabaseException& e) {
cout << "数据库错误: " << e.what() << endl;
} catch (const exception& e) {
cout << "其他标准异常: " << e.what() << endl;
} catch (...) {
cout << "未知异常" << endl;
}

return 0;
}

输出:

数据库错误: 连接字符串不能为空

函数try块

C++还提供了一种特殊形式的try块,称为函数try块,主要用于构造函数的异常处理:

cpp
#include <iostream>
using namespace std;

class Resource {
public:
Resource() {
cout << "Resource构造函数" << endl;
throw runtime_error("Resource初始化失败");
}
};

class MyClass {
private:
Resource* resource;

public:
// 函数try块
MyClass() try : resource(new Resource()) {
cout << "MyClass构造函数体" << endl;
} catch (const exception& e) {
cout << "在MyClass构造函数捕获异常: " << e.what() << endl;
delete resource; // 清理资源
throw; // 重新抛出异常
}

~MyClass() {
delete resource;
}
};

int main() {
try {
MyClass obj;
} catch (const exception& e) {
cout << "在main()中捕获异常: " << e.what() << endl;
}

return 0;
}

输出:

Resource构造函数
在MyClass构造函数捕获异常: Resource初始化失败
在main()中捕获异常: Resource初始化失败

catch块的性能考虑

异常处理提供了处理错误的强大方式,但它也有一些性能上的考虑:

  1. 异常处理机制会在编译时增加代码大小
  2. 异常的抛出和捕获会有一定的运行时开销
  3. 在性能关键的代码中,可能需要考虑使用条件检查而非异常处理
提示

一般原则是:使用异常处理真正的异常情况,而不是用作常规控制流。

总结

C++的catch块是异常处理机制中的核心组成部分,它允许程序捕获并处理运行时发生的错误。正确使用catch块可以使程序更加健壮和可维护:

  • 可以捕获特定类型的异常或使用catch(...)捕获所有异常
  • catch块的匹配顺序是从上到下的,应将更具体的异常类型放在前面
  • 可以在catch块中重新抛出异常
  • 可以自定义异常类来传递更多错误信息
  • 函数try块提供了特殊的构造函数异常处理方式

练习

  1. 编写一个程序,使用多个catch块捕获不同类型的异常(int, double, string)。
  2. 创建一个自定义异常类,继承自std::exception,并重写what()方法。
  3. 实现一个简单的计算器程序,对除以零、无效输入等情况使用异常处理。
  4. 编写一个程序,演示异常的重新抛出。
  5. 创建一个使用函数try块的类,并在初始化列表中处理可能发生的异常。

延伸阅读

  • C++ 标准库的异常类层次结构
  • noexcept说明符与异常规范
  • 异常安全性的等级(基本保证、强异常保证和无异常保证)
  • RAII(Resource Acquisition Is Initialization)设计模式与异常安全

通过理解和熟练使用catch块,你将能够编写更加健壮和可靠的C++程序,有效地处理各种错误情况。