六十三、C 语言异常处理
1、异常处理
- 异常的概念
- 程序在运行过程中可能产生异常
- 异常(Exception)与Bug的区别
- 异常是程序运行时可预料的执行分支
- Bug是程序中的错误,是不被预期的运行方式
- 异常(Exception)和Bug的对比:
- 异常
- 运行时产生除0的情况
- 需要打开的外部文件不存在
- 数组访问时越界
- Bug
- 使用野指针
- 堆数组使用结束后未释放
- 选择排序无法处理长度为0的数组
- C语言经典处理方式:if ...else ..
2、编程实验:除法操作异常处理
3、异常处理的方式
- 缺陷
- divide函数有3个参数,难以理解其用法
- divide函数调用后必须判断valid代表的结果
- 当valid为true时,运算结果正常
- 当valid为false 时,运算过程出现异常
- 通过setjmp()和longjmp()进行优化
- int setjmp(jmp_buf env)
- void longjmpjmp_buf env, int val)
- 从jmp_buf结构体中恢复setjmp()保存的上下文
- 最终从setjmp函数调用点返回,返回值为val
4、编程实验:除法操作异常处理优化
5、异常处理的方式
- 缺陷
- setjmp()和longjmp()的引入
- 必然涉及到使用全局变量
- 暴力跳转导致代码可读性降低
- 本质还是if...else...异常处理方式
- C语言中的经典异常处理方式会使得程序中逻辑中混入大量的处理异常的代码。
- 正常逻辑代码和异常处理代码混合在一起,导致代码迅速膨胀,难以维护。。 。
6、实例分析:异常处理代码分析
7、问题
C++中有没有更好的异常处理方式?
8、小结
- 程序中不可避免的会发生异常
- 异常是在开发阶段就可以预见的运行时问题
- C语言中通过经典的if ... else ...方式处理异常
- C++中存在更好的异常处理方式
六十四、C++ 中的异常处理(上)
1、C++异常处理
- C++内置了异常处理的语法元素try ... catch…
- try语句处理正常代码逻辑
- catch语句处理异常情况
- try语句中的异常由对应的catch语句处理
- C++异常处理分析
- throw抛出的异常必须被catch处理
- 当前函数能够处理异常,程序继续往下执行
- 当前函数无法处理异常,则函数停止执行,并返回
- 未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,否则程序将停止执行。
2、编程实验:C++异常处理初探
3、C++异常处理
- 同一个try语句可以跟上多个catch语句
- catch语句可以定义具体处理的异常类型
- 不同类型的异常由不同的catch语句负责处理
- try语句中可以抛出任何类型的异常
- catch(...)用于处理所有类型的异常
- 任何异常都只能被捕获( catch )一次
- 异常处理的匹配规则
4、编程实验:异常类型匹配
5、小结
- C++中直接支持异常处理的概念
- try ... catch ...是C++中异常处理的专用语句
- try语句处理正常代码逻辑,catch语句处理异常情况
- 同一个try语句可以跟上多个catch语句
- 异常处理必须严格匹配,不进行任何的类型转换
六十五、C++ 中的异常处理(下)
1、C++中的异常处理
2、问题
为什么要在catch中重新抛出异常?
3、C++中的异常处理
- catch中捕获的异常可以被重新解释后抛出
- 工程开发中使用这样的方式统一异常类型
4、编程实验:异常的重新解释
5、C++中的异常处理
- 异常的类型可以是自定义类类型
- 对于类类型异常的匹配依旧是至上而下严格匹配
- 赋值兼容性原则在异常匹配中依然适用
- 一般而言
- 匹配子类异常的catch放在上部
- 匹配父类异常的catch放在下部
- 在工程中会定义一系列的异常类
- 每个类代表工程中可能出现的一种异常类型
- 代码复用时可能需要重解释不同的异常类
- 在定义 catch语句块时推荐使用引用作为参数
6、编程实验:类类型的异常
7、C++中的异常处理
- C++标准库中提供了实用异常类族
- 标准库中的异常都是从exception类派生的
- exception类有两个主要的分支
- logic_error
- runtime_error
- 标准库中的异常
8、编程实验:标准库中的异常使用
9、小结
- catch语句块中可以抛出异常
- 异常的类型可以是自定义类类型
- 赋值兼容性原则在异常匹配中依然适用
- 标准库中的异常都是从exception类派生的
六十六、C++ 中的类型识别
1、类型识别
- 静态类型 - 变量(对象)自身的类型
- 动态类型 - 指针(引用)所指向对象的实际类型
- 基类指针是否可以强制类型转换为子类指针取决于动态类型!
2、问题
C++中如何得到动态类型?
3、动态类型识别
- 解决方案 - 利用多态
- 在基类中定义虚函数返回具体的类型信息
- 所有的派生类都必须实现类型相关的虚函数
- 每个类中的类型虚函数都需要不同的实现
4、编程实验:动态类型识别
5、动态类型识别
- 多态解决方案的缺陷
- 必须从基类开始提供类型虚函数
- 所有的派生类都必须重写类型虚函数
- 每个派生类的类型名必须唯一
6、类型识别关键字
- C++提供了typeid关键字用于获取类型信息
- typeid 关键字返回对应参数的类型信息
- typeid返回一个type_info类对象
- 当typeid的参数为NULL时将抛出异常
- typeid关键字的使用
7、动态类型识别
- typeid的注意事项
- 当参数为类型时:返回静态类型信息
- 当参数为变量时:
- 不存在虚函数表 - 返回静态类型信息
- 存在虚函数表 - 返回动态类型信息
8、编程实验:typeid类型识别
9、小结
- C++中有静态类型和动态类型的概念
- 利用多态能够实现对象的动态类型识别
- typeid是专用于类型识别的关键字
- typeid能够返回对象的动态类型信息
六十七、经典问题解析五
1、面试问题
编写程序判断一个变量是不是指针。
2、指针的判别
- 拾遗
- C++中仍然支持C语言中的可变参数函数
- C++编译器的匹配调用优先级
- 思路
- 将变量分为两类:指针 vs 非指针
- 编写函数:
- 指针变量调用时返回true
- 非指针变量调用时返回false
3、编程实验:指针判断
4、指针的判别
- 存在的缺陷:
- 进一步的挑战:
- 如何让编译器精确匹配函数,但不进行实际的调用?
- 利用 sizeof 在编译期间就可以获得字节数。而不必执行函数。
5、面试问题
如果构造函数中抛出异常会发生什么情况?
6、构造中的异常
- 构造函数中抛出异常
- 构造过程立即停止
- 当前对象无法生成
- 析构函数不会被调用
- 对象所占用的空间立即收回
- 工程项目中的建议
- 不要在构造函数中抛出异常
- 当构造函数可能产生异常时,使用二阶构造模式
7、编程实验:构造中的异常
8、析构中的异常
避免在析构函数中抛出异常!!!
析构函数的异常将导致:
对象所使用的资源无法完全释放。
9、小结
- C++中依然支持变参函数
- 变参函数无法很好的处理对象参数
- 利用函数模板和变参函数能够判断指针变量
- 构造函数和析构函数中不要抛出异常
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |