try catch
在C++中,try
和catch
是异常处理机制的关键字。异常处理是一种处理程序中特殊情况(即异常)的机制,这些情况通常在程序正常运行时不会发生,但一旦发生就需要特殊处理。
基本结构
异常处理的基本结构如下:
try {
// 尝试执行的代码块
// 这里可能会抛出异常
}
catch (异常类型1 e) {
// 处理异常类型1的代码
}
catch (异常类型2 e) {
// 处理异常类型2的代码
}
// ... 可以有多个catch块来处理不同类型的异常
catch (...) {
// 处理所有未被前面的catch块捕获的异常
}
- try块:在
try
块中,你放置可能会抛出异常的代码。 - catch块:
catch
块用于捕获并处理try
块中抛出的异常。你可以为不同类型的异常提供不同的catch
块。如果try
块中的代码抛出了一个异常,那么与该异常类型匹配的catch
块将被执行。 - 异常类型:在
catch
关键字后面的括号中,你指定要捕获的异常的类型。这可以是任何内置类型、用户定义类型或标准库中的异常类型(如std::exception
)。 - 异常变量(可选):在异常类型后面,你可以指定一个变量名来存储捕获到的异常对象。这样,你就可以在
catch
块内部访问该异常对象的属性和方法。 - 捕获所有异常:使用三个点(
...
)作为异常类型,可以捕获所有类型的异常。这通常用作最后的catch
块,以确保所有未被特定处理的异常都能得到某种形式的处理。
抛出异常
在C++中,使用throw
关键字来抛出异常。例如:
throw std::runtime_error("发生了一个运行时错误");
这里,std::runtime_error
是一个标准库中的异常类型,但你也可以抛出任何类型的对象作为异常。
异常处理的流程
- 当在
try
块中遇到throw
语句时,程序的控制流将立即离开当前的执行路径,并查找与抛出的异常类型相匹配的catch
块。 - 如果找到了匹配的
catch
块,程序将执行该块中的代码,然后继续执行紧跟在该catch
块之后的代码(如果有的话)。 - 如果没有找到匹配的
catch
块,并且存在一个捕获所有异常的catch(...)
块,那么将执行该块。 - 如果没有找到任何匹配的
catch
块,并且也没有捕获所有异常的块,那么程序将调用std::terminate
函数并异常终止。
注意事项
- 异常处理机制不是用来处理常规错误的。它应该用于处理那些如果不加以处理就可能导致程序崩溃或产生不可预测行为的异常情况。
- 过度使用异常处理可能会使代码难以理解和维护。因此,应谨慎使用异常处理,并遵循良好的编程实践。
常见的标准异常类型
在C++中,异常类型非常多样,包括标准库定义的异常和用户自定义的异常。标准库中的异常主要定义在<stdexcept>
和<exception>
头文件中。以下是一些常见的标准异常类型:
std::exception
:所有标准异常类的基类。std::bad_alloc
:在动态内存分配失败时抛出。std::bad_cast
:在动态类型转换失败时抛出。std::bad_exception
:当异常处理函数抛出异常时抛出(不过请注意,这个异常类型在实际应用中较少使用,因为它与C++的异常处理机制的一些早期设计有关)。std::bad_typeid
:在typeid
操作符应用于null指针或空引用时抛出。std::logic_error
及其派生类:表示程序逻辑错误,如无效的参数值。这些异常通常在程序员的控制范围内,应该在开发阶段就被捕获并处理。std::runtime_error
及其派生类:表示运行时错误,如文件不存在或无法打开。这些异常通常是由外部因素引起的,程序员可能无法完全预防。
用户还可以定义自己的异常类,这些类通常从std::exception
或其派生类继承而来。
示例
当然,下面是一个简单的C++程序,展示了如何使用try
、catch
块来处理异常。
#include <iostream>
#include <stdexcept> // 包含标准异常类的头文件
int divide(int a, int b) {
if (b == 0) {
throw std::invalid_argument("Divisor cannot be zero!"); // 抛出异常
}
return a / b;
}
int main() {
int dividend = 10;
int divisor = 0; // 设置为0来触发异常
try {
int result = divide(dividend, divisor);
std::cout << "The result of the division is: " << result << std::endl;
} catch (const std::invalid_argument& e) {
// 处理特定类型的异常
std::cerr << "Caught an invalid_argument exception: " << e.what() << std::endl;
} catch (const std::exception& e) {
// 处理其他所有标准异常
std::cerr << "Caught an exception of an unexpected type: " << e.what() << std::endl;
} catch (...) {
// 捕获所有未被前面的catch块捕获的异常
std::cerr << "Unknown exception caught" << std::endl;
}
std::cout << "Program execution continues..." << std::endl;
return 0;
}
在这个例子中,我们定义了一个divide
函数,它接受两个整数作为参数,并返回它们的商。如果除数为零,函数会抛出一个std::invalid_argument
异常。
在main
函数中,我们尝试调用divide
函数,并将其结果打印到控制台。我们使用try
块来包围可能会抛出异常的代码,并使用多个catch
块来处理不同类型的异常。
- 第一个
catch
块专门处理std::invalid_argument
异常,这是我们在divide
函数中抛出的特定类型的异常。 - 第二个
catch
块处理所有从std::exception
派生的其他标准异常。由于std::invalid_argument
是从std::exception
派生的,因此这个catch
块实际上不会捕获到std::invalid_argument
异常,因为第一个catch
块已经捕获了它。但是,如果有其他类型的标准异常被抛出,这个catch
块将会捕获它们。 - 最后一个
catch
块使用三个点(...
)作为异常类型,这意味着它将捕获所有未被前面的catch
块捕获的异常。这是一个通用的异常处理程序,通常用于确保程序在未知异常发生时不会崩溃。
注意,在这个例子中,由于除数为零,程序将执行到throw std::invalid_argument("Divisor cannot be zero!");
语句,并立即跳转到与之匹配的catch
块。在这种情况下,将打印出“Caught an invalid_argument exception: Divisor cannot be zero!”消息,并且程序将继续执行,打印出“Program execution continues...”消息。
Do not communicate by sharing memory; instead, share memory by communicating.