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 块
}

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;
}

当你使用除法操作,分母为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;
}

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

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

断言

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

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

if(假设成立)
{
     程序正常运行;
}
else
{
      报错&&终止程序!(避免由程序运行引起更大的错误)  
}

使用方法如下

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

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;
}

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

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

static_assert(bool_constexpr, message) //从C++11起

static_assert(bool_constexpr) //从C++17起

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

异常捕获和断言的对比

首先是使用场景

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

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

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

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

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

你可能感兴趣的:(C++,c++,开发语言)