
⭐️博主: 此生决int-@CSDN博客
** 速胜派就是最大的投降派!!!**
** 🔥热门专栏🔥**
文章目录
-
- 上期回顾
- [1. 异常的概念及使用](#1. 异常的概念及使用)
- [2. 标准库的异常](#2. 标准库的异常)
-
-
- [① `logic_error`(逻辑错误)](#①
logic_error(逻辑错误)) - [② `runtime_error`(运行时错误)](#②
runtime_error(运行时错误)) - [③ `invalid_argument`(无效参数)](#③
invalid_argument(无效参数)) - [④ `out_of_range`(越界错误)](#④
out_of_range(越界错误)) - [⑤ `length_error`(长度错误)](#⑤
length_error(长度错误)) - [⑥ `domain_error`(定义域错误)](#⑥
domain_error(定义域错误)) - [⑦ `overflow_error`(上溢错误)](#⑦
overflow_error(上溢错误)) - [⑧ `underflow_error`(下溢错误)](#⑧
underflow_error(下溢错误)) - [⑨ `bad_alloc`(内存分配失败)](#⑨
bad_alloc(内存分配失败)) - 小结
- [① `logic_error`(逻辑错误)](#①
-
- 下期预告
- 智能指针
- 结语
上期回顾
上一篇我们主要学习了 C ++11的一些新特性 ,今天我们来看看C++的异常处理机制!
1. 异常的概念及使用
1.1 异常的概念
我们常常会遇到这样的问题:
cpp
int Find(const vector<int>& v, int x)
{
// 找到了返回下标
// 找不到返回 -1
}
但是,万一-1也有可能是要返回的呢?再说了,你出错返回-1,我写的返回0,所以,要用一种统一的方式来管理这种情况,处理这种异常情况。
于是 C++ 提供了异常机制(Exception Handling)。
正常情况下按照正常流程执行;发生异常时,立即跳出当前正常执行流程,把异常交给能够处理它的地方。
1.2 异常的抛出和捕获
异常就三个关键字:try catch throw:
例如:
cpp
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。
cpp
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思想。即:
把资源的生命周期交给对象管理,在对象析构时自动释放资源
像下面这种情况就不是交给对象管理
cpp
int* ptr=new int[4];
throw 10;
delete[] ptr;//这句不会执行,资源没有释放!
意义就是:即使程序因为异常提前离开作用域,已经构造成功的局部对象依然会被正确析构,从而完成资源释放。
1.4 查找匹配的处理代码
我们上面说了,throw出来的类型要和catch对应
实际代码通常不是只有一个 catch:
cpp
try
{
// ...异常代码
}
catch (int e)
{
cout << "int异常" << endl;
}
catch (double e)
{
cout << "double异常" << endl;
}
catch (const char* e)
{
cout << "字符串异常" << endl;
}
catch (...)//可以匹配任何类型的异常,匹配规则是从上往下依次匹配,所以,通常放到最后
{
cout << "未知异常" << endl;
}
throw也可以处理自定义类型,继承类和派生类
cpp
class Exception
{
};
class FileException : public Exception
{
};
如果:
cpp
try
{
throw FileException();
}
catch (const Exception& e)//使用&,避免拷贝,const是因为异常通常不会被修改,建议都这样写
{
cout << "Exception" << endl;
}
catch (const FileException& e)//一般都是先捕捉派生类,再捕捉基类,也就是从具体到抽象!
{
cout << "FileException" << endl;
}
注意:
cpp
try
{
throw 10;
}
catch (double e)//虽然,int可以隐式类型转换成double,但是不能捕获
{
}
异常匹配并不是按照普通函数调用/赋值时的所有隐式转换规则来进行匹配的 ,所以,不要依赖数值类型之间的转换来设计异常匹配;让 throw 的类型和 catch 的类型明确对应。
,另外,如果异常一直没有合适的匹配,
那么程序会调用:
cpp
std::terminate()
最终通常导致程序异常终止。
1.5 异常重新抛出
cpp
void Func()
{
try
{
throw 10;
}
catch (int e)
{
cout << "Func捕获异常:" << e << endl;//只是出错错误信息,当前还不想处理这个异常,所以,要抛出给外面,并且是,原封不动的抛出
throw;
}
}
int main()
{
try
{
Func();
}
catch (int e)
{
cout << "main再次捕获:" << e << endl;
}
}
输出:
text
Func捕获异常:10
main再次捕获:10
throw; 和 throw e;的区别
throw 抛出当前异常!原封不动
throw e; 抛出异常e
例子:
cpp
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 异常安全问题
程序发生异常以后,不能因为异常导致资源泄漏、数据损坏、对象处于不可用状态等问题。
cpp
void Func()
{
int* p = new int[100];
// 中间发生异常
throw 10;
delete[] p;//这里不会执行,导致资源泄漏
//所以,不要这么搞,要让对像管理资源
void Func()
{
vector<int> v(100);
throw 10;
}
}
1.7 异常规范
这个概念主要是用来描述:
一个函数可能抛出什么异常,或者保证自己不抛异常。
不过这里一定要注意:**C++历史上有两套不同的异常规范机制。**旧的就不学了,直接看新的
关键字: noexcept,保证不会发生异常
| 写法 | 含义 | 现代 C++中的情况 |
|---|---|---|
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(逻辑错误)
程序本身的逻辑/使用方式存在问题。
常见:
cpp
invalid_argument
out_of_range
length_error
domain_error
② runtime_error(运行时错误)
程序运行过程中发生了无法预先避免的错误。
常见:
cpp
range_error
overflow_error
underflow_error
③ invalid_argument(无效参数)
函数接收到一个不合法的参数。参数类型没问题,但参数的值不符合要求。
④ out_of_range(越界错误)
访问的下标、位置等超出了允许的范围。
例如:
cpp
vector<int> v = {1, 2, 3};
cout << v.at(10) << endl;
⑤ length_error(长度错误)
对象的长度超过了允许的范围。
cpp
string s;
s.resize(s.max_size() + 1); // 长度超过允许范围
⑥ domain_error(定义域错误)
参数虽然类型正确,但不属于这个操作允许的数学/逻辑定义域。
⑦ overflow_error(上溢错误)
计算结果超出了能够表示的范围。
⑧ underflow_error(下溢错误)
计算结果出现了下溢。
⑨ bad_alloc(内存分配失败)
动态内存分配失败。
cpp
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,轻松备战期末考试
** 愿每一次敲下键盘,都比昨天更进一步!** ** 愿每一行代码落下,都让未来多一种可能!**