您的位置:首页 > 编程语言 > C语言/C++

第63课 C语言异常处理

2018-01-29 14:35 274 查看

1、异常的概念

    1.1、程序在运行过程中可能产生异常
    1.2、异常(Exception)与Bug的区别
        1.2.1、异常是程序运行时可预料的执行分支
        1.2.2、Bug是程序是的错误,是不被预期的运行方式

2、异常和Bug的对比

    2.1、异常:如运行时产生除0的情况、需要打开的外部文件不存在、数组访问时越界
    2.2、Bug:如使用野指针堆数组使用结束后未释放选择排序无法处理长度为0的数组

3、
C语言经典处理方式:if-else

    3.1、示例程序

void func(…)
{
    if(判断是否产生异常)
    {
        正常情况代码逻辑
    }
    else
   {
        异常情况代码逻辑
    }
}

/***************         除法操作异常处理      ************/
#include <iostream>
using namespace std;
double divide(double a, double b, int* valid)
{
    const double delta = 0.0000000000000001;//因为double在内存中是不精确的,所以需要这样判断。
    double ret = 0;
    
    if(!( (-delta<b) && (b<delta) ))
    {
        ret = a/b;
        *valid = 1; //正常
    }
    else
    {
        *valid = 0;//除0错误
    }
    
    return ret;
}
//如果只有两个参数,那么异常为0和实际为0无法分辨。
int main()
{
    int valid = 0;
    double r = divide(1, 0, &valid); //当第三个参数为NULL,还是会出问题,段错误。
    
    if(valid)
    {
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divide by Zero....." << endl;
    }
    
    return 0;
}

3.2、缺陷
    3.2.1、divide函数有3个参数,难以理解其用途
    3.2.2、divide函数调用后必须判断valid代表的结:true表示结果正常,false表示出现异常

4、C语言异常处理的优化方式

    4.1、通过setjmp()longjmp()进行优化
        4.1.1、包含头文件#include <setjmp.h>或<csetjmp>
        4.1.2、int
setjmp(jmp_buf env):将当前上下文保存在jum_buf结构体中,以供以后longjmp()恢复状态信息时使用。如果是直接调用setjmp(),那么返回值为0;如果是由于调用longjmp()而调用setjmp(),那么返回值非0。setjmp()只能在某些特定情况下调用,如在if语句、
switch语句及循环语句的条件测试部分以及一些简单的关系表达式中。

        4.1.3、void
longjmp(jmp_buf env, int val):用于恢复由最近一次调用setjmp()时保存到env的状态信息。当它执行完时,程序执行流会跳转到setjmp()那行,并根据重新执行setjmp(),但此时的setjmp()得到的返回值是val

/***************    除法操作异常处理优化    ************/
#include <iostream>
#include <csetjmp>      //for  setjmp,  longjmp();
using namespace std;
static jmp_buf env; //须定义全局的上下文环境。  static避免别的文件使用。作用域限定符号(修饰全局变量时,就是这个作用)
double divide(double a, double b)
{
    const double delta = 0.0000000000000000001;
    double ret = 0;
    
    
    if(!( (-delta < b) && (b < delta) ))
    {
        ret = a/b;
    }
    else
    {
        longjmp(env, 1);    //当错误发生时会跳转到setjmp那里的代码处,重新执行setjmp并把其的返回值设置为1.。
    }
    
    return ret;
}
        //分析函数时先从main函数分析,
int main()
{
    if(setjmp(env) == 0)//先保存上下文环境,直接调用时返回值为0,通过longjmp调用,返回值为非0; 这种结构破坏了三大执行结构。
    {
        double r = divide(1, 0);
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divide by Zero....." << endl;
    }
    
    return 0;
}

4.2、缺陷
    4.2.1、引入setjmp()和longjmp()必然涉及到使用全局变量
    4.2.2、暴力跳转导致代码可读性降低
    4.2.3、本质还是if-else异常处理方式

5、C语言异常处理存在的通病

    5.1、会使程序逻辑中混入大量的处理异常的代码
    5.2、正常逻辑和异常处理代码混合在一起,导致代码迅速膨胀,难以维护。

/************      异常处理代码分析       ************/
#include <iostream>
using namespace std;
#define SUCCESS            0
#define INVALID_POINTER   -1
#define INVALID_LENGTH    -2
#define INVALID_PARAMETER -3
int MemSet(void* dest, unsigned int length, unsigned char v)
{
    if(dest == NULL)
    {
        return INVALID_POINTER;
    }
    
    if (length < 4)
    {
        return INVALID_LENGTH;
    }
    
    if( (v < 0) || (v > 9) )
    {
        return INVALID_PARAMETER;
    }
    
    unsigned char* p = (unsigned char*)dest;
    
    for(int i = 0; i< length; i++)
    {
        //将char赋值给int。
        //1.如果是char,则最高位为符号位,char赋值给int时会扩展最高位
        //2.如果是unsigned,则不会扩展,即低8位为char的值,其余24位为0
        p[i] = v;
    }
    
    return SUCCESS;
}
int main()
{
    int ai[15];
    int ret = MemSet(ai, sizeof(ai),0);
    
    //正常处理与异常处理的代码混杂,不容易看出哪些是正常处理时的代码
    //哪些是异常发生时应处理的代码(特别是当返回值用数字表示,而不是这里有意义的常量名时)
    if(ret == SUCCESS)
    {
    }
    else if(ret == INVALID_POINTER)
    {
    }
    else if(ret == INVALID_LENGTH)
    {
    }
    else if(ret == INVALID_PARAMETER)
    {
    }
      
    return 0;
}

6、小结

    6.1、程序中不可避免的会发生异常
    6.2、异常是在开发阶段就可以预见的运行时问题
    6.3、C语言中通过经典的if-else
    6.4、C++中存在更好的异常处理方式
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: