C++(异常处理)
异常处理
1.概念:
异常是指程序在执行期间产生的问题,C++的异常是值在程序运行时发生的特殊情况,比如at函数的下标访问越界等。
2.特点:
(1)异常提供了一种转义控制权的方式。
控制权转移的本质:
当程序中发生异常时,它会中断当前的正常执行流程,将程序的控制权(即代码执行权)从当前点转移到专门处理错误的异常处理代码块。
(2)程序一旦出现异常没有经过处理,就会造成程序奔溃。
(3)异常的处理方式有两种,抛出异常(throw)和捕获异常(try-catch)。
2.抛出异常:
2.1 特点:
使用throw语句在代码块中任何地方都可以抛出异常。
throw语句的操作数可以是任意的表达式,表达式的结果决定了抛出异常的类型。
抛出的异常是抛出到函数调用的上一级。
2.2 自动抛出异常:
使用C++内置函数,在遇到一些错误情况会自动抛出异常错误,.at就是,如果越界就会抛出异常错误,上面那个图就是。
2.3 手动抛出异常:
设置string类型的异常信息test,当b是零的时候(被除数不能为零),a除以b不会计算,而是直接返回到上一级在division()位置抛出异常错误,如果后面没有对异常处理,就会继续返回上一级在iniput()位置抛出异常错误,如果还是无人处理就会程序崩溃,显示异常信息到终端。(不会显示设置的异常信息)
#include <iostream>using namespace std;double division(double a,double b){if(b == 0){//int test(0);string test("输入的第一个数是零");throw test;}return a/b;
}double input(){double a,b;cout << "输入除数和被除数:" << endl;cin >> a >> b;double c = division(a,b); //第一个异常抛出,无人处理就会继续向上抛出异常cout << "结果是:" << c << endl;return c;
}int main (){cout << "程序开始" << endl;input(); //第二次异常抛出位置,依旧无人处理,程序卡死cout << "程序结束" << endl;return 0;
}
3.捕获异常:
使用try去尝试运行可能会抛出错误的语句,然后在后面使用catch捕获try内语句可能抛出的异常。
3.1 解析:
上的程序在两个位置抛出了异常,我们在这两个位置都进行异常捕获,我们设置的异常信息是string类型的,但是我第一次尝试捕获的类型是double,捕获异常失败,会继续向上抛出异常,在第二次抛出异常的位置捕获string类型的异常,捕获成功,执行捕获到异常后的操作:先使用.what()打印异常信息,然后可以错误处理,退出当前函数体或者不写,不写就继续执行函数其他语句。
3.2 三种情况:
如果没有异常抛出,程序正常执行;
如果有异常抛出,正确捕获异常并且执行catch块;
如果有异常抛出,错误捕获异常,程序会继续向上抛出异常,如果每一层都没有正确捕获,程序仍然终止。
#include <iostream>using namespace std;double division(double &a,double &b){if(b == 0){//int test(0);string test("输入的第一个数是零");throw test;}return a/b;
}double input(){double a,b;cout << "输入除数和被除数:" << endl;cin >> a >> b;double c;try{c = division(a,b); //尝试运行函数}catch(double &e){ //类型不匹配会捕获失败,向上抛出异常//打印错误信息cout << e << endl;//错误处理return 0;}cout << "结果是:" << c << endl;return c;
}int main (){cout << "程序开始" << endl;try{input(); //尝试运行}catch(string &e){ //此处捕获成功成功cout << e << endl;return 0;}cout << "程序结束" << endl;return 0;
}
没有打印程序结束,因为最外层捕获到异常打印后直接退出程序了。
4.标准异常体系
4.1 基础知识:
C++给常见的异常类型进行了定义和分类,引入头文件#include<stdexcept>后可以使用。
C++中这个体系比较薄弱,就是类型比较少,可以自行扩展。
自定义一个类型,继承某个异常类型后就可以使用,在C++中异常类型也是被做成类,然后继承出多个派生类来使用的。左边图中最上面的异常类型就死所有异常的基类,依次往后继承出多个派生类异常类型。
4.2 尝试捕获out_of_range标准异常
#include <iostream>
#include <stdexcept>using namespace std;int main (){cout << "程序开始" << endl;string s("hello");try{s.at(100);}catch(out_of_range &e){cout << e.what() << endl;}cout << "程序结束" << endl;return 0;
}
这就是越界的时候,错误的详细信息,不捕获打印详细信息就只能看到上面的错误类型,还会使程序奔溃。正常处理完异常后还可以继续运行其他程序语句。
4.3 自定义类型异常
继承的是所有异常类型的基类,开创一个新的异常类型分支,然后重新定义异常信息。
#include <iostream>
#include <stdexcept>using namespace std;class MyEcepthion:public exception{
public:// 覆盖what函数// throw():异常规格说明// 表示此函数不会出现任何异常的抛出const char * what() const throw(){ //自定义类型异常return "自定义异常";}};int Mstrcmp(string a, string b){if(a == "1" || b == "1"){ //输入字符串是1throw MyEcepthion(); //抛出异常}return 0;
}int input(){string a;string b;cout << "输入两个字符串:" << endl;cin >> a >> b;try{Mstrcmp(a,b);}catch(MyEcepthion &e){ //捕获异常cout << e.what() << endl;return 0;}return 1;
}int main (){cout << "程序开始" << endl;input();cout << "程序结束" << endl;return 0;
}
5.多重捕获
一个try块可以配合多个catch块同时匹配,因为一个catch块只能捕获一种类型错误信息,如果try内语句设置多中类型的异常,应该对多种异常都进行捕获。
#include <iostream>
#include <stdexcept>using namespace std;class MyEcepthion:public exception{
public:const char * what() const throw(){ //自定义类型异常return "自定义异常";}};int input(int type){try{if(type == 1){string a = "hello";cout << a.at(100) << endl; //抛出越界异常}else if(type == 2){throw overflow_error("这是一个神奇的错误"); //抛出溢出错误}else{throw MyEcepthion(); //抛出自定义异常}}catch(out_of_range &e){ //捕获越界异常cout << e.what() << endl;return 0;}catch(overflow_error &e){ //捕获溢出异常cout << e.what() << endl;return 0;}catch(MyEcepthion &e){ //捕获自定义异常cout << e.what() << endl;return 0;}return 1;
}int main (){cout << "程序开始" << endl;int type;cin >> type; //输入类型input(type);cout << "程序结束" << endl;return 0;
}
输入1:
输入2:
输入3:
6.粗略捕获
6.1 粗略捕获标准异常
catch内捕获异常的类型设置为标准异常的基类,这样就可以捕获所有派生类异常,这提现了多态的性质,基类指针指向(引用)派生类对象)。
特点:
越粗略的捕获所处位置越靠后,目的是捕获精确捕获没有捕获到的异常,防止程序崩溃。
#include <iostream>
#include <stdexcept>using namespace std;class MyEcepthion:public exception{
public:const char * what() const throw(){ //自定义类型异常return "自定义异常";}};int input(int type){try{if(type == 1){string a = "hello";cout << a.at(100) << endl; //抛出越界异常}else if(type == 2){throw overflow_error("这是一个神奇的错误"); //抛出溢出错误}else{throw MyEcepthion(); //抛出自定义异常}}catch(out_of_range &e){ //捕获越界异常cout << e.what() << endl;return 0;}/*catch(overflow_error &e){ //捕获溢出异常cout << e.what() << " 溢出异常" << endl;return 0;}*/catch(MyEcepthion &e){ //捕获自定义异常cout << e.what() << endl;return 0;}catch(exception &e){ //粗略捕获,类型2被注释掉,只能被粗略捕获cout << e.what() << " 粗略捕获" << endl; return 0;}return 1;
}int main (){cout << "程序开始" << endl;int type;cin >> type; //输入类型input(type);cout << "程序结束" << endl;return 0;
}
6.2 捕获所有异常
可以使用...代替catch内的内容,表示捕获所以类型的异常,包括throw抛出的int等类型数据。
#include <iostream>
#include <stdexcept>using namespace std;class MyEcepthion:public exception{
public:const char * what() const throw(){ //自定义类型异常return "自定义异常";}};int input(int type){try{if(type == 1){string a = "hello";cout << a.at(100) << endl; //抛出越界异常}else if(type == 2){throw overflow_error("这是一个神奇的错误"); //抛出溢出错误}else if(type == 3){throw MyEcepthion(); //抛出自定义异常}else{ //抛出int类型的数据throw 2;}}catch(out_of_range &e){ //捕获越界异常cout << e.what() << endl;return 0;}/*catch(overflow_error &e){ //捕获溢出异常cout << e.what() << " 溢出异常" << endl;return 0;}*/catch(MyEcepthion &e){ //捕获自定义异常cout << e.what() << endl;return 0;}catch(exception &e){ //粗略捕获cout << e.what() << " 粗略捕获" << endl;return 0;}catch(...){ //最后收尾,捕获所有类型异常,包括数据cout << "捕获全部异常" << endl;}return 1;
}int main (){cout << "程序开始" << endl;int type;cin >> type; //输入类型input(type);cout << "程序结束" << endl;return 0;
}