您的位置:首页 > 汽车 > 新车 > C++基础与深度解析 | 异常处理 | 枚举与联合 | 嵌套类与局部类 | 嵌套名字空间与匿名名字空间 | 位域与volatile关键字

C++基础与深度解析 | 异常处理 | 枚举与联合 | 嵌套类与局部类 | 嵌套名字空间与匿名名字空间 | 位域与volatile关键字

2024/12/28 10:07:36 来源:https://blog.csdn.net/zwcslj/article/details/139552638  浏览:    关键词:C++基础与深度解析 | 异常处理 | 枚举与联合 | 嵌套类与局部类 | 嵌套名字空间与匿名名字空间 | 位域与volatile关键字

文章目录

  • 一、异常处理
  • 二、枚举与联合
  • 三、嵌套类与局部类
  • 四、嵌套名字空间与匿名名字空间
  • 五、位域与volatile关键字

一、异常处理

  异常处理用于处理程序在调用过程中的非正常行为。

  • 传统的处理方法:传返回值表示函数调用是否正常结束。

    例如,返回 0 表示成功,非 0 表示失败。这种方法的缺点是函数的返回值被错误处理逻辑占用,不能用于其他目的。

    这种方法有局限性

  • C++ 中的处理方法:通过关键字 try/catch/throw 引入异常处理机制

    通过 try/catch/throw 引入了结构化的错误处理机制,使得错误处理逻辑与正常逻辑分离,提高了代码的可读性和可维护性。

    C++异常处理的关键组件

    • throw关键字

      throw 关键字用于抛出一个异常。它后面可以跟任意类型的表达式,该表达式的结果将被用作异常对象

    • try块

      try块包含了可能会抛出异常的代码。如果 try 块中的代码抛出了异常,那么与之匹配的 catch 块将被执行。

    • catch块

      catch块用于捕获并处理异常。可以有多个 catch 块来捕获不同类型的异常。

    • 异常类

      C++标准库中定义了一些基本的异常类,如 std::exceptionstd::runtime_errorstd::logic_error

异常触发时的系统行为—栈展开

  • 抛出异常后续的代码不会被执行

    一旦抛出异常,throw 语句之后的代码将不会被执行。控制流会立即转移到异常处理机制。

  • 局部对象会按照构造相反的顺序自动销毁

    在栈展开过程中,局部对象(包括由 new 分配的对象)会按照它们构造的相反顺序自动销毁。这是为了保证资源的正确释放,防止内存泄漏。

  • 系统尝试匹配相应的 catch 代码段

    • 如果找到匹配的 catch 块:

      • 执行 catch 块中的异常处理逻辑。
      • 异常被“捕获”后,catch 块之后的代码会继续执行。
    • 如果没有找到匹配的 catch 块:

      • 栈展开会继续进行,直到找到匹配的 catch 块或者退出当前函数。

      • 如果当前函数中没有找到匹配的

        块,栈展开会继续,直到:

        • 找到一个匹配的 catch 块。

        • 达到 main 函数。

          如果在 main 函数之前的所有函数中都没有找到匹配的 catch 块,程序将退出 main 函数。如果程序在退出 main 函数之前没有捕获到异常,std::terminate 函数将被调用。这通常会导致程序立即终止。

异常对象

  • 系统会使用抛出的异常拷贝初始化一个临时对象,称为异常对象
  • 异常对象会在栈展开过程中被保留,并最终传递给匹配的 catch 语句

try / catch语句块

  • 一个 try 语句块后面可以跟一到多个 catch 语句块(至少跟一个)

  • 每个 catch 语句块用于匹配一种类型的异常对象

    可以有多个catch块,每个用于处理不同类型的异常。

  • catch 语句块的匹配按照从上到下进行

    catch块按照它们在代码中出现的顺序(从上到下)进行匹配。一旦找到匹配的异常类型,就执行相应的catch块,忽略后面的catch块。

  • 使用 catch(…) 匹配任意异常

    catch(...)是一个通用的异常捕获器,它可以捕获任何类型的异常,包括未被前面的catch块捕获的异常。

  • 在 catch 中调用 throw 继续抛出相同的异常

    catch块中,可以使用throw;(不带参数)来重新抛出当前捕获的异常,这将导致继续搜索外层catch块。

示例:

#include <iostream>struct Str{};
struct Base{};
struct Derive : Base{};void f1()
{int x;Str obj;//throw Derive{}; //打印Derive exception is called in f2 throw Str{};  //打印exception is called in f2
}void f2()
{int x2;Str obj2;try{f1();}catch(Derive& e){std::cout << "Derive exception is called in f2 " << "\n";}catch(Base& e){std::cout << "Base exception is called in f2 " << "\n";}catch(...){std::cout << "exception is called in f2" << "\n";throw;  //重新抛出当前捕获的异常}std::cout << "other logic in f2.\n";
}void f3()
{try{f2(); }catch(Str& e){std::cout << "exception is called in f2" << "\n";}
}int main()
{f3();
}

在一个异常未处理完成时抛出新的异常会导致程序崩溃

  • 不要在析构函数或 operator delete 函数重载版本中抛出异常
  • 通常来说, catch 所接收的异常类型为引用类型

异常与构造、析构函数

  • 使用 function-try-block保护初始化逻辑

    在C++中,function-try-block允许你在函数的初始化列表和函数体中使用trycatch。这在构造函数中特别有用,因为它可以保护对象的初始化代码。

    示例:

    #include <iostream>struct Str
    {Str() { throw 100; }
    }class Resource {
    public:Resource() try : m_str(){}catch(int){std::cout << "Exception is catched at Resource::Resource" << std::endl;throw;}private:Str m_str;
    };int main()
    {try{Resource obj; }catch(int){std::cout << "Exception is catched at main" << std::endl;}
    }
    

    运行结果:

    Exception is catched at Resource::Resource
    Exception is catched at main
    
  • 在构造函数中抛出异常:

    • 已经构造的成员对象会被销毁

      如果在构造函数中抛出异常,已经构造的成员对象将按照它们构造的相反顺序自动销毁。

    • 类本身的析构函数不会被调用

      如果异常是在对象的构造过程中抛出的,并且没有被捕获,那么类的析构函数不会被调用。这是因为对象被视为未完全构造,因此析构函数不适用。

    • 局部对象的销毁

      如果对象是局部的(即在栈上),异常抛出时,局部对象会自动销毁,但不会调用其析构函数。

    • 动态分配对象的销毁

      如果对象是动态分配的(即使用new),在构造函数中抛出异常且未被捕获时,需要手动释放分配的内存,因为析构函数不会被调用。

描述函数是否会抛出异常

  • 如果函数不会抛出异常,则应表明,从而为系统提供更多的优化空间

    • C++ 98 的方式:
      • throw() :表明不会抛出异常
      • throw(int, char):表明可能抛出异常,显式给定了要抛出异常的类型
    • C++11 后的改进:
      • noexcept :表明不会抛出异常
      • noexcept(false):表明可能抛出异常,不需要显式给定会抛出哪种类型的异常
  • noexcept

    • 限定符:接收 false / true 表示是否会抛出异常
    • 操作符:接收一个表达式,根据表达式是否可能抛出异常返回 false/true
    • 在声明了 noexcept 的函数中抛出异常会导致 terminate 被调用,程序终止
    • 不作为函数重载依据,但函数指针、虚拟函数重写时要保持形式兼容

示例:

#include <iostream>void fun2()
{}void fun() noexcept(noexcept(fun2()))
{fun2();
}int main()
{std::cout << noexcept(fun()) << std::endl;
}

二、枚举与联合

三、嵌套类与局部类

四、嵌套名字空间与匿名名字空间

五、位域与volatile关键字

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com