行业知识
突破编程
面试题 1 :什么是异常处理?为什么需要它?
在C++��,异常处理是一种处理程序运行时错误的机制。它允许程序员在程序的某个部分中定义和处理可能会出现的异常情况,即“异常”。这些异常情况通常是由错误条件、非法操作或其他意外情况引起的。
()异常处理主要包括以下三个部分:
(1)抛出异常(Throw): 当程序遇到错误或异常情况时,它会使用throw关键字抛出一个异常。throw关键字后面通常跟着一个值,这个值可以是任何数据类型,它包含了关于异常的信息。
()(2)捕获异常(Catch): catch 关键字用于捕获异常。它定义了一个或多个代码块,每个代码块处理特定类型的异常。当异常被抛出时,程序会查找匹配的 catch 块来处理该异常。
(3)异常规格(Exception Specification): 这是 C++98 中引入的一个特性,用于声明函数可能抛出的异常类型。然而,在 C++11 及以后的版本中,异常规格已被弃用,推荐使用 noexcept 关键字来声明函数是否抛出异常。
为什么需要异常处理:
(1)错误处理: 异常处理提供了一种统一的机制来处理错误。相比于传统的错误代码返回值,异常处理更加直观和易于维护。
(2)资源管理: 当对象在创建或使用过程中分配了资源(如内存、文件句柄等),并且这些资源需要在对象生命周期结束时释放时,异常处理可以确保在发生异常时这些资源得到正确释放,防止资源泄漏。
(3)代码清晰: 通过将错误处理代码从正常业务逻辑代码中分离出来,异常处理可以保持代码更加清晰和易读。
(4)错误传播: 异常可以被抛出并向上传播到调用栈的更高级别,这样可以在更高层次上处理错误,而不是在每个可能出错的地方都进行错误检查。
(5)程序健壮性: 通过妥善处理异常,可以提高程序的健壮性和稳定性,减少程序崩溃或未定义行为的可能性。
总体而言,异常处理是 C++ 中一个强大的错误处理机制,它有助于编写更加健壮、清晰和可维护的代码。
面试题 2 :可以抛出什么类型的异常?
在 C++ 中,可以抛出任何类型的异常。这包括基本数据类型(如int、char、bool等)、复合数据类型(如结构体、类等)、指针类型,甚至是异常类型本身(即派生自 std::exception 的异常类)。
通常会选择抛出派生自 std::exception 的异常类型,因为这样做可以提供更多的上下文信息,并允许使用 catch 块捕获所有类型的异常(通过捕获 std::exception 类型的引用或指针)。
下面是一些示例:
(1)抛出基本数据类型:
throw 1; // 抛出一个整数
(2)抛出复合数据类型:
struct MyError { int errorCode; const char* errorMessage; }; void funcThrowError() { MyError error = {100, "Internal Server Error"}; throw error; // 抛出一个自定义的结构体 } (3)抛出指针类型:
void* ptr = new int(2); throw ptr; // 抛出一个指针
(4)抛出派生自std::exception的异常类型:
#include #include class MyException : public std::exception { public: const char* what() const throw() { return "MyException occurred"; } }; void funcThrowError() { throw MyException(); // 抛出一个自定义异常类 } 在实践中,建议使用派生自 std::exception 的异常类型,因为它们提供了 what() 成员函数,该函数返回一个描述异常的字符串,这对于调试和错误报告非常有用。
面试题 3 :什么是异常规格(Exception Specification)?
异常规格(Exception Specification)是 C++98 中引入的一种特性,用于声明函数可能抛出的异常类型。它允许程序员指定函数可能抛出的异常类型,并在函数实现中确保这些约束被遵守。然而,需要注意的是,在 C++11 及以后的版本中,异常规格已被弃用,并推荐使用 noexcept 关键字来声明函数是否抛出异常。
异常规格(Exception Specification)用于声明函数可能抛出的异常类型。在函数声明中,可以通过在函数参数列表后面添加 throw(exception_type_list) 来指定函数可能抛出的异常类型。exception_type_list 是一个逗号分隔的异常类型列表。
如下为样例代码:
void func() throw(int, std::exception) { // 函数实现 if (/* some condition */) { throw std::runtime_error("An error occurred"); } } 这表示 func 函数可能抛出 int 类型或 std::exception 类型及其派生类型的异常。如果函数抛出了其他类型的异常,程序将调用 std::unexpected() 函数,除非该函数被 std::set_unexpected() 函数覆盖。
需要注意的是,基于类型的异常规格并不要求函数实际抛出列表中的异常类型,它只是一个声明,用于告知编译器和调用者该函数可能抛出的异常类型。
面试题 4 :说一下你对 noexcept 关键字的理解。
noexcept 是在 C++11 引入的关键字,它提供了一种更简洁且更强大的方式来指定函数是否抛出异常。noexcept关键字可以接受一个可选的常量表达式,用于指示函数是否可能抛出异常。
如下为样例代码:
void func1() noexcept { // 函数实现,保证不抛出异常 } void func2() noexcept(true) { // 函数实现,保证不抛出异常 } void func3() noexcept(false) { // 函数实现,可能抛出异常 } 使用 noexcept 关键字的好处是,如果函数声明为 noexcept 且实际上抛出了异常,编译器会保证调用 std::terminate() 来结束程序,而不是调用 std::unexpected()。此外,noexcept 还允许编译器进行更多的优化,因为它知道函数不会抛出异常。
因此,在现代 C++ 编程中,推荐使用 noexcept 关键字来替代 C++98 中的异常规格。
面试题 5 :如何重新抛出当前捕获的异常?
在C++中,如果在一个 catch 块中捕获了一个异常,并且想要在当前函数或更高级别的函数中重新抛出它,可以使用 throw; 语句(没有带任何参数)来重新抛出该异常。这允许异常继续向上传播,直到它被另一个 catch 块捕获或程序终止。
如下为样例代码:
#include #include void handleException() {try {// ... 这里可能会抛出异常的代码 ... throw std::runtime_error("An error occurred");}catch (const std::exception& e) {// 处理异常,例如记录日志 std::cerr try {handleException();}catch (const std::exception& e) {// 在主函数中捕获并处理异常 std::cerr // 处理原始异常 // ... // 抛出一个新的异常,包含原始异常的信息 throw std::runtime_error("An error occurred while processing: " + e.what()); }