• C++异常处理和断言


    异常处理

    异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw

    • **throw:**当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
    • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获
    • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
    try
    {
       // 保护代码
    }catch( ExceptionName e1 )
    {
       // catch 块
    }catch( ExceptionName e2 )
    {
       // catch 块
    }catch( ExceptionName eN )
    {
       // catch 块
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    try可以理解为是你想要进行的操作,而操作过程中出现异常(比如除法操作除到了0)。这个时候就需要捕获出现的异常。

    那么这个异常是怎么产生的呢。

    是通过自己使用throw关键字抛出的。下面来看一个例子。

    #include 
    using namespace std;
     
    double division(int a, int b)
    {
       if( b == 0 )
       {
          throw "Division by zero condition!";
       }
       return (a/b); 
    }
     
    int main ()
    {
       int x = 50;
       int y = 0;
       double z = 0;
     
       try {
         z = division(x, y);
         cout << z << endl;
       }catch (const char* msg) {
         cerr << msg << endl;
       }
     
       system("pause");
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    当你使用除法操作,分母为0时,调用throw关键字,抛出异常。

    再用catch捕捉异常。

    最后当尝试执行try中的代码出现异常时,则会被捕捉。

    当然上述也出现了一个问题,那就是如果有多种不同的异常,那么我们怎么通过catch捕捉不同的异常呢?

    标准异常

    C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eL5n14kb-1666243803703)(exceptions_in_cpp.png)]

    异常描述
    std::exception该异常是所有标准 C++ 异常的父类。
    std::bad_alloc该异常可以通过 new 抛出。
    std::bad_cast该异常可以通过 dynamic_cast 抛出。
    std::bad_typeid该异常可以通过 typeid 抛出。
    std::bad_exception这在处理 C++ 程序中无法预期的异常时非常有用。
    std::logic_error理论上可以通过读取代码来检测到的异常。
    std::domain_error当使用了一个无效的数学域时,会抛出该异常。
    std::invalid_argument当使用了无效的参数时,会抛出该异常。
    std::length_error当创建了太长的 std::string 时,会抛出该异常。
    std::out_of_range该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator
    std::runtime_error理论上不可以通过读取代码来检测到的异常。
    std::overflow_error当发生数学上溢时,会抛出该异常。
    std::range_error当尝试存储超出范围的值时,会抛出该异常。
    std::underflow_error当发生数学下溢时,会抛出该异常。

    使用方式则是通过继承和重载exception类来定义新的异常,下面实例演示了如何使用std::exception类

    #include 
    #include 
    #include 
    using namespace std;
     
    struct MyException : public exception
    {
      const char * what () const throw ()
      {
        return "C++ Exception";
      }
    };
     
    int main()
    {
      vector<int> a(2);
      try
      {
        throw MyException();
        // a.at(10);  标准异常
      }
      catch(MyException& e)
      {
        std::cout << "MyException caught" << std::endl;
        std::cout << e.what() << std::endl;
      }
      catch(std::out_of_range& exc)
      {
        cout<<"超出范围"<<endl;
        cout<<exc.what()<<endl;
      }
    
      system("pause");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    这里采用了自定义类型和标桩异常类型。

    • 首先是自定义,通过继承的方式继承exception,然后采用定义throw异常的结果。
    • 还有就是标准异常,这里通过vector索引了一个超出范围的值,抛出了异常。

    断言

    除了异常处理之外,断言也是经常使用的方式。

    assert不仅仅是个报错函数,还是一个宏

    if(假设成立)
    {
         程序正常运行;
    }
    else
    {
          报错&&终止程序!(避免由程序运行引起更大的错误)  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用方法如下

    #include "assert.h" 
    void assert( int expression );
    
    • 1
    • 2

    assert 的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。

    #include 
    #include 
    using namespace std;
    
    int main()
    {
        int a = 10;
        int b = 0;
    
        assert( b != 0 );
    
        cout << "nihao" << endl;
    
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    同样举个例子,上述b除数不能为0,否则触发断言,后面的输出语句将不会执行。

    因为断言是宏,所以在编译阶段,是不会报错的,只有在代码运行阶段,且运行到该断言处,才会出现错误。为此C++11推出了静态断言。

    static_assert(bool_constexpr, message) //从C++11起
    
    static_assert(bool_constexpr) //从C++17起
    
    • 1
    • 2
    • 3

    第二种断言定义方式并不意味着前述的assert失去了作用,因为static_assert的第一个参数必须是常量表达式,这样才能实现编译时断言。当我们只需要运行时断言时,还是得使用assert而不是static_assert。另外,第二个参数message必须是字符串字面值,这一点在使用的时候也需要注意。
    static_assert的另一个好处是,因为是编译期断言,不生成目标代码,因此使用static_assert不会造成任何运行期性能损失。

    异常捕获和断言的对比

    首先是使用场景

    • 异常捕获:内存不足,运行时异常,用户输入错误,数据库连接错误,IO错误。
    • 断言:写该代码的人造成的错误,例如指针为空

    一般生产环境不使用assert,因为会造成崩溃。

    而异常捕获可用于给用户反馈,例如在HTTP服务中,用户发送的消息格式不正确(JSON格式非法),数据库连接错误,这时候catch捕获对应的exception,并发送HTTP响应消息给客户端,返回错误码。

    总结
    • assert用于检查产品上线前错误以及修复代码,生产环境不使用。用来检查非法情况而不是错误情况的,用来帮开发者快速定位问题的位置。
    • 异常捕获用于处理不可控制的错误,生产环境可使用。用于对程序发生异常情况的处理,增强程序的健壮性和容错性。

    老规矩,有用二连,感谢大家阅读。

  • 相关阅读:
    【原创教程】EPLAN如何制作专属的封面
    看完这一篇教你学会Zookeeper和Dubbo,安装下载使用快速上手
    获取购买到的商品订单列表API接口
    mybatis配置文件
    (12)yolov5+deepsort 应用实例之跟踪目标起始时间并记录结果图像
    C++中函数原型和函数定义
    国家高新技术企业的好处
    Django与Ajax
    权威推荐!腾讯安全DDoS边缘安全产品获国际研究机构Omdia认可
    AMD锐龙R5600G&VEGA7 GPU环境搭建
  • 原文地址:https://blog.csdn.net/suren_jun/article/details/127425309