c语言的错误处理方式
- 返回值
return 0;
- 全局错误标志
int test() { int fd = open ("1.txt",O_RDONLY); if(fd == -1) { //open打开文件错误会返回错误码 errno perror()://或者用strerror可以打印出错误 return -1; } return 0; }
缺点
- 当函数有多级嵌套的时候,要一层一层的进行返回值判断才能知道哪里错误
- 错误码不好设置
c++异常
异常处理的核心思想是,把功能模块代码与系统中可能出现错误的处理代码分离开来,以此来达到使我们的代码组织起来更美观、逻辑上更清晰,并且同时从根本上来提高我们软件系统长时间稳定运行的可靠性。
异常,即exception,是C++中的基本概念之一,在某段程序发生无法继续正常执行的情况时,C++允许程序进行所谓抛出异常(有时也被称为吐出异常)的行为,这些被抛出的异常,会自动地从触发点开始向外传播,直到被捕获(有时也被称为吞下异常)或者程序终止。
抛出异常 throw
int test01()
{
int fd = open("1.txt",O_RDONLY);
if(fd == -1)
{
//抛出异常 ---字符串对象异常
throw "open error";
}
return 0;
}
捕获异常 try
try {
//可能发生异常的代码块 函数
test01();
} catch (const char*str) //捕获字符串异常
{
cout<<"str:"<<str<<endl;
}catch (int a) //捕获整型异常
{
cout<<"a:"<<a<<endl;
}catch(...) //捕捉其他异常
{
cout<<"other"<<endl;
}
实例 从键盘输入两个整数(a b),进行相除,输出结果,b等于0抛出异常重新输入
#include <iostream>
using namespace std;
int main()
{
while (1) {
cout<<"input two int:";
int a,b;
try {
//有抛出异常的代码块
cin>>a>>b;
if(b == 0)
throw 0;
} catch (int ra) {
//捕捉到异常之后,对异常的处理
cout<<"b != 0"<<endl;
//重新执行上面代码块
continue;
}
cout<<"result:"<<a/b<<endl;
}
return 0;
}
异常类型
抛出异常的时候可以抛出不同的类型异常,有些时候需要抛出很多类型的异常,捕捉的时候就要步骤很多类型异常,很不方便,可以自定义异常体系
自定义异常体系
简单来说就是定义一个异常基类,每当遇到异常就抛出一个异常基类的派生类,捕获的时候就捕获异常基类就可以了
异常基类
//异常基类
class Exception
{
public:
Exception(const char*str = nullptr,int id = 0):_errmsg(str),_id(id){}
virtual void what() const = 0; //纯虚函数
protected:
string _errmsg;//错误信息
int _id;//错误码
};
如果数据库异常就定义一个数据库异常派生类
//数据库异常
class SqlException:public Exception
{
public:
SqlException(const char*str = nullptr,int id = 1):Exception(str,id){}
virtual void what() const
{
cout<<"error msg:"<<_errmsg<<endl;
cout<<"error id:"<<_id<<endl;
}
};
如果网络异常就定义一个网络异常派生类
class SocketException:public Exception
{
public:
SocketException(const char*str = nullptr,int id = 2):Exception(str,id){}
virtual void what() const
{
cout<<"error msg:"<<_errmsg<<endl;
cout<<"error id:"<<_id<<endl;
}
};
在test函数里遇到了网络和数据库异常,抛出数据库和网络异常
void test()
{
//当使用数据库的过程中,发生了一个非正常情况,可以直接抛出一个数据库异常
//throw SqlException("sql error");
//当使用网络接口的过程中,可能会发生非正常的情况,比如说无法发送数据,或者接收数据不对
throw SocketException("recv data error");
}
int main()
{
try {
test();
} catch (Exception &ra) { //用基类类型来捕获
ra.what();
}
return 0;
}
c++标准库异常体系
c++已经提供了标准的异常类
头文件
#include<exception>
根据标准异常基类生成的派生类
std::exception - cppreference.comhttps://zh.cppreference.com/w/cpp/error/exception
异常安全问题
- 由于抛异常只要找到匹配的catch就直接跳到catch块执行,没有找到对应catch的函数就不会继续执行。这样导致函数的执行流回很乱。可能会导致一些问题。
- 构造函数完成对象的构造和初始化,最好不要再构造函数中抛出异常,否则可能导致对象不完整或者没有完全初始化析构函数主要完成资源的清理,最好不要在析构函数中抛异常,否则可能导致内存泄漏。
- C++异常经常会导致资源泄漏问题。比如: 在new和delete中抛出异常,导致new出来的资源没有释放,导致内存泄漏。在Iock和unlock中抛出异常,导致锁没有释放,导致死锁。
- 有两种解决办法:
- 将异常捕获,释放资源后,将锁重新抛出。
- 使用RAI的思想解决。定义一个类封装,管理资源。当要使用时实例化一个类对象,将资源传入,当退出函数,调用对象析构函数,释放资源了
异常规范说明
- 异常规格说明,是使函数调用者知道函数可能会抛出哪些异常。可以在函数后面接throw(异常类型),列出这个函数可能抛出的所有异常类型。
- 在函数后面加throw()或者noexcept表示不抛异常
- 若没有接口声明表示,此函数可能会抛出任意类型的异常
用法1 如何抛出标准异常
#include <iostream>
#include <exception> //C++标准异常头文件
using namespace std;
float divider(float a, float b)
{
// 抛出标准异常对象
if(b == 0)
throw invalid_argument("b!=0");
return a/b;
}
int main()
{
try {
divider(10,0);
} catch (exception &ra) {
cout<<"333"<<endl;
ra.what();//ra没有作用,可以自己继承然后重写函数
}
return 0;
}
用法2 如何标准化写抛出标准异常
#include <iostream>
#include <exception> //C++标准异常头文件
using namespace std;
//函数的后面 所加的 throw(invalid_argument)就是告诉函数的使用者这个函数可能会抛出这个异常对象
float divider(float a, float b) throw(invalid_argument);
int main()
{
try {
divider(10,0);
} catch (exception &ra) {
cout<<"333"<<endl;
ra.what();
}
return 0;
}
float divider(float a, float b) throw(invalid_argument)
{
// 抛出标准异常对象
if(b == 0)
throw invalid_argument("b!=0");
return a/b;
}
用法3 如何表示函数不会抛出异常 noexcept关键字
#include <iostream>
#include <exception> //C++标准异常头文件
using namespace std;
//noexcept 关键字 明确表示这个函数 不会抛出任何的异常
void test01() noexcept;
void test02() throw();
void test01() noexcept
{
}
void test02() throw()
{
}