总结: 对于小错误:返回错误码 对于严重的错误:终止程序
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的 直接或间接调用者 处理这个错误。
try: try 块中的代码被称为保护代码 ,执行保护代码可能会抛出异常
throw: 出现问题,程序抛异常。[关键字]
catch: 在要处理问题的地方捕获异常[关键字]
double Division(int a, int b)
{
return ((double)a / (double)b);
}
void Func()
{
int left, right;
cin >> left >> right;
cout << Division(left, right) << endl;
}
int main()
{
Func();
return 0;
}
inf是啥玩意儿???[infinite]
超出浮点数范围 即值很大或很小:1.0/0.0,-1.0/0.0,0.0+inf=inf;log(0);
有同学见过nan吗???这又是个啥???[Not a number]
sqrt(-1.0f): nan
上述inf和nan在使用浮点数时会出现
个例: 抛出派生类对象,使用基类捕获
栈展开: 沿着调用链查找匹配的catch子句的过程
class Exception
{
public:
Exception(int errid, const string& msg)
:_errid(errid)
, _errmsg(msg)
{
}
const string& GetMsg() const
{
return _errmsg;
}
int GetErrid() const
{
return _errid;
}
private:
int _errid; // 错误码
string _errmsg; // 错误描述
};
double Division(int a, int b)
{
if (b == 0)
{
//Exception err(1, "除0错误");
//throw err;
//throw Exception(1, "除0错误");
throw 1;
}
else
{
return ((double)a / (double)b);
}
}
void Func()
{
int left, right;
cin >> left >> right;
try
{
cout << Division(left, right) << endl;
}
catch (char str)
{
cout << str << endl;
}
cout << "void Func()" << endl;
}
int main()
{
while (1)
{
try
{
Func();
}
catch (const Exception& e)
{
cout << e.GetMsg() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
}
return 0;
}
class exception
{
public:
exception () throw();
exception (const exception&) throw();
exception& operator= (const exception&) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
}
//基类 -- 异常基类
class Exception
{
public:
//构造函数
Exception(int errid, const string& errmsg)
:_errid(errid)
, _errmsg(errmsg)
{
}
//基类 -- 获得错误信息的虚函数
virtual string what() const
{
return _errmsg;
}
//获得错误码
int GetErrid() const
{
return _errid;
}
protected:
int _errid; // 错误码
string _errmsg; // 错误描述
};
//数据库异常 -- 子类
class SqlException : public Exception
{
public:
//构造函数
SqlException(int errid, const string& msg, const string& sql)
:Exception(errid, msg)
, _sql(sql)
{
}
//子类重写父类虚函数
virtual string what() const
{
string msg = "SqlException:";
msg = _errmsg + "->" + _sql;
return msg;
}
protected:
string _sql;
};
class CacheException : public Exception
{
public:
CacheException(int id, const string& errmsg)
:Exception(id, errmsg)
{
}
virtual string what() const
{
string msg = "CacheException:" + _errmsg;
return msg;
}
};
class HttpServerException : public Exception
{
public:
HttpServerException(int id, const string& errmsg, const string& type)
:Exception(id, errmsg)
, _type(type)
{
}
virtual string what() const
{
string msg = "HttpServerException:" + _errmsg + "->" + _type;
return msg;
}
private:
const string _type;
};
//数据库测试
void SQLTest()
{
srand(time(0));
if (rand() % 8 == 0)
{
throw SqlException(100, "No permissions!", "Encountered special value!");
}
cout << "SQL call successful!" << endl;
}
//高速缓冲器测试
void CacheTest()
{
srand(time(0));
if (rand() % 6 == 0)
{
throw CacheException(1, "Insufficient permissions!");
}
else if (rand() % 7 == 0)
{
throw CacheException(2, "Data does not exist!");
}
SQLTest();
}
//网络服务器测试
void HttpServerTest()
{
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException(1,"The requested resource does not exist!", "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException(2, "Insufficient permissions!", "post");
}
else if (rand() % 5 == 0)
{
throw 1;
}
CacheTest();
}
int main()
{
while (1)
{
this_thread::sleep_for(chrono::seconds(1));
try
{
HttpServerTest();
}
catch (const Exception& e)
{
//基类引用指向 基类/子类
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
异常的重新抛出实际上是一种解决异常安全问题的一种办法 只不过这种办法不太好 是一种迫不得已的手段 更好的办法会在下一节<<智能指针>>中讲解 关注主啵不迷路~
- 构造函数完成对象的构造和初始化,不要在构造函数中抛出异常,可能导致对象不完整或没有完全初始化
- 析构函数主要完成资源的清理,不要在析构函数内抛出异常,可能导致资源泄漏(内 存泄漏、句柄未关闭等)
- 异常经常会导致资源泄漏问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁
//基类 -- 异常基类
class Exception
{
public:
//构造函数
Exception(int errid, const string& errmsg)
:_errid(errid)
, _errmsg(errmsg)
{
}
//基类 -- 获得错误信息的虚函数
virtual string what() const
{
return _errmsg;
}
//获得错误码
int GetErrid() const
{
return _errid;
}
protected:
int _errid; // 错误码
string _errmsg; // 错误描述
};
//数据库异常 -- 子类
class SqlException : public Exception
{
public:
//构造函数
SqlException(int errid, const string& msg, const string& sql)
:Exception(errid, msg)
, _sql(sql)
{
}
//子类重写父类虚函数
virtual string what() const
{
string msg = "SqlException:";
msg = _errmsg + "->" + _sql;
return msg;
}
protected:
string _sql;
};
class CacheException : public Exception
{
public:
CacheException(int id, const string& errmsg)
:Exception(id, errmsg)
{
}
virtual string what() const
{
string msg = "CacheException:" + _errmsg;
return msg;
}
};
class HttpServerException : public Exception
{
public:
HttpServerException(int id, const string& errmsg, const string& type)
:Exception(id, errmsg)
, _type(type)
{
}
virtual string what() const
{
string msg = "HttpServerException:" + _errmsg + "->" + _type;
return msg;
}
private:
const string _type;
};
//数据库测试
void SQLTest()
{
srand(time(0));
if (rand() % 8 == 0)
{
throw SqlException(100, "No permissions!", "Encountered special value!");
}
cout << "SQL call successful!" << endl;
}
//高速缓冲器测试
void CacheTest()
{
srand(time(0));
if (rand() % 6 == 0)
{
throw CacheException(1, "Insufficient permissions!");
}
else if (rand() % 7 == 0)
{
throw CacheException(2, "Data does not exist!");
}
SQLTest();
}
//网络服务器测试
void HttpServerTest()
{
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException(1, "The requested resource does not exist!", "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException(2, "Insufficient permissions!", "post");
}
else if (rand() % 5 == 0)
{
throw 1;
}
CacheTest();
}
double Division(int a, int b) noexcept
{
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func()
{
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
// 重新抛出去。
int* pointer = new int[10];
int left, right;
cin >> left >> right;
try
{
cout << Division(left, right) << endl;
HttpServerTest();
}
/*
当抛异常时仍然想要执行catch块后的部分去释放堆区空间 以避免内存泄漏
那么我们可以在这里加一个catch来匹配 而不让程序直接跳到main去匹配
catch (const char* errmsg)
{
cout << errmsg << endl;
}
*/
//但是我们另一个更重要的目的是想要统一在main函数执行catch
//那么我们可以在这里进行一个异常的重新抛出 在抛出之前
//我们可以先释放堆区空间 这样两个目的都达到[1.释放空间2.main匹配catch]
//为什么要把异常统一放到main去匹配 ?
//不同异常类型可能有许多工作要处理[假定所有的异常都要先输出错误信息并记录日志]
// 下面情况还要做其他事情
//比如遇到网络问题不是直接终止而是连续访问几次后若仍无网络再终止
//如果我们在上一层函数就捕获了异常 那么就需要再当前函数记录日志并输出错误信息
//为了增加代码复用性和提高泛型化 我们统一再main函数匹配catch
//因为我们不想在这里就进行catch匹配但是又需要释放空间
//所以对于所有的异常我们都在此接收但只释放空间 重新抛异常
//让main函数去处理
catch (...)
{
cout << "delete []" << pointer << endl;
delete[] pointer;
// catch到的是何异常类型就抛出何异常类型
throw;
}
//注意:这种办法是为了释放空间而不得已的做法 为了释放空间我们不得不搞出这样的处理方式
//更好的解决办法在下一章: <<智能指针>> 中会详细讲解 关注博主不迷路哦~
cout << "delete []" << pointer << endl;
delete[] pointer;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const Exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
return 0;
}
void Func()
{
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
// 重新抛出去。
int* pointer = new int[10];
int left, right;
cin >> left >> right;
try
{
cout << Division(left, right) << endl;
HttpServerTest();
}
/*
当抛异常时仍然想要执行catch块后的部分去释放堆区空间 以避免内存泄漏
那么我们可以在这里加一个catch来匹配 而不让程序直接跳到main去匹配
catch (const char* errmsg)
{
cout << errmsg << endl;
}
*/
//但是我们另一个更重要的目的是想要统一在main函数执行catch
//那么我们可以在这里进行一个异常的重新抛出 在抛出之前
//我们可以先释放堆区空间 这样两个目的都达到[1.释放空间2.main匹配catch]
//为什么要把异常统一放到main去匹配 ?
//不同异常类型可能有许多工作要处理[假定所有的异常都要先输出错误信息并记录日志]
// 下面情况还要做其他事情
//比如遇到网络问题不是直接终止而是连续访问几次后若仍无网络再终止
//如果我们在上一层函数就捕获了异常 那么就需要再当前函数记录日志并输出错误信息
//为了增加代码复用性和提高泛型化 我们统一再main函数匹配catch
//因为我们不想在这里就进行catch匹配但是又需要释放空间
//所以对于所有的异常我们都在此接收但只释放空间 重新抛异常
//让main函数去处理
catch (...)
{
cout << "delete []" << pointer << endl;
delete[] pointer;
// catch到的是何异常类型就抛出何异常类型
throw;
}
//注意:这种办法是为了释放空间而不得已的做法 为了释放空间我们不得不搞出这样的处理方式
//更好的解决办法在下一章: <<智能指针>> 中会详细讲解 关注博主不迷路哦~
cout << "delete []" << pointer << endl;
delete[] pointer;
}
//当遇到A/B/C/D中某种类型的异常 -- 抛出
为了让函数使用者知道该函数可能抛出的异常有哪些 遇到异常E的类型也会抛出
void fun() throw(A,B,C,D);
//当遇到bad_alloc的异常 -- 抛出
void* operator new (size_t size) throw (bad_alloc);
//不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
//可以抛任何类型异常
void fun();
需要注意的是 上述声明 是否会抛出异常/遇到何种类型的异常抛出 不是强制的 就是说加不加都行
那人们为了省事当然不加 于是这个规范形同虚设
//C++98的依然支持
class exception
{
public:
exception () noexcept;//明确不抛异常 -- 如果一个会抛出异常的函数加了noexcept会报错
//实际上有的编译器对于这一块是有点含糊的 比如main函数调用A函数 A函数调用B函数 B函数后加noexcept如果会抛异常则报错 但是只在A后面加noexcept 虽然有异常抛出但却可以运行通过[即简介抛出异常的检测不到] 那么对于这一块 既然C++设计的没有强制我们使用 那就别加这种东西 加了反而比较麻烦
exception (const exception&) noexcept;
exception& operator= (const exception&) noexcept;
virtual ~exception();//可能会抛异常 不加
virtual const char* what() const noexcept;
}