• C++学习39 异常处理入门(try和catch)


    编译器能够保证代码的语法是正确的,但是对逻辑错误和运行时错误却无能为力,例如除数为 0、内存分配失败、数组越界等。这些错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。

    优秀的程序员能够从故障中恢复,或者提示用户发生了什么;不负责任的程序员放任不管,让程序崩溃。C++提供了异常机制,让我们能够捕获逻辑错误和运行时错误,并作出进一步的处理。

    一个程序崩溃的例子:

    #include <iostream>
    using namespace std;
    int main(){
        string str = "c plus plus";
        char ch1 = str[100];  //下标越界,ch1为垃圾值
        cout<<ch1<<endl;
        char ch2 = str.at(100);  //下标越界,抛出异常
        cout<<ch2<<endl;
        return 0;
    }

    运行代码,在控制台输出 ch1 的值后程序崩溃。下面我们来分析一下。

    at() 是 string 类的一个成员函数,它会根据下标来返回字符串的一个字符。与“[ ]”不同,at() 会检查下标是否越界,如果越界就抛出一个异常(错误);而“[ ]”不做检查,不管下标是多少都会照常访问。

    上面的代码中,下标 100 显然超出了字符串 str 的长度。由于第 6 行代码不会检查下标越界,虽然有逻辑错误,但是程序能够正常运行。而第 8 行代码则不同,at() 函数检测到下标越界会抛出一个异常(也就是报错),这个异常本应由程序员处理,但是我们在代码中并没有处理,所以系统只能执行默认的操作,终止程序执行。

    捕获异常

    在C++中,我们可以捕获上面的异常,避免程序崩溃。捕获异常的语法为:

    try{
        // 可能抛出异常的语句
    }catch(异常类型){
        // 处理异常的语句
    }

    try 和 catch 都是C++中的关键字,后跟语句块,不能省略“{ }”。try 中包含可能会抛出异常的语句,一旦有异常抛出就会被捕获。从“try”的意思可以看出,它只是“尝试”捕获异常,如果没有异常抛出,那就什么也不捕获。catch 用来处理 try 捕获到的异常;如果 try 没有捕获到异常,就不会执行 catch 中的语句。

    修改上面的代码,加入捕获异常的语句:

    #include <iostream>
    using namespace std;
    int main(){
        string str = "c plus plus";
       
        try{
            char ch1 = str[100];
            cout<<ch1<<endl;
        }catch(exception e){
            cout<<"[1]out of bound!"<<endl;
        }
        try{
            char ch2 = str.at(100);
            cout<<ch2<<endl;
        }catch(exception e){
            cout<<"[2]out of bound!"<<endl;
        }
        return 0;
    }

    可以看出,第一个 try 没有捕获到异常,输出了一个垃圾值。因为“[ ]”不会检查下标越界,不会抛出异常,所以即使有逻辑错误,try 什么也捕获不到。

    第二个 try 捕获到了异常,并跳转到 catch,执行 catch 中的语句。需要说明的是,异常一旦抛出,会立即被捕获,而且不会再执行异常点后面的语句。本例中抛出异常的位置是第 15 行的 at() 函数,它后面的 cout 语句不会再被执行,也就看不到输出。

    异常类型

    所谓抛出异常,实际上是创建一份数据,这份数据包含了错误信息,程序员可以根据这些信息来判断到底出了什么问题,接下来该怎么处理。

    异常既然是一份数据,那么就应该有数据类型。C++规定,异常类型可以是基本类型,也可以是标准库中类的类型,还可以是自定义类的类型。C++语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的类型。也就是说,抛出异常时,会创建一个 exception 类或其子类的对象。

    异常被捕获后,会和 catch 所能处理的类型对比,如果正好和 catch 类型匹配,或者是它的子类,那么就交给当前 catch 块处理。catch 后面的括号中给出的类型就是它所能处理的异常类型。上面例子中,catch 所能处理的异常类型是 exception,at() 函数抛出的类型是 out_of_range,out_of_range 是 exception 的子类,所以就交给这个 catch 块处理。

    catch 后面的exception e可以分为两部分:exception 为异常类型,e 为 exception 类的对象。异常抛出时,系统会创建 out_of_range 对象,然后将该对象作为“实参”,像函数一样传递给“形参”e,这样,在 catch 块中就可以使用 e 了。

    其实,一个 try 后面可以跟多个 catch,形式为:

    try
    {
        //可能抛出异常的语句
    }
    catch (exception_type_1)
    {
        //处理异常的语句
    }
    catch (exception_type_2)
    {
        //处理异常的语句
    }
    // ……
    catch (exception_type_n)
    {
        //处理异常的语句
    }

    异常被捕获时,先和 exception_type_1 作比较,如果异常类型是 exception_type_1 或其子类,那么执行当前 catch 中的代码;如果不是,再和 exception_type_2 作比较……依此类推,直到 exception_type_n。如果最终也没有找到匹配的类型,就只能交给系统处理,终止程序。

    多个 catch 块的例子:

    #include <iostream>
    #include <exception>
    #include <stdexcept>
    using namespace std;
    //自定义错误类型
    class myType: public out_of_range{
    public:
        myType(const string& what_arg): out_of_range(what_arg){}
    };
    int main(){
        string str = "c plus plus";
        try{
            char ch1 = str.at(100);
            cout<<ch1<<endl;
        }catch(myType){
            cout<<"Error: myType!"<<endl;
        }catch(out_of_range){
            cout<<"Error: out_of_range!"<<endl;
        }catch(exception){
            cout<<"Error: exception!"<<endl;
        }
        return 0;
    }

    try 捕获到异常后和第一个 catch 对比,由于此时异常类型为 out_of_range,myType 是 out_of_range 的子类,所以匹配失败;继续向下匹配,发现第二个 catch 合适,匹配结束;第三个 catch 不会被执行。

    需要注意的是:catch 后面的括号中仅仅给出了异常类型,而没有所谓的“形参”,这是合法的。如果在 catch 中不需要使用错误信息,就可以省略“形参”。

  • 相关阅读:
    打开网页总结
    学期总结
    总结
    Sprint3
    Sprint2团队贡献分
    6.14
    典型用户与场景
    5种创建型模式
    JAVA 将接口的引用指向实现类的对象
    Java里的接口
  • 原文地址:https://www.cnblogs.com/Caden-liu8888/p/5837477.html
Copyright © 2020-2023  润新知