您好,欢迎访问三七文档
当前位置:首页 > 临时分类 > C++程序设计 ch13 程序设计的防错
面向对象程序设计与实践第13章程序设计的防错主讲人:杨峰Page1本课主要内容保证程序的正确性异常函数的防错测试调试1保证程序的正确性1.1什么是程序的正确性?实现正确的软件正确的实现软件Page21.2实现正确的软件编写的软件应满足用户的要求至少应满足以下3个要求:对于所有正确的输入能产生正确的结果。对于所有不正确的输入能给出合理的提示信息。程序运行遇到错误后能终止程序的运行。较高要求包括:硬件故障情况下,程序能继续运行或合理终止(掉电、硬盘损坏,网络故障等)系统软件故障情况下,程序能继续运行或合理终止(操作系统,数据库管理系统等)对于商业和工业应用的实用化系统应能满足较高要求,比如电子商务系统,医疗系统,工业控制系统等。Page31.3正确的实现软件编写的程序应该尽量减少错误程序开发过程中会出现4种错误:编译错误(compile-timeerrors)。编译器发现的错误,说明程序代码不符合计算机语言规定的语法标准。链接错误(link-timeerrors)。链接器发现的错误,一般是函数实现与函数声明不对应,全局变量未定义,未链接需要的库等。运行时错误(run-timeerrors)。一般表示与硬件或系统软件发生冲突,比如网络中断,硬盘耗尽,硬件中断错误,软件中断错误,资源读写竞争,除0等等。逻辑错误(logicerrors)。程序编写中有bug,程序不能得到正确的计算结果。Page41.4如何减少错误一般原则通过良好的代码组织减少错误,减小错误的影响范围(充分利用函数和类实现封装,如果出现错误,尽量把错误的范围限制在封装的范围内,不波及到程序的其它部分)通过调试和测试消除错误。通过以上方法尽量减少错误,并使得未发现和未纠正的错误不会严重影响程序的正常运行。编写正确的程序虽然需要大量的实践经验,但是从一开始就进行良好的训练更重要。Page52解决错误解决编译错误编译错一般都是语法错误,比如:写掉了行尾的;号把英文的;号写成了汉字的;号括号没有配对类型名称,变量名称,函数名称,头文件名称键入错了等等。一般情况下,编译器给出的编译错误都是描述性的,根据其描述的内容查找到指定的代码行纠正语法错误就可以了。一般总是先解决第一个编译错指定的代码行,然后再编译。如果还有编译错,以此循环,直至消除全部编译错。2解决错误解决链接错误链接错误的原因很少,一般就3种情况:函数定义和函数声明不对应(一般是因为函数名键入错了)全局变量进行了声明和使用,但没有定义(前面加extern是全局变量的声明,而不是定义)没有链接需要的库(标准库是自动链接的,但其它的库,比如提供网络通信功能的库就需要告诉IDE库的名称和路径才能实现链接)2解决错误解决运行时错误通过一套完整的错误检查,报告与处理机制来解决运行时错误解决逻辑错误通过调试和测试手段来解决逻辑错误3函数的防错设计函数设计的基本原则:函数负责检查和报告错误;函数的调用者负责处理错误报告错误的4种方式函数的返回值全局的错误变量;assert宏异常Page93.1报告错误(Errorreport)通过返回值报告错误返回整型和指针类型的函数可以通过返回值报告错误整型返回值返回负数表示函数执行中出现了运行时错误,不能得到预期的结果。各种负返回值应该定义为符号常量或const常量。指针返回值返回空指针表示函数执行中遇到了错误或没有执行函数功能。Page103.1报告错误(Errorreport)示例:Page11constintTheFirstError=-1;constintTheSecondError=-2;constintTheThirdError=-3;......intFunc(argumentlist){......if(...)returnTheFirstError;....if(...)returnTheSecondError;......return0;}3.1报告错误(Errorreport)通过全局的错误变量浮点型的返回值任何一个浮点型的值都是合法的结果,找不到一个可以表示函数执行错误的值作为返回值。使用全局的errno变量报告函数执行出现运行时错误。全局变量errnoC标准库的头文件errno.h中声明了一个全局变量errno存储错误代码。一些库函数通过给errno赋值来产生函数执行出错的信号。在调用函数后,可以检查errno的值是否为0检查函数执行是否正常。在调用前需要将errno清0,因为这是一个全局变量,函数只管修改它的值,而不管复原它。Page123.1报告错误(Errorreport)Page13errno=0;y=sqrt(x);if(errno!=0){fprintf(stderr,sqrterror,programterminated.\n);exit(-1);}也可以执行perror函数输出错误信息,该函数原型为voidperror(constchar*);该函数向标准错误流输出参数字符串一个分号一个空格最后是errno相关的错误信息errno=0;y=sqrt(x);if(errno!=0){perror(sqrterror);exit(-1);}自定义的函数也可以使用这个全局变量并定义自己的错误码。3.1报告错误(Errorreport)通过assert宏Page14assert宏声明在assert.h中(C++是cassert)其原型为voidassert(intexpression);带一个参数,这个参数是一个正常情况一定为真的表达式。每次执行到这个函数,就检查参数表达式的值,如果该值为假(0),则向stderr输出一行错误信息,并调用abort函数终止程序执行。显示的错误信息包括参数表达式,源文件名和代码行号。如:char*p=newchar[N+1];assert(p);该代码保证p不为空。3.1报告错误(Errorreport)通过异常报告错误C++中,如果函数执行过程中发生错误,则在发生错误的地方抛出一个异常(throwexception),函数不再往下执行。函数的调用者通过try-catch块捕获可能发生的异常并进行相应处理。Page15Page16try{vectorintv;intx;while(cinx)v.push_back(x);for(inti=0;i=v.size();++i)coutv[i]=v[i]endl;}catch(constout_of_range&e){cerrOops!Rangeerror.\n;return-1;}catch(...){cerrOops!Unknownexception.\n;return-2;}3.1报告错误(Errorreport)使用哪种方式报告错误一般的区分原则是:函数返回值表示函数执行过程中可以预见到的不正常情况,比如文件打不开,数据库连不上,给sqrt一个负数参数等等。errno只是函数返回值的替代方案(找不到一个合适的返回值表示错误)assert用于检查完全不应该发生的情况(比如给指针参数提供一个空指针)assert宏只用于调试版本,而在发行版本中不起作用assert宏实际上是用来防止程序中出现的bug。异常主要用于编程时无法预见的运行时错误。如网络连接中断,硬盘耗尽等。以上原则并不是强制性的,4种方法都可以用。一般C程序习惯用前3种C++程序习惯用后2种,并且以抛出异常为主要的错误报告方式,返回值只在函数正常执行完成时才有用。Page173.2函数的前置条件与后置条件每个函数总是包含输入,处理和输出3个部分。函数对输入参数的正确性要求称为函数的前置条件(pre-conditions)。函数成功执行后产生的结果称为后置条件(post-conditions)。函数的前置和后置条件都应该在函数首部的注释中写明。Page183.2函数的前置条件与后置条件前置条件前置条件反映函数的输入参数的要求。如果输入参数不满足要求,建议采用assert宏或抛出异常的方式报告错误。Page19用assert宏:voidmy_strcpy(char*dest,constchar*src){assert(dest&&src);while(*dest++=*src++);}3.2函数的前置条件与后置条件前置条件Page20用异常:doublearea(doublea,doubleb,doublec){if(a0||b0||c0)throwinvalid_argument(边长不能为负);if(!(a+bc&&a+cb&&b+ca))throwinvalid_argument(不是一个三角形);doubles=(a+b+c)/2.0;doublearea=sqrt(s*(s-a)*(s-b)*(s-c));returnarea;}3.2函数的前置条件与后置条件后置条件后置条件描述函数成功执行后的结果。如果函数只通过返回值返回,则应当描述这个返回值。如果函数还通过参数返回值,要描述对参数的修改内容。Page21如阶梯型电阻电路示例:voidgetResists(doublers[],intrs_num)函数的后置条件就是接收用户输入,向电阻值数组中填充rs_num个电阻值,每个电阻值都大于0。又如冒泡排序示例:templateclassTvoidBubbleSort(vectorT&)其后置条件是对类型为T的向量进行冒泡排序,最小的元素在前,最大的元素在后。4异常4.1异常的作用函数防错的基本原则是函数负责检测和报告错误,调用函数处理错误C语言通常使用返回值和全局错误变量的方法,工业强度级的软件系统,每调用一个函数都要在后面跟上一大堆if-elseif-else语句来检测调用过程中是否出现了错误,过多的错误处理代码常常淹没了真正起核心作用的业务函数的调用代码C++通过引入异常机制,将正常调用函数和对错误进行处理的代码严格分开,提高了程序的可读性和可维护性4.1异常的作用函数抛出异常函数负责检测和报告异常。当函数中出现异常后,由函数抛出异常。抛出异常的语法:throwexpr;4.1异常的作用抛出异常的过程:构造一个异常类的变量,然后抛出。if(a0||b0||c0)throwinvalid_argument(边长不能为负);这是一个简写的形式,完整的代码可以如下:if(a0||b0||c0){invalid_argumenterror(边长不能为负);throwerror;}invalid_argument是标准库定义的标准异常类,其构造函数带有一个参数,表示出错信息。4.1异常的作用抛出异常以后函数中,执行到抛出异常的语句就和执行到return语句一样,函数出栈,所有局部变量析构,如果函数中动态分配了内存或其它资源没有释放就会产生内存泄漏C++编程建议,类应该在构造函数中动态分配内存或资源,在析构函数中释放函数抛出异常出栈以后,与return不同,代码并不是回到调用函数的地方,而是跳转到捕获该异常的代码处,如果该异常没有被捕获,程序就会自动终止(异常终止)4.2标准异常logic_errorexceptionruntime_errorlength_errordomain_errorbad_allocbad_exceptionrange_errorout_of_rangeinvalid_argumentios_base::failurebad_castbad_typeidoverflow_errorunderflow_error标准异常的基类是exception。该类含有一个成员函数what()用于提供出错信息,函数原型:virtualconstchar*what();4.2标准异常exception的派生类分为3类:
本文标题:C++程序设计 ch13 程序设计的防错
链接地址:https://www.777doc.com/doc-3972293 .html