⭐️博主: 此生决int-@CSDN博客
速胜派就是最大的投降派!!!
🔥热门专栏🔥
深入理解 C++ 系列 | 算法系列
快速复习系列 | Java 速通系列
文章目录
-
- 上期回顾
- 1. 异常的概念及使用
-
- 1.1 异常的概念
- 1.2 异常的抛出和捕获
- 1.3栈展开
-
- 局部对象会析构
- 1.4 查找匹配的处理代码
- 1.5 异常重新抛出
-
- `throw;` 和 `throw e;`的区别
- 1.6 异常安全问题
- 1.7 异常规范
- 2. 标准库的异常
-
-
- ① `logic_error`(逻辑错误)
- ② `runtime_error`(运行时错误)
- ③ `invalid_argument`(无效参数)
- ④ `out_of_range`(越界错误)
- ⑤ `length_error`(长度错误)
- ⑥ `domain_error`(定义域错误)
- ⑦ `overflow_error`(上溢错误)
- ⑧ `underflow_error`(下溢错误)
- ⑨ `bad_alloc`(内存分配失败)
- 小结
-
- 下期预告
- 智能指针
- 结语
上期回顾
上一篇我们主要学习了 C ++11的一些新特性,今天我们来看看C++的异常处理机制!
1. 异常的概念及使用
1.1 异常的概念
我们常常会遇到这样的问题:
int Find(const vector<int>& v, int x)
{
// 找到了返回下标
// 找不到返回 -1
}
但是,万一-1也有可能是要返回的呢?再说了,你出错返回-1,我写的返回0,所以,要用一种统一的方式来管理这种情况,处理这种异常情况。 于是 C++ 提供了异常机制(Exception Handling)。
正常情况下按照正常流程执行;发生异常时,立即跳出当前正常执行流程,把异常交给能够处理它的地方。
1.2 异常的抛出和捕获
异常就三个关键字:try catch throw: 例如:
int Div(int x, int y)
{
if (y == 0)
{
throw "除数不能为0";//throw的是char* str类型
cout<<"你好"<<endl;//永远不会执行,throw后面可以认为函数就退出了
}
return x / y;
}
int main()
{
try
{
//可能出现异常的代码
int ret = Div(10, 0);
cout << ret << endl;
}
catch (const char* str)//char* str类型,类型匹配,才可以捕捉
{
//发生异常后怎么处理
cout << str << endl;//打印错误信息
}
return 0;
}
注意: 1,类型必须匹配! 那么,异常到底是怎么传出去的呢?栈展开!
1.3栈展开
发生异常后,程序从当前函数开始,一层一层往外退,同时销毁这些函数中的局部对象,直到找到匹配的 catch。
void D()
{
throw 10;//抛出异常,并且没有对应的catch捕获,函数结束,返回上一层C
}
void C()
{
D();//没有对应的catch捕获,函数结束,返回上一层C
}
void B()
{
C();//没有对应的catch捕获,函数结束,返回上一层C
}
void A()
{
B();//没有对应的catch捕获,函数结束,返回上一层C
}
int main()
{
try
{
A();//这里有对应的catch,
}
catch (int e)
{
cout << "捕获异常:" << e << endl;
}
}
这个过程就是栈展开!
局部对象会析构
即,栈展开时,函数栈帧里面创建的局部对象会析构掉(正常,函数结束了,局部对象当然要销毁),这里体现了C++里面的RAII思想。即:
把资源的生命周期交给对象管理,在对象析构时自动释放资源
像下面这种情况就不是交给对象管理
int* ptr=new int[4];
throw 10;
delete[] ptr;//这句不会执行,资源没有释放!
意义就是:即使程序因为异常提前离开作用域,已经构造成功的局部对象依然会被正确析构,从而完成资源释放。
1.4 查找匹配的处理代码
我们上面说了,throw出来的类型要和catch对应 实际代码通常不是只有一个 catch:
try
{
// …异常代码
}
catch (int e)
{
cout << "int异常" << endl;
}
catch (double e)
{
cout << "double异常" << endl;
}
catch (const char* e)
{
cout << "字符串异常" << endl;
}
catch (...)//可以匹配任何类型的异常,匹配规则是从上往下依次匹配,所以,通常放到最后
{
cout << "未知异常" << endl;
}
throw也可以处理自定义类型,继承类和派生类
class Exception
{
};
class FileException : public Exception
{
};
如果:
try
{
throw FileException();
}
catch (const Exception& e)//使用&,避免拷贝,const是因为异常通常不会被修改,建议都这样写
{
cout << "Exception" << endl;
}
catch (const FileException& e)//一般都是先捕捉派生类,再捕捉基类,也就是从具体到抽象!
{
cout << "FileException" << endl;
}
注意:
try
{
throw 10;
}
catch (double e)//虽然,int可以隐式类型转换成double,但是不能捕获
{
}
异常匹配并不是按照普通函数调用/赋值时的所有隐式转换规则来进行匹配的,所以,不要依赖数值类型之间的转换来设计异常匹配;让 throw 的类型和 catch 的类型明确对应。 ,另外,如果异常一直没有合适的匹配, 那么程序会调用:
std::terminate()
最终通常导致程序异常终止。
1.5 异常重新抛出
void Func()
{
try
{
throw 10;
}
catch (int e)
{
cout << "Func捕获异常:" << e << endl;//只是出错错误信息,当前还不想处理这个异常,所以,要抛出给外面,并且是,原封不动的抛出
throw;
}
}
int main()
{
try
{
Func();
}
catch (int e)
{
cout << "main再次捕获:" << e << endl;
}
}
输出:
Func捕获异常:10
main再次捕获:10
throw; 和 throw e;的区别
throw 抛出当前异常!原封不动 throw e; 抛出异常e 例子:
class Exception
{
public:
virtual const char* what() const
{
return "Exception";
}
};
class FileException : public Exception//继承
{
public:
const char* what() const override
{
return "FileException";
}
};
try
{
throw FileException();//抛出派生类异常
}
catch (const Exception& e)//基类接收
{
cout << e.what() << endl;
throw;//抛出的还是派生类而不是基类!!!
//throw e抛出的异常是基类,会导致派生类信息丢失,也就是对象切片
}
throw; vs throw e; 总结
| throw; | 重新抛出当前异常 | 保持 | 向上层继续传递异常 |
| throw e; | 抛出 e 这个对象 | 可能发生切片 | 确实需要重新构造/改变异常对象时 |
| throw xxx; | 抛出一个新的异常对象 | 取决于 xxx | 抛出新的异常 |
1.6 异常安全问题
程序发生异常以后,不能因为异常导致资源泄漏、数据损坏、对象处于不可用状态等问题。
void Func()
{
int* p = new int[100];
// 中间发生异常
throw 10;
delete[] p;//这里不会执行,导致资源泄漏
//所以,不要这么搞,要让对像管理资源
void Func()
{
vector<int> v(100);
throw 10;
}
}
1.7 异常规范
这个概念主要是用来描述:
一个函数可能抛出什么异常,或者保证自己不抛异常。
不过这里一定要注意:**C++历史上有两套不同的异常规范机制。**旧的就不学了,直接看新的 关键字: noexcept,保证不会发生异常
| void Func() | 没有 noexcept 承诺 | 可以抛异常 |
| void Func() throw(int) | 允许特定类型异常 | 旧式机制,不推荐使用 |
| void Func() throw() | 旧式“不抛异常” | 已被 noexcept 取代 |
| void Func() noexcept | 保证不抛异常 | 现代 C++常用 |
| void Func() noexcept(false) | 不保证不抛异常 | 可以抛 |
| noexcept(Func()) | 判断表达式是否不抛异常 | 模板/STL中常见 |
2. 标准库的异常
虽然我们可以直接定义异常的名字,但是库库里面给我们设计好了一些异常类型:

① logic_error(逻辑错误)
程序本身的逻辑/使用方式存在问题。 常见:
invalid_argument
out_of_range
length_error
domain_error
② runtime_error(运行时错误)
程序运行过程中发生了无法预先避免的错误。 常见:
range_error
overflow_error
underflow_error
③ invalid_argument(无效参数)
函数接收到一个不合法的参数。参数类型没问题,但参数的值不符合要求。
④ out_of_range(越界错误)
访问的下标、位置等超出了允许的范围。
例如:
vector<int> v = {1, 2, 3};
cout << v.at(10) << endl;
⑤ length_error(长度错误)
对象的长度超过了允许的范围。
string s;
s.resize(s.max_size() + 1); // 长度超过允许范围
⑥ domain_error(定义域错误)
参数虽然类型正确,但不属于这个操作允许的数学/逻辑定义域。
⑦ overflow_error(上溢错误)
计算结果超出了能够表示的范围。
⑧ underflow_error(下溢错误)
计算结果出现了下溢。
⑨ bad_alloc(内存分配失败)
动态内存分配失败。
int* p = new int[100000000000000];
小结
| exception | 所有标准异常的基类之一 | 统一捕获 |
| logic_error | 逻辑/使用方式相关错误 | 程序逻辑问题 |
| invalid_argument | 参数不合法 | 函数收到不符合要求的参数 |
| out_of_range | 超出合法范围 | vector::at() 越界 |
| length_error | 长度超过允许范围 | 容器长度请求过大 |
| domain_error | 超出合法定义域 | 数学/逻辑定义域问题 |
| runtime_error | 运行过程中发生的问题 | 运行环境/运行条件导致的问题 |
| overflow_error | 数值溢出 | 计算结果过大 |
| underflow_error | 数值下溢 | 计算结果过小 |
| bad_alloc | 内存分配失败 | new 分配失败 |
下期预告
智能指针
结语
本文到此结束,感谢大家的阅读!如果觉得本文对你有所帮助,欢迎点赞、收藏、关注,也欢迎在评论区一起交流讨论。 也欢迎订阅我的 深入理解 C++系列:从语法入门到底层原理,系统掌握现代 C++ 算法系列:从入门到精通,蓝桥杯、ACM、LeetCode 与面试算法全路线 快速复习系列:知识梳理、查漏补缺,考前冲刺必备 Java 速通系列:已学 C 语言,快速上手 Java,轻松备战期末考试
愿每一次敲下键盘,都比昨天更进一步!
愿每一行代码落下,都让未来多一种可能!
网硕互联帮助中心
评论前必须登录!
注册