C++ catch块
什么是catch块
在C++中,异常处理是通过try-catch
机制实现的。其中,catch
块是用来捕获并处理在相关联的try
块中发生的异常的代码块。当try
块中抛出异常时,程序会寻找匹配的catch
块来处理该异常。
catch块的基本语法如下:
try {
// 可能引发异常的代码
} catch (exceptionType parameter) {
// 处理异常的代码
}
catch块的工作原理
当程序执行到try
块中的代码时,如果发生了异常,程序会立即停止try
块中的执行,并开始查找匹配的catch
块。匹配规则基于异常的类型,如果找到匹配的catch
块,则执行该块中的代码;如果没有找到匹配的catch
块,异常会继续向上传播。
基本示例
下面是一个简单的示例,展示了catch
块的基本用法:
#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
程序继续执行
在这个例子中:
- 程序首先执行
try
块中的代码 - 当遇到
throw 20
语句时,抛出一个int类型的异常 - 程序查找匹配的
catch
块,找到接受int类型参数的catch
块 - 执行该
catch
块中的代码,打印异常值 - 异常处理完成后,程序继续在
try-catch
结构后执行
catch块的多种形式
1. 捕获特定类型异常
try {
// 可能抛出不同类型异常的代码
} catch (int e) {
// 处理int类型异常
} catch (double e) {
// 处理double类型异常
} catch (char e) {
// 处理char类型异常
}
2. 使用省略号捕获所有异常
try {
// 可能抛出异常的代码
} catch (...) {
// 处理任何类型的异常
}
虽然catch(...)
可以捕获任何异常,但它无法获取关于异常的具体信息,因此应该谨慎使用。通常它被用作最后一个catch块,作为处理未预期异常的保底方案。
3. 捕获对象引用
try {
// 可能抛出异常的代码
} catch (const exception& e) {
// 通过引用处理异常对象
cout << e.what() << endl;
}
异常对象和异常层次结构
C++标准库提供了一个层次化的异常类结构,基类是std::exception
。捕获异常时,可以利用这种层次结构捕获特定类或其子类的异常。
#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
语句实现:
#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()中捕获异常: 异常发生
实际应用案例
文件操作异常处理
#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;
}
输出:
文件读取错误: 无法打开文件
自定义异常类
#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块,主要用于构造函数的异常处理:
#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块的性能考虑
异常处理提供了处理错误的强大方式,但它也有一些性能上的考虑:
- 异常处理机制会在编译时增加代码大小
- 异常的抛出和捕获会有一定的运行时开销
- 在性能关键的代码中,可能需要考虑使用条件检查而非异常处理
一般原则是:使用异常处理真正的异常情况,而不是用作常规控制流。
总结
C++的catch块是异常处理机制中的核心组成部分,它允许程序捕获并处理运行时发生的错误。正确使用catch块可以使程序更加健壮和可维护:
- 可以捕获特定类型的异常或使用
catch(...)
捕获所有异常 - catch块的匹配顺序是从上到下的,应将更具体的异常类型放在前面
- 可以在catch块中重新抛出异常
- 可以自定义异常类来传递更多错误信息
- 函数try块提供了特殊的构造函数异常处理方式
练习
- 编写一个程序,使用多个catch块捕获不同类型的异常(int, double, string)。
- 创建一个自定义异常类,继承自std::exception,并重写what()方法。
- 实现一个简单的计算器程序,对除以零、无效输入等情况使用异常处理。
- 编写一个程序,演示异常的重新抛出。
- 创建一个使用函数try块的类,并在初始化列表中处理可能发生的异常。
延伸阅读
- C++ 标准库的异常类层次结构
- noexcept说明符与异常规范
- 异常安全性的等级(基本保证、强异常保证和无异常保证)
- RAII(Resource Acquisition Is Initialization)设计模式与异常安全
通过理解和熟练使用catch块,你将能够编写更加健壮和可靠的C++程序,有效地处理各种错误情况。