异常处理
异常是程序在执行期间产生的问题。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 <iostream>
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)]
使用方式则是通过继承和重载exception类来定义新的异常,下面实例演示了如何使用std::exception类
#include <iostream>
#include <exception>
#include <vector>
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 <iostream>
#include <assert.h>
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用于检查产品上线前错误以及修复代码,生产环境不使用。用来检查非法情况而不是错误情况的,用来帮开发者快速定位问题的位置。
- 异常捕获用于处理不可控制的错误,生产环境可使用。用于对程序发生异常情况的处理,增强程序的健壮性和容错性。
老规矩,有用二连,感谢大家阅读。