在C语言中,我们常见的处理错误的方式就是assert,不过其方式较为暴力,会直接终止程序。
在Linux操作系统中,退出码也是常见的记录错误信息的方式,不过需要我们自己去查找错误原因
C++在C语言的基础上,觉得单单返回退出码较为单调,信息不足,所以提出了异常的概念
异常的概念:
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误,或者想让外部函数处理该错误时,就可以抛异常。让函数的直接或间接调用者处理这个错误
异常的三个关键字:
throw
:当问题出现时,可以通过throw关键字抛出,抛出的异常可以是任何类型try
:try块中的代码,代表其可能会抛出异常,需要进行捕获,其后通常跟着一个或者多个catch块catch
:通过catch捕获可能抛出的异常,如果捕捉成功,即可执行catch块中的代码(处理方式)如果一个块抛出一个异常,就需要try,catch语句捕获。try块中放置可能抛出异常的代码,catch块中放置处理方式等等。try块中的代码被称为保护代码。
接下来我们使用除零错误
演示以上三个关键字的使用
#include
using namespace std;
//除法函数
double division(double a, double b)
{
if (b == 0)//除零异常,抛出异常
throw "Divide by zero error";
return a / b;
}
//除法函数调用者
void Func()
{
while (1)
{
double a, b;
cin >> a >> b;
cout << division(a, b) << endl;;
cout << "void Func()" << endl;
}
}
//主函数
int main()
{
//捕获
try
{
Func();
}
catch (const char*str)
{
cout << str << endl;
}
return 0;
}
当没有出现除零异常时,while循环一直进行,“void Func()”也成功打印
但出现除零异常时,代码运行直接跳转到了main函数的catch部分,并且str成功捕捉到了throw的字符串
与该对象类型匹配且离抛出异常位置最近
的那一个catch(...)
可以捕获任意类型的异常,…是可变参数包。但问题是不知道异常错误是什么多态
,即抛出派生类对象,然后使用基类捕获
,这个在实际中非常实用。上述沿着调用链查找匹配的catch语句的过程称为栈展开
。所以实际中,为了防止未知异常终止程序,我们可以在最后加上catch(…),捕获任意类型的异常
异常没有被捕获
捕获匹配原则
还是除零异常
#include
using namespace std;
//除法函数
double division(double a, double b)
{
if (b == 0)
throw "Divide by zero error";
return a / b;
}
//除法函数调用者
void Func()
{
while (1)
{
double a, b;
cin >> a >> b;
try
{
cout << division(a, b) << endl;
}
catch (const char*str)
{
cout << str << endl;
}
cout << "void Func()" << endl;
}
}
//主函数
int main()
{
//捕获
try
{
Func();
}
catch (const char*str)
{
cout << str << endl;
}
return 0;
}
如果Func()中也有匹配的catch语句,那么就会直接在Func中被捕获,不会再跳转到main函数中。
成功捕获后,会执行catch语句后的代码
catch(...)捕获任意类型异常
#include
using namespace std;
//除法函数
double division(double a, double b)
{
if (b == 0)
throw "Divide by zero error";
return a / b;
}
//除法函数调用者
void Func()
{
while (1)
{
double a, b;
cin >> a >> b;
try
{
cout << division(a, b) << endl;
}
catch (const char*str)
{
cout << str << endl;
throw 6;//抛出另外的异常
}
cout << "void Func()" << endl;
}
}
//主函数
int main()
{
//捕获
try
{
Func();
}
catch (const char*str)
{
cout << str << endl;
}
catch (...)//捕获任意类型的异常
{
cout << "捕获未知异常" << endl;
}
return 0;
}
多态是使用基类指针或者基类引用接收不同对象,会有不同的效果。
因为异常存在多种形式,多种错误信息,所以可以提供一个基类,其派生类再添加各自的错误信息。
比如,我们定义一个Exception基类
class Exception
{
public:
//构造函数
Exception(string &errMes,int errId)
:_errMes(errMes)
,_errId(errId)
{}
//显示错误信息的虚函数
virtual string what()const
{
return _errMes;
}
//权限需要设置成保护的
//不然派生类无法访问
protected:
string _errMes;
int _errId;
};
我们定义what函数,获取异常的错误信息,派生类通过重写what虚函数,或者添加成员变量来满足不同的需求
比如在项目中,我们可能会用到数据库,网络。那么在不同部分抛出的异常信息不同,就可以使用派生类
//数据库异常
class SqlException : public Exception
{
public:
SqlException(const string& errmsg, int id, const string& sql)
:Exception(errmsg, id)
, _sql(sql)
{}
virtual string what() const
{
string str = "SqlException:";
str += _errMes;
str += "->";
str += _sql;
return str;
}
private:
const string _sql;
};
//缓存异常
class CacheException : public Exception
{
public:
CacheException(const string& errmsg, int id)
:Exception(errmsg, id)
{}
virtual string what() const
{
string str = "CacheException:";
str += _errMes;
return str;
}
};
//Http异常
class HttpServerException : public Exception
{
public:
HttpServerException(const string& errmsg, int id, const string& type)
:Exception(errmsg, id)
, _type(type)
{}
virtual string what() const
{
string str = "HttpServerException:";
str += _type;
str += ":";
str += _errMes;
return str;
}
private:
const string _type;
};
在main函数中,不管调用哪个功能,抛出的异常都可以用const Exception&捕获,调用what显示异常信息。
C++也有提供异常类——excepion
exception就是异常类的基类,提供虚函数what
也实现了很多派生类
像我们使用的new关键字,其实是调用了operator new
如果失败,会抛bad_alloc这个异常
下图是C++提供的异常类
比如:
第一个**throw(std::bad_alloc)表明可能会抛std::bad_alloc这个异常
第二个和第三个throw ()**表明不会抛异常
但是因为这个不是强求的,所以很多人并不会遵循这个异常规范。其次,即使throw()标识不会抛异常,但是仍然在其中抛异常也并不会报错。抛出了未标识的异常也不会报错
比如线程的构造函数
一个函数如果明确不抛异常,可以加noexcept。但是如果加了noexcept仍然抛异常,也不会直接报错或者终止程序,而是会有警告。
如果对加了noexcept的函数进行try,catch捕获,则会终止程序
可能会抛异常,就不加noexcept,C++98的异常规范看个人
C++异常的优点
C++异常的缺点
异常尽量规范使用,随意抛异常,外层捕获的人苦不堪言。所以异常规范有两点:一. 抛出异常类型都继承一个基类
。二. 函数是否抛异常,抛什么异常,可以使用noexcept和throw(...)
标识
感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。