异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
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;
}
这里采用了自定义类型和标桩异常类型。
除了异常处理之外,断言也是经常使用的方式。
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不会造成任何运行期性能损失。
首先是使用场景
一般生产环境不使用assert,因为会造成崩溃。
而异常捕获可用于给用户反馈,例如在HTTP服务中,用户发送的消息格式不正确(JSON格式非法),数据库连接错误,这时候catch捕获对应的exception,并发送HTTP响应消息给客户端,返回错误码。
老规矩,有用二连,感谢大家阅读。