c++的异常处理

来源:互联网 发布:六年级上册优化答案 编辑:程序博客网 时间:2024/05/22 15:28

1. c++的异常处理介绍

  c++的异常处理语法:

int main(void){    try    {        double a = div(6, 0);    }    catch(...)    {        printf("div 0 is error!!\n");    }       return 0;}

  如上try…catch两个语句块,将正常功能代码和异常处理代码在一个函数中分隔开,提高代码的可读性:try语句用于处理正常代码逻辑,catch语句用于处理异常情况,try语句中的异常由对应的catch语句处理。

  在div()函数中,若发现除以0操作,可以通过throw语句抛出异常,并终止本函数运行:

double div(double a, double b){    double tmp = 0.00000001;    if ((-tmp < b) && (b < tmp))    {        throw 0;    }    else        return a / b;}

  编译运行:
这里写图片描述

  在div()函数中抛出的异常必须被catch处理,当前函数能够处理异常,程序则继续往下执行,当前函数无法处理异常,则函数停止执行并返回。未被处理的异常会顺着函数调用栈向上抛出,直至被处理为止,
这里写图片描述

  若直到main()函数都没有处理,那么程序将停止执行:

int main(void){    //try    //{        double a = div(6, 0);    //}    //catch(...)    //{    //  printf("div 0 is error!!\n");    //}    return 0;}

  编译运行:
这里写图片描述

  一个函数执行过程中可能会抛出多重类型的异常,那么也就意味着,一个try语句可以跟上多个catch。catch语句可以定义具体处理的异常类型,不同的类型的异常由不同的catch语句负责处理。代码中的catch(…)可用于处理所有类型的的异常,需要注意,任何异常都只能被捕获一次。

void exception_test1(){    try{        throw 'a';    }    catch(char c)    {        printf("catch(char c)\n");    }    catch(int i)    {        printf("catch(int i)\n");    }    catch(double d)    {        printf("catch(double d)\n");    }    catch(...)    {        printf("catch(...)\n");    }}int main(void){    exception_test1();    return 0;}

  编译运行:
这里写图片描述

  异常抛出后,编译器将自上而下严格匹配每一个catch语句处理的类型,异常处理会进行严格类型匹配,不会进行任何类型转换:

void exception_test2(){    throw std::string("hello");}int main(void){    try    {        exception_test2();      }    catch(char* s)    {        printf("catch(char* s)\n");    }    catch(const char* cs)    {        printf("catch(char* s)\n");    }    catch(std::string ss)    {        printf("catch(std::string ss)\n");    }       return 0;}

  编译运行:
这里写图片描述

2. catch语句块抛出异常

  前面讲到try语句块中可能会抛出异常,抛出的异常被当前函数或者函数调用栈的上一和函数的catch语句块处理。在catch语句块中,处理异常操作还可以是把异常再次抛出,catch抛出的异常需要外层的try…catch语句捕获。

  在catch没有抛出异常的情景中:

void test_func(int i){    switch (i)    {        case 1:            throw -1;            break;        case 2:            throw -2;            break;        case 3:             throw -3;             break;        default:            break;    }    printf("void test_func(int i)\n");}int main(void){    try    {        test_func(2);    }    catch(int i)    {        printf("catch(int i), i = %d\n", i);    }    return 0;}

  编译运行:
这里写图片描述
  抛出异常为-2,此时对于程序调试者可能还尚未知道-2究竟是什么错误码。然而它并不知道test_func(int i)的源代码,只能通过另外的查询手段去获取-2是什么异常,所以说比较麻烦。在实际工程中,catch语句块捕获到的异常可以被重新解释后抛出,假设:
  -1代表输入错误
  -2代表通讯异常
  -3代表其它错误

  上面函数不动,增加run_test_func()函数:

void run_test_func(int i){    try{        test_func(i);    }    catch (int i)    {        if (i == -1)        {            throw "input error!";        }        else if (i == -2)            throw "communication erroe!";        else if ( i == -3)            throw "error!";    }}

  在main()函数中,try语句块调用run_test_func()函数,catch语句块捕获run_test_func()可能抛出的异常:

int main(void){    try    {        run_test_func(2);    }    catch(const char* s)    {        printf("%s\n", s);    }    return 0;}

  编译运行:
这里写图片描述
  这样就能对程序所抛出的异常类型一目了然。

3. 抛出类类型异常

  异常的类型还可以是自定义的类类型,对于类类型的异常匹配规则依旧是从上而下严格匹配,但是注意,子父类的赋值兼容性原则在异常匹配中仍然适用,所以说匹配子类异常的 catch应该放在上面,匹配父类异常的chtch放在下面。

//异常的基类class exceptionBase{public:};class exceptionSub : public exceptionBase{private:    int m_id;    std::string desc_str;public:    exceptionSub(int id, std::string str) : m_id(id), desc_str(str)    {}    int id()    {        return m_id;    }    std::string description()    {        return  desc_str;    }};void test_func(int i){    switch (i)    {        case 1:            throw -1;            break;        case 2:            throw -2;            break;        case 3:             throw -3;             break;        default:            break;    }    printf("void test_func(int i)\n");}void run_test_func(int i){    try{        test_func(i);    }    catch (int i)    {        if (i == -1)        {            //throw "input error!";            throw exceptionSub(-1, "input error!");        }        else if (i == -2)            //throw "communication erroe!";            throw exceptionSub(-2, "communication erroe!");        else if ( i == -3)            //throw "error!";            throw exceptionSub(-3, "error!");    }}int main(void){    try    {        run_test_func(2);    }    catch(exceptionSub& ee)    {        printf("ID: %d\n", ee.id());        printf("description: %s\n", ee.description().c_str());    }    catch(exceptionBase& ee)            //因为支持赋值兼容性原则,所以父类类型应放在子类类型后面    {        printf(" catch(exceptionBase& ee)\n");    }    return 0;}

  编译运行:
这里写图片描述

4. try…catch的两种特殊写法

  (1) c++的函数声明和定义时可以直接指定可能抛出的异常类型,异常声明成为函数声明的一部分。这可以提高代码的可阅读性,如:

void func1(int i) trow(int) //表示该函数可能抛出int类型的异常{    //...}void func2(int i, char c)   //表示该函数可能抛出int类型、char类型的异常{    //...}

  需要注意的是,函数的异常声明是一种和编译器的协议,也就是说函数声明异常后就只能抛出所声明的类型的异常。

void func(int i) throw(int){    if (i > 5)        throw -i;}int main(void){    try{        func(9);    }    catch(int i)    {        printf("exception: %d\n", i);    }     catch(...)    {        printf("catch(...)\n");    }     return 0;}

  编译运行正常:
这里写图片描述

  尝试在声明抛出异常类型为int的func()函数中抛出char类型的异常:

void func(int i) throw(int){    if (i > 5)        throw 'e';}

  编译运行:
这里写图片描述

  即使是在main()函数中有catch(…)代码用于捕捉所有异常,但是不起作用,func()函数违背了与编译器的协议了。

  (2) try…catch的意义在于用于分隔正常功能代码与异常功能代码,这个分隔实现还可以写成这样:

void test_fun(int i) try{    func(i);    printf("test_fun...\n");}catch(int i){    printf("test_fun, catch(int) = %d\n", i);}catch(...){     printf("test_fun, catch(...)\n");}int main(void){    test_fun(12);    test_fun(4);    return 0;

  编译运行:
这里写图片描述
  这样的写法会让初学者感觉try…catch处于两个函数一样,其实不然,它们跟前面写的在一个函数内实现的效果是一致的。

  c++的异常先笔记到这里,下来看看c++标准库的异常类。