文章目录
基本结构关键要点:示例讲解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: 20Caught 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
结构提供了一种优雅的错误处理机制,允许程序在运行时动态应对异常情况而不至于崩溃。通过自定义异常类、捕获不同类型的异常、甚至捕获所有异常,开发者可以为程序提供更健壮的错误处理。