建立网站的公司杭州长沙网站关键词推广
web/
2025/10/3 3:00:15/
文章来源:
建立网站的公司杭州,长沙网站关键词推广,wordpress 首页 插件,邯郸网站建设 安联网络公司文章目录 一. 什么是异常#xff1f;二. 为什么要引入抛异常机制#xff1f;方法一#xff1a;直接终止程序方法二#xff1a;返回错误码方法三#xff1a;C 标准库中的 setjmp 和 longjmp 组合总结 C 中处理异常的方式 三. 如何进行抛异常#xff1f;1. 关于抛出的异常对… 文章目录 一. 什么是异常二. 为什么要引入抛异常机制方法一直接终止程序方法二返回错误码方法三C 标准库中的 setjmp 和 longjmp 组合总结 C 中处理异常的方式 三. 如何进行抛异常1. 关于抛出的异常对象的类型2. 抛出的异常对象一定要有对应类型的捕获3. 异常的执行流顺序4. 在函数调用链中异常栈展开匹配原则5. 规范的异常处理 四. 异常的重新抛出五. 异常安全六. 异常规范七. 异常的优缺点八. C 标准库的异常体系 一. 什么是异常
异常是一种处理错误的方式当一个函数发现自己无法处理的错误时就会停止往下运行然后把这个异常抛出去让这个函数的直接或间接调用者去处理这个错误。
C 中异常相关的关键字有如下三个
throw当问题出现时程序可以通过 throw 关键字来抛出一个异常异常其实就是一个对象可以是基本类型也可以是自定义类型trytry 块中的代码标识“将被激活的特定异常”即可能抛出异常的代码块必须在 try 内它后面通常跟着一个或多个 catch 块。catch在您想要处理问题的地方通过异常处理程序捕获异常catch 关键字用于捕获异常可以有多个 catch 进行捕获。
PS如果有一个块抛出一个异常捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码try 块中的代码被称为保护代码然后后面通常跟着一个或多个 catch 块用来捕获不同类型的异常对象异常对象可以是基本类型也可以是自定义类型这个由自己来设定。
使用 try/catch 语句的语法格式如下所示
try
{// 可能抛出异常对象的代码
}
catch( A a ) //捕获 try 中抛出的类型为 A 的异常对象
{// 处理异常
}
catch( B b ) //捕获 try 中抛出的类型为 B 的异常对象
{// 处理异常
}
catch( C c ) //捕获 try 中抛出的类型为 C 的异常对象
{// 处理异常
}
...二. 为什么要引入抛异常机制
在 C 语言中传统的处理错误的方式有如下三个
方法一直接终止程序
如发生内存错误、除 0 错误时操作系统会给进程发送 SIGSEGV 信号、SIGFPE 信号来直接终止进程。如 assert(…) 函数。缺陷用户难以接受。
// 在 memcpy 函数的实现里一开始就要进行断言检查
void* memcpy(void* dest, const void* src, unsigned int num)
{//断言判断传入地址的有效性防止野指针assert(dest!NULL);assert(src!NULL);//...
}方法二返回错误码
缺陷需要程序员自己去查找对应的错误
比如系统的很多库的接口函数调用失败时都是通过把错误码放到 errno 中
又比如父进程等待子进程时通过一个输出型参数来解析子进程的退出状态
方法三C 标准库中的 setjmp 和 longjmp 组合
setjmp 和 longjmp是 C 标准库中的函数它们用于实现非局部跳转non-local jumps。这意味着你可以在程序的不同位置之间跳转而不仅仅是在函数之间跳转。
setjmp 函数原型如下
int setjmp(jmp_buf env);函数说明setjmp 函数用于保存程序的当前执行环境包括 CPU 寄存器和栈信息并将这些信息保存在 jmp_buf 类型的变量 env 中。然后setjmp 函数返回 0表示保存环境成功。
longjmp 函数的原型如下
void longjmp(jmp_buf env, int val);函数说明longjmp 函数用于从一个先前保存的环境通过 setjmp 函数保存的恢复程序的执行。它接受两个参数第一个参数是保存的环境 env第二个参数是一个整数值 val它指定了程序应该跳转到setjmp调用的位置并且可以传递一个值给 setjmp 函数。程序将继续执行就好像从 setjmp 返回一样。
总结 C 中处理异常的方式
实际中 C 语言基本都是使用返回错误码的方式处理错误部分情况下使用终止程序的方式处理非常严重的错误。
每次出错要么直接崩溃要么返回一个错误码可读性差还要自己对照错误码去找错误信息。使用抛异常的话当一个函数发现自己无法处理的错误时就可以抛出异常让这个函数的直接或间接调用者去自定义处理这个错误的方式。
三. 如何进行抛异常
1. 关于抛出的异常对象的类型
throw 抛出异常对象异常对象可以是任意类型
异常是通过抛出对象而引发的该对象的类型决定了应该激活对应 try 之下哪个 catch 的处理代码
2. 抛出的异常对象一定要有对应类型的捕获
异常是通过抛出对象而引发的该对象的类型决定了应该激活哪个 catch 的处理代码。如果没有捕获或者没有匹配类型的捕获则程序终止并报错: 补充 catch(…)可以捕获任意类型的异常问题是捕捉到之后不知道这个异常错误是什么 如果有异常被 catch(…) 捕获到了这个时候说明我们当前程序中还存在某种异常没有被去处理和考虑到
int Test()
{try{throw exception;}catch(...){cout 未知异常 endl;}return 0;
}//------输出结果------
未知异常3. 异常的执行流顺序
异常对象被抛出后执行流会直接跳转到捕获它的地方然后一直往下执行不会再回去
4. 在函数调用链中异常栈展开匹配原则
首先检查 throw 本身是否在 try 块内部如果在的话就去查找匹配的 catch 语句。如果有匹配的则调到 catch 的地方进行处理。没有匹配的 catch 则退出当前函数栈继续在调用函数的栈中进行查找匹配的 catch。如果到达 main 函数的栈依旧没有匹配的则终止程序。上述这个沿着调用链查找匹配的 catch 子句的过程称为栈展开。所以实际中我们最后都要加一个 catch(…) 捕获任意类型的异常否则当有异常没捕获程序就会报错终止。找到匹配的 catch 子句并处理以后会继续沿着 catch 子句后面继续执行
核心被选中的 catch 处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
示例函数多层调用时异常对象的被捕捉情况 PS在实际中一般都是把异常统一抛到最外层调用链main 函数去处理然后最外层调用链拿到这些异常后会写日志记录它们。
5. 规范的异常处理
在实际情况中可能存在许多种不同类型的异常我们可以包装一个类来统一描述异常对象异常的错误码和错误描述然后抛异常时直接抛出这个类的对象即可
#include iostream
using namespace std;// 自定义一个类用来封装异常对象包括异常信息和异常编号
class MyException
{
public:// 构造函数传入异常编号和异常信息MyException(const int id, const string msg):_errid(id), _errmsg(msg){}// 获取异常编号int GetErrId() const{return _errid;}// 获取异常信息string What() const{return _errmsg;}private:int _errid; //异常编号string _errmsg;//异常信息
};int Division()
{int a, b;cin a b;if (b 0) throw MyException(1, 除法除0错误);return a / b;
}int Remainder()
{int a, b;cin a b;if (b 0) throw MyException(1, 取模除0错误);return a % b;
}int main()
{try{Division();Remainder();}catch (const MyException e){cout 错误码 e.GetErrId() endl;cout 错误信息 e.What() endl;}return 0;
}现实中很多公司都会去自己定义公司专属的异常体系来让自己的异常管理规范化。如果一个项目中每个人都随意抛异常那么外层的调用者基本就没办法玩了他要对每种类型的异常对象去设定专门的 catch 捕捉处理。
更实用的是去定义一套继承的异常体系这样大家抛出的都是继承的派生类对象最外层调用者只需捕获一个基类对象就可以了因为派生类对象可以切片赋值给基类对象
总结建立一个异常继承体系抛出派生类对象使用基类对象来捕获这样只需写一个 catch 就能捕捉到多种类型的异常此外我们还可以借助多态来增强异常体系的灵活性。
四. 异常的重新抛出
C 中异常经常会导致资源泄漏的问题
比如在 new 和 delete 中间抛出了异常导致内存泄漏在 lock 和 unlock 中间抛出了异常导致死锁
下面演示在 new 和 delete 中间抛出异常导致内存泄漏的场景
解决办法在自己调用链中先把这个异常对象给捕了然后在 catch 中完成 delete 资源清理的工作之后再把这个异常重新抛出到上面的调用链
PS不过这种写法太挫了不推荐这样处理。在 C 更推荐使用 RAII 来解决这种情况。
五. 异常安全
在使用异常机制时应该考虑到以下几点
构造函数完成对象的构造和初始化最好不要在构造函数中抛出异常否则可能导致对象不完整或没有完全初始化。析构函数主要完成资源的清理最好不要在析构函数内抛出异常否则可能导致资源清理的不干净出现资源泄漏内存泄漏、句柄未关闭等的问题。C 中异常经常会导致资源泄漏的问题比如在 new 和 delete 中间抛出了异常导致内存泄漏在 lock 和 unlock 之间抛出了异常导致死锁不过这类情况可以通过 RAII 来避免。
六. 异常规范
C11 定义异常规范的目的是为了让函数使用者知道该函数可能抛出的异常有哪些
可以在函数的后面接 throw(类型)列出这个函数可能抛掷的所有异常的类型函数的后面接 throw() 或者 noexcept表示这个函数不抛异常若无异常接口声明则此函数内可以抛掷任何类型的异常
// 1、这里表示这个函数会抛出 A/B/C/D 中的某种类型的异常
void fun() throw(ABCD);// 2、这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);// 3、这里表示这个函数不会抛出异常(下面两种写法等价)
// void* operator new (std::size_t size, void* ptr) noexcept;
void* operator new (std::size_t size, void* ptr) throw();查阅 C 官方文档时可以看到有些函数标明了 noexcept表示该函数不会抛出异常 补充函数的异常规范可以是函数更干净但它并不是强制的C 标准委员会也不敢给这种规范设计成强制的因为之前的很多企业已经累计了很多的 C 代码如果把这个规范设计成强制的话之前的代码都会编译不通过。不过我们自己写代码还是遵守这个规范比较好。
七. 异常的优缺点
C 异常的优点
异常对象定义好了相比只返回一个错误码的方式异常对象可以清晰准确的展示出错误的各种信息甚至可以包含堆栈调用的信息这样可以帮助我们更好的定位程序的 bug。返回错误码的传统方式有个很大的问题就是在函数调用链中深层函数返回了错误码之后之前每个调用该函数的地方都需要检查返回值那么我们得层层返回错误直到最终最外层拿到错误码后才结束而 C 中的异常让错误的处理简化了出错的地方只管抛出异常即可然后可以直接跳转到对这个异常对象的捕捉处。很多知名的第三方库都包含异常比如 boost、gtest、gmock 等等常用的库如果我们的项目不用的话就不能很好的配合。很多测试框架都使用异常这样能更好的使用单元测试等进行白盒的测试部分函数使用异常更好处理比如构造函数没有返回值不方便使用错误码方式处理。比如 T operator[ ] (size_t pos) 这样的函数如果 pos 越界了只能使用异常或者终止程序处理没办法通过返回值表示错误。
C 异常的缺点
异常会导致程序的执行流乱跳并且非常的混乱。特别是在调试的时候如果抛异常了它会直接跳过断点而去到对应的 catch 中这会导致我们跟踪调试信息以及分析程序时比较困难。异常会有一些性能的开销。当然在现代硬件速度很快的情况下这个影响基本忽略不计。C 没有垃圾回收机制资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用 RAII 来处理资源的管理问题学习成本较高。C 标准库的异常体系定义得不好导致大家各自定义各自的异常体系非常的混乱。异常尽量规范使用否则后果不堪设想随意抛异常会让外层捕获的用户苦不堪言。所以异常规范有两点一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常都使用 func() throw(); 的方式规范化。
总结异常总体而言利大于弊所以工程中我们还是鼓励使用异常的。另外 OO 的语言基本都是用异常来处理错误的异常一定是未来的大势所趋。
八. C 标准库的异常体系
C 提供了一系列标准的异常定义在 exception 头文件中我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的如下所示
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/85979.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!