文章目录
- 基本结构
- 关键要点:
- 示例讲解
- 1. 基本异常处理
- 2. 捕获不同类型的异常
- 3. 捕获所有类型的异常
- 4. 自定义异常类
- 程序执行流程:
- 5. 抛出和重新抛出异常
- 例外情况:异常规范(Deprecated)
- 异常处理的注意事项
- 总结
C++中的try-catch
结构用于异常处理。通过异常处理机制,程序可以在运行过程中捕获和处理错误,而不至于直接导致程序崩溃。try
语句块用于标记可能抛出异常的代码,catch
语句块用于捕获并处理这些异常。
基本结构
try {// 可能抛出异常的代码
} catch (type1 e1) {// 处理type1类型异常的代码
} catch (type2 e2) {// 处理type2类型异常的代码
} catch (...) {// 处理所有类型异常的代码(可选的,使用"..."表示捕获任意类型异常)
}
关键要点:
- try 块:包含可能抛出异常的代码。当遇到异常时,程序会跳出
try
块,进入与异常类型匹配的catch
块。 - catch 块:用于捕获在
try
块中抛出的异常,执行相应的处理代码。每个catch
块处理一种类型的异常。 - throw 语句:用来抛出异常,通知调用者发生了某种错误或特殊情况。
- …(省略号):可以用在最后一个
catch
块,用来捕获任何类型的异常。
示例讲解
1. 基本异常处理
#include <iostream>int main() {try {int a = 10;int b = 0;if (b == 0) {throw "Division by zero error!"; // 抛出异常}std::cout << "Result: " << a / b << std::endl;} catch (const char* e) { // 捕获异常std::cerr << "Caught an exception: " << e << std::endl;}return 0;
}
输出:
Caught an exception: Division by zero error!
在这个例子中,程序试图进行除法运算,但因为除数b
为零,所以通过throw
语句抛出了一个异常字符串。在catch
块中,异常被捕获并输出错误信息。
2. 捕获不同类型的异常
异常可以是任意类型的对象,如整数、字符串、类对象等。可以为不同类型的异常定义多个catch
块:
#include <iostream>int main() {try {int choice = 1;if (choice == 1) {throw 100; // 抛出整数类型异常} else if (choice == 2) {throw "String exception"; // 抛出字符串类型异常}} catch (int e) {std::cerr << "Caught an integer exception: " << e << std::endl;} catch (const char* e) {std::cerr << "Caught a string exception: " << e << std::endl;}return 0;
}
输出:
Caught an integer exception: 100
在这个例子中,choice
为1
,因此抛出了一个整数类型的异常,被第一个catch
块捕获。如果抛出的是字符串异常,则会进入第二个catch
块。
3. 捕获所有类型的异常
有时候我们不关心具体的异常类型,只想捕获所有异常。可以使用...
(省略号)来捕获所有未明确匹配的异常:
#include <iostream>int main() {try {throw 3.14; // 抛出double类型异常} catch (...) {std::cerr << "Caught an unknown exception!" << std::endl;}return 0;
}
输出:
Caught an unknown exception!
这里,catch(...)
捕获了double
类型的异常,尽管没有具体的catch
块来处理double
,但省略号可以捕获任何类型的异常。
4. 自定义异常类
通常情况下,C++异常处理不仅限于内置数据类型,还可以通过自定义异常类来传递更加详细的错误信息。我们可以定义自己的异常类,并通过throw
语句抛出它:
#include <iostream>
#include <string>// 自定义异常类
class MyException {
public:MyException(const std::string& message) : msg_(message) {}const char* what() const noexcept { return msg_.c_str(); }private:std::string msg_;
};int main() {try {throw MyException("Something went wrong!"); // 抛出自定义异常} catch (const MyException& e) {std::cerr << "Caught MyException: " << e.what() << std::endl;}return 0;
}
【代码详解】
以下是逐行讲解和注释:
#include <iostream> // 引入标准输入输出流库,用于输入输出操作
#include <string> // 引入字符串库,用于处理 std::string 类型
这两行头文件引入了标准库中的iostream
和string
。iostream
用于输入输出操作,string
用于字符串操作。
// 自定义异常类
class MyException {
public:// 构造函数,接收一个字符串参数并将其存储在私有成员变量中MyException(const std::string& message) : msg_(message) {}// what()函数,返回异常信息的C风格字符串(const char*),并使用noexcept确保该函数不会抛出异常const char* what() const noexcept { return msg_.c_str(); }private:std::string msg_; // 用于存储异常消息的私有成员变量
};
- 自定义异常类:创建了一个名为
MyException
的类,它代表一个自定义的异常类。异常类可以存储异常信息,并提供一个方法返回异常信息。 - 构造函数:
MyException(const std::string& message)
是构造函数,它接受一个std::string
类型的异常信息,并将其存储在类的私有成员变量msg_
中。 - what()方法:
what()
方法返回一个const char*
(C风格的字符串),用于返回异常信息。该方法标记为noexcept
,表示它不会抛出异常。 - 私有成员变量
msg_
:用于存储传入的异常消息。
int main() {
主函数的入口点,程序从这里开始执行。
try {// 抛出自定义异常,包含消息 "Something went wrong!"throw MyException("Something went wrong!"); }
- try 块:尝试执行可能抛出异常的代码。如果发生异常,程序会跳到对应的
catch
块。 - throw:抛出一个
MyException
类型的异常对象,异常消息为"Something went wrong!"
。这会立即跳出try
块,进入catch
块。
catch (const MyException& e) {// 捕获 MyException 类型的异常,并输出异常信息std::cerr << "Caught MyException: " << e.what() << std::endl;}
- catch 块:当抛出
MyException
类型的异常时,异常会被捕获在catch
块中。捕获的是const MyException&
,通过引用避免拷贝,同时使用const
保护数据不被修改。 - 输出异常信息:
e.what()
调用的是自定义异常类中的what()
方法,用于获取异常信息。std::cerr
用于向标准错误流输出捕获的异常消息。
return 0; // 返回0表示程序正常结束
}
程序正常执行完毕后返回0
。
程序执行流程:
try
块中的代码试图抛出一个MyException
异常。- 异常被
throw
语句抛出,并被catch
捕获。 catch
块输出异常信息,程序继续执行并最终正常退出。
输出:
Caught MyException: Something went wrong!
在这个例子中,我们定义了一个简单的异常类MyException
,并在try
块中抛出该类型的异常。在catch
块中,捕获并输出了该异常的错误信息。
5. 抛出和重新抛出异常
在某些情况下,捕获异常后可能希望将异常继续向上抛出。可以使用throw
重新抛出当前捕获的异常。
#include <iostream>void test() {try {throw 20; // 抛出异常} catch (int e) {std::cout << "Caught in test: " << e << std::endl;throw; // 重新抛出异常}
}int main() {try {test();} catch (int e) {std::cout << "Caught in main: " << e << std::endl;}return 0;
}
输出:
Caught in test: 20
Caught in main: 20
在这个例子中,test()
函数捕获了异常后,使用throw
语句将其重新抛出。然后在main()
函数中,重新捕获了该异常并处理。
例外情况:异常规范(Deprecated)
在C++98中,函数可以指定它们会抛出哪些异常类型。但自C++11以来,异常规范已被弃用,并引入了新的noexcept
关键字,表示函数不会抛出异常。
void foo() noexcept {// 这个函数保证不会抛出任何异常
}
如果在noexcept
函数中抛出异常,程序将调用std::terminate
直接终止。
异常处理的注意事项
-
不要滥用异常:异常处理应该用于无法通过常规手段预测和处理的异常情况。不要使用异常处理来控制正常的程序流,这会降低代码效率并导致混乱。
-
性能问题:异常的抛出和捕获是有开销的,尤其是在嵌套的
try-catch
结构中。因此,在高性能代码中,尽量避免在频繁的操作中使用异常处理。 -
捕获异常时使用引用:如果捕获的是类类型异常,最好使用引用(如
const MyException& e
)来避免不必要的对象拷贝。 -
资源释放与异常安全:在处理异常时,特别要注意确保资源(如内存、文件句柄等)能够正确释放,通常通过RAII(资源获取即初始化)模式来保障这一点。
总结
C++中的try-catch
结构提供了一种优雅的错误处理机制,允许程序在运行时动态应对异常情况而不至于崩溃。通过自定义异常类、捕获不同类型的异常、甚至捕获所有异常,开发者可以为程序提供更健壮的错误处理。