目录
1.异常 vs Bug
• Bug:程序出错、有 逻辑错误 ,无论输入数据是什么都无法得到正确结果(比如程序员在写除法代码,除号写成乘号,无论用户输入啥数据得到的结果都是错误的)• 异常:程序逻辑是正确的,但遇到了 正常范围之外 的输入数据而发生意外中断• 对于Bug:程序员可控,越用心进行开发/走查/测试,Bug越少• 对于Exception:程序员不可控,需要代码中设置针对异常情况的 保护 措施•网络应用:网络连通性、时延等不可控因素对于程序来说是异常• 游戏:玩家打出极限操作、突破预先设置的情况是异常• 数据分析:数据缺失值、未曾料想的特殊值等情况是异常• ......• 总之,程序正确编译了,也能按照既定逻辑执行功能,但是遇到了合法范围外的数据• 导致程序失去 确定性 而中断这个时候我们需要写一种保护程序,使之发生异常的时候不要终端,与其卡死,不如继续走下去
2.throw 和 try - catch
2.1异常处理 Exception Handle
• 异常导致程序失去确定性,不知道怎么继续下去了• 异常保护:引导程序继续走下去,再不济也要做好收尾工作( 如释放占用的内存 ),体面结束try - throw - catch保护 抛出 捕获• 抛出异常 • 谁来捕获
• 处理完从哪继续#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int main() { int a = 0; int b = 0; int c = 0; cin >> a >> b; try { if (b == 0) { throw(string("b is 0,exception throw")); } c = a / b; } catch (string e) { cout << e << endl; c = 0; } cout << "c=" <<c<< endl; return 0; }
2.2throw 和 try - catch
• throw: 使用throw语句主动立即抛出一个异常 对象 (但不一定throw才抛异常,如n/0)• try: 保护一个语句块,后跟 一个或多个 catch块。try块中抛出异常不会立刻中止程序而是寻找catch处理• catch: 捕获 指定 异常并进行处理,处理完 不会再返回 try块,直接离开try-catch继续执行
try { // 被保护的代码段,可以理解为试一试,看能否成功运行 throw(异常对象); } catch(期望捕获类型 变量名) { // 处理异常的代码段 } 其中异常对象可以是任何东西,但通常是 std::exception 的子类对象
try { throw(1); cout << "After throw";//不会被执行 } catch(int e) { cout << e;//会输出1 } // catch处理完从这里继续
2.3 多个catch块:异常捕获顺序
try { ... throw(something); ... } catch( Type1 e1 ) { // catch 块1 } catch( Type2 e2 ) { // catch 块2 } ... catch( Type3 eN ) { // catch 块N } • 从上到下依次尝试,直至类型匹配 • 一个异常只会被捕获一次
特别注意抛出对象是继承类,细节注意 若 A←B←C,哪种catch块排序合理? 1.对 try { ... } catch( C e) { ... } catch( B e) { ... } catch( A e) { ... } 2.错 try { ... } catch( A e) { ... } catch( B e) { ... }//不可能捕获到东西 catch( C e) { ... }//不可能捕获到东西
2.4捕获一切异常:catch(...)
try { throw(something); } catch( Type1 e1 ) { // catch 块1 } catch( Type2 e2 ) { // catch 块2 } catch(...) { // 处理任何异常 }
• catch(...)能捕获一切异常,它后面的其他catch块等于没写 • 因此catch(...)应放在多个catch块的最后一个
2.5函数中抛出异常
void funcA() { try { funcB(); } catch (E6 e) { } catch (E7 e) { } // a处 }
void funcB() { try { funcC(); } catch (E4 e) { } catch (E5 e) { } // b处 }
void funcC() { try { E6 e; throw(e); } catch (E1 e) { } catch (E2 e) { } catch (E3 e) { } // c处 }
2.6函数异常限定符
• 异常限定符:显式地指明是否允许函数抛出(哪些)异常
void func1 () throw() ;--------------------------->什么都不允许抛出void func2 () throw(int,double) ;-------------->允许抛出int或doublevoid func3 () throw(…) ;------------------------>允许抛出任何东西3.自定义异常类
3.1C++标准异常类
• 虽说我们可以手动throw,抛出什么都可以,但一些常见的程序错误是C++自身抛出的(如除以零)• C++ 根据错误分类根据预设了一系列 标准异常 ,定义在 <exception> 中• 它们以 父子类 层次结构组织,都在名字空间 std 下![]()
3.2自定义异常类
• 自定义异常类通常继承自std::exception,并重写一些方法,比如what()• 通常在自己编写的程序中会根据业务需求自定义异常类自定义异常类例子:三角形异常类#include<iostream> using namespace std; class TriangleException :public exception { public: float a; float b; float c; TriangleException(float a,float b,float c):a(a),b(b),c(c){} const char* what()const override { return "Invalid triangle exception!"; } }; //通过海伦公式计算三角形面积 float get_triangle_area(float a, float b, float c)throw(TriangleException) { if (a + b <= c || a + c <= b || b + c <= a) { throw(TriangleException(a, b, c)); } float p = (a + b + c) / 2.f; float s = p * (p - a) * (p - b) * (p - c); return sqrtf(s); } int main() { float a, b, c, result; cin >> a >> b >> c; try { result = get_triangle_area(a, b, c); } catch(TriangleException e) { cout << e.what() << endl; return 0.f; } cout << "result=" << result << endl; return 0; }
4.例题
⭐ Key 1 :try-throw-catch分别用于保护代码段、手动抛出异常与捕获异常⭐ Key 2 :抛出的异常会按照其类型被首个匹配的catch块捕获(若有)⭐ Key 3 :抛出的异常只会在首次匹配的catch块处被捕获一次⭐ Key 4 :catch(...){ } 能捕获任何异常1.关于C++的异常处理,正确的说法是:AA.try必须和catch连用B.throw必须和catch连用 (抛出异常不一定有匹配的catch捕获)C.try语句块中必须有throwD.try-throw-catch这三个关键字必须一起使用分析:异常对象可以通过throw主动抛出,也可能某处代码执行失败自动抛出(如除以0)2. 填空:(1)异常是通过_ 保护 _ 抛出和捕获 _____来实现的。(2)网站后端程序遇到网络不稳定断连是程序_ 异常_ , 误将用户名作为密码进行登录验证是程序_ 错误 _(3)C++程序中,异常处理的主要任务是_ 处理程序在运行时遇到的问题,尝试回复运行或善后 _3.假设funcA被调用,则最终将进入哪个catch块,并在处理完后从何处继续执行程序:CA. ⑤, c处B. ④, b处C. ①, a处D. ①, c处void funcA() { try { funcB(); } catch (E6 e) {//①} catch (E7 e) {//②} // a处 }
void funcB() { try { funcC(); } catch (E4 e) {//③} catch (E5 e) {//④} // b处 }
void funcC() { try { E6 e; throw(e); } catch (E1 e) {//⑤} catch (E2 e) {//⑥} catch (E3 e) {//⑦} // c处 }
4.假设funcA被调用,则最终将进入哪个catch块,并在处理完后从何处继续执行程序:B
A. ⑤, c处B. ④, b处C. ①, a处D. ①, c处void funcA() { try { funcB(); } catch (E6 e) {//①} catch (E7 e) {//②} // a处 }
void funcB() { try { funcC(); } catch (E4 e) {//③} catch (...) {//④} // b处 }
void funcC() { try { E6 e; throw(e); } catch (E1 e) {//⑤} catch (E2 e) {//⑥} catch (E3 e) {//⑦} // c处 }