1. C语言传统的处理错误的方式
错误处理机制: 1.终止程序 如:assert 断言终止 ,会直接报告出现错误的位置 (assert只在debug版本生效)
如:发生内存错误,或者除0错误时,导致终止程序
2. 返回错误码 错误码本质就是一个编号,不能包含更多的信息,就需要程序员自己去查对应的错误信息,很不方便 所以为了在产生错误时,有更丰富的信息显示, C 就提出了异常
异常 可以抛一个对象出来,对象可以包含各种各样的信息
2. C 异常
概念
异常是一种处理错误的方式 当一个函数发现自己无法处理的错误时,就可以抛异常,让函数直接或者间接的调用者处理这个错误
用法
分为抛出异常 (throw)和 捕获异常(try catch)
throw:出现问题时,使用 thow关键字 进行 抛异常 catch : 在想要处理问题的地方, 通过 异常处理程序 捕获异常, catch 关键字用于 捕获异常 try :try块中代码块标识将被激活的特定异常,后面通常跟 catch块
抛异常,异常必须被捕获 ,若没有被捕获就会报错
该图程序中只有抛异常,没有捕获异常存在,所以当b为0时,程序直接报错
抛异常和捕获的位置可以有很多个地方
当发现错误时,一般是由最外层处理 如:上图程序中先由main 函数调用Func,再由Func函数调用 Division,在Division中完成抛异常 所以应在最外层的main函数中捕获异常
异常的抛出和匹配原则
规则1
异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码
catch时,需要跟throw抛出对象类型 进行匹配 如:此时的throw传过来的是字符串,所以catch 用const char*接收
由于有捕获异常,所以当再次b为0时,就不会报错了,显示的详细信息为Division by zero condition!
try和catch 两者是配对的, catch 只能捕获 try里面的抛的异常 如:在主函数中的catch 捕获异常只能 捕获 Func函数中抛的异常
情况2
被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
抛出异常位置最近的验证
若在Func函数处添加捕获,并且类型与对象类型匹配 则当b为0时,由于Func函数处 更近,所以在Func函数处捕捉异常,而不在main函数中捕获异常
对象类型匹配的验证
此时Func函数中的捕获异常与对象类型不匹配,当再次输入b为0时,在main函数处 捕获异常
规则3
若Func函数和main函数的捕获异常 与对象类型都不匹配 ,则程序会报错
此时由于两个捕获异常都与对象类型不匹配,所以进入catch(...)中
使用catch(...),若有匹配的就用匹配的,若没有匹配的,就使用catch(...),对任意类型异常进行捕获 防止一些异常没有捕获(没有对象类型匹配),导致程序终止
规则4
抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似 于函数的传值返回)
如果错误信息只是一个字符串,有些过于简单,所以设置一个类,内部包含错误码和错误描述
由于成员变量是私有的,在类外可能拿不到,所以设置两个函数,通过函数返回值的方式取到错误码和错误描述
由于对象类型为 const Exception,所以想要使用对象取到这两个函数 ,就需要在外部加上const 修饰
对比上面,将字符串替换成了对象,对象含有错误码和错误描述两部分 通过抛异常的方式将对象 传递给 catch的捕获 在将对象的错误码和错误信息打印出来
抛异常时,并不是把e1直接传给 e 因为e1是一个局部对象,出了作用域就销毁了,会产生一个临时对象,将e1对象的错误码和错误描述拷贝给临时对象 再通过临时对象 传给 对象e,在catch结束后,临时对象销毁
异常的重新抛出
若抛异常,则会导致内存泄漏(没有使用delete释放)
若要求在main函数将异常处理, 所以可以采用异常的重新抛出 当在Func函数中的catch要捕获异常时,再将异常抛出,使main函数中进行 捕获异常
异常规范
在C 98中 exception() throw(); 后面加了个throw,意思为声明这个函数不会抛异常
声明可以不给,但是加上会让人更容易理解 这个函数异常声明并不是强制的,并且比较繁琐,就导致很多人不遵循这个规范
在C 11中 若一个函数明确不抛异常的话,就加 noexcept 可能会抛异常,就什么都不加
异常的优缺点
优点
1.相比错误码的方式,可以清晰的展示出错误的各种信息 附带各种想要的数据,如:sql语句
2.返回错误码的方式,是需要层层返回的,就代表一层一层处理,直到最外层才能拿到错误 而抛异常,就会直接跳到最外层拿到错误
3. 很多的第三方库都包含异常,比如boost、gtest(单元测试)、gmock(打桩测试)等等常用的库,那么我们使用它们 也需要使用异常
4. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如 T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回 值表示错误
缺点
1.异常会导致执行流 乱跳(抛异常会直接跳到catch处) ,就会导致分析程序很困难
2. C 没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常 安全问题
3. C 标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱