异常处理这部分内容其实并不属于OPP的技术,仅仅是C++对程序出错的处理。
异常处理
程序中常见的错误有两⼤类:语法错误和运⾏错误。在编译时,编译系统能发现程序中的语法错误。
在设计程序时,应当事先分析程序运⾏时可能出现的各种意外的情况,并且分别制订出相应的处理⽅法,这就是程序的异常处理的任务。在运⾏没有异常处理的程序时,如果运⾏情况出现异常,由于程序本⾝不能处理,程序只能终⽌运⾏。如果在程序中设置了异常处理机制,则在运⾏情况出现异常时,由于程序本⾝已规定了处理的⽅法,于是程序的流程就转到异常处理代码段处理。
异常(exception)是运⾏时(run-time)的错误,通常是⾮正常条件下引起的,例如,下标(index)越界、new操作不能正常分配所需内存。C语⾔中,异常通常是通过被调⽤函数返回⼀个数值作为标记的。
C++采取的办法是:如果在执⾏⼀个函数过程中出现异常,可以不在本函数中⽴即处理,⽽是发出⼀个信息,传给它的上⼀级(即调⽤它的函数),它的上级捕捉到这个信息后进⾏处理。如果上⼀级的函数也不能处理,就再传给其上⼀级,由其上⼀级处理。如此逐级上送,如果到最⾼⼀级还⽆法处理,最后只好异常终⽌程序的执⾏。
C++中,函数可以识别标记为异常的条件,然后通告发⽣了异常。这种通告异常的机制称为抛出异常(throwing an exception)。
为什么要使⽤异常处理?
1.常规代码与错误处理代码的分离
⽤伪代码描述下⾯这个程序的各部分功能readFile{
open the file; determine its size;
allocate that much memory; read the file into memory; close the file;}
What happens if the file can't be opened?
What happens if the length of the file can't be determined? What happens if enough memory can‘t be allocated(分配)? What happens if the read fails(失败)? What happens if the file can't be closed?
⾯对这些可能发⽣的问题我们需要在编写程序的时候就要考虑到,如果采⽤⾮异常处理的⽅法会得到下⾯的代码:errorCodeType readFile{
initialize errorCode = 0; open the file; if (theFileIsOpen) {
determine the length of the file; if (gotTheFileLength) {
allocate that much memory; if (gotEnoughMemory) {
read the file into memory; if (readFailed) {
errorCode = -1; } } else {
errorCode = -2; } } else {
errorCode = -3; }
close the file;
if (theFileDidntClose && errorCode == 0) {
errorCode = -4; } else {
errorCode = errorCode and -4; } } else {
errorCode = -5; }
return errorCode;}
我们为了确定到底在代码执⾏的那⼀个部分出错,嵌套了太多的if语句,这显然是我们不能接受的。如果采⽤C++的异常处理得到下⾯的代码:readFile{ try {
open the file; determine its size;
allocate that much memory; read the file into memory; close the file; }
catch (fileOpenFailed) {
doSomething; }
catch (sizeDeterminationFailed) {
doSomething;
}
catch (memoryAllocationFailed) {
doSomething; }
catch (readFailed) {
doSomething; }
catch (fileCloseFailed) {
doSomething; }}
实现了常规代码与错误处理代码的分离。
2.在调⽤栈中传播异常
method1{
call method2;}method2{
call method3;}method3{
call readFile;}
如果有上⾯这样的函数嵌套,我们想要确定到底在那⼀个函数中出错,如果采⽤⾮异常处理的⽅法会得到下⾯的代码:method1{
errorCodeType error; error = call method2; if (error)
doErrorProcessing; else proceed;}
errorCodeType method2{
errorCodeType error; error = call method3; if (error) return error; else proceed;}
errorCodeType method3{
errorCodeType error;
error = call readFile; if (error) return error; else proceed;}
异常处理⽅式:method1{ try {
call method2; }
catch (exception e) {
doErrorProcessing; }}method2{ try {
call method3; }
catch (exception e) {
doErrorProcessing; }}method3{ try { readFile; }
catch (exception e) {
doErrorProcessing; }}
3.对不同的错误类型进⾏分类
catch (FileNotFoundException e){ ...}
catch (IOException e){ … ….}…
catch (Exception e) //A (too) general exception handler{
...}
异常处理⽅式
C++中,try与catch⽤于实现异常的处理。
C++处理异常的机制是由3个部分组成的,即检查(try)、抛出(throw)和捕捉(catch)。
把需要检查的语句放在try块中,throw⽤来当出现异常时发出⼀个异常信息,⽽catch则⽤来捕捉异常信息,如果捕捉到了异常信息,就处理它。
Catch块可以任何顺序排列。
⼀个关键要求是catch必须定义在try块之后,在try块中可能会有异常被抛出(发⽣)。异常与catch是以类型来进⾏匹配的。
其中异常也分两种,⼀种是系统定义的异常,另⼀种是⽤户⾃定义的异常。
1.系统定义异常
例如下⾯的程序,删除字符串中的⼀部分内容,⼀旦要删除的内容造成越界,系统就会抛出异常。#include string s = \"Mike Jackson\"; int index, len; while(true) { cout << \"Enter index and length to erase: \"; cin >> index >> len; try { s.erase(index, len); } catch ( out_of_range ) // out_of_range is a system-defined type { continue; } cout< 下⾯的代码为了得到正确的数组第i个数的值,需要⾃⼰按照数组的规模进⾏限制。const int MaxSize = 1000;float arr[MaxSize]; enum out_of_bounds { underflow, overflow };float& access( int i ){ if ( i < 0 ) throw underflow; if ( i > MaxSize ) throw overflow; return arr[i];} void g( ){ // … try { val = access( k ); } catch( out_of_bounds t) { if ( t == underflow ) { cerr << \"arr: underflow…aborting\\n\"; exit( EXIT_FAILURE ); } if( t == overflow ) { cerr << \"arr: overflow…aborting\\n\"; exit( EXIT_FAILURE ); } //… }} 通常,如果⼀个函数抛出了⼀个异常,但没有对应的catch处理它,则系统通过调⽤函数unexpected 函数去处理它。实际上(In effect), unexpected 是没有被程序员处理的异常的缺省处理者。 函数嵌套检测异常处理 int main( ){ try { f1( ); } catch(double) { cout<<″OK0!″< catch(char) { cout<<″OK1!″; } cout<<″end1″< catch(int) { cout<<″Ok2!″< double a=0; try { throw a; } catch(float) { cout<<″OK3!″< 程序运⾏结果如下: OK0! (在主函数中捕获异常) end0 (执⾏主函数中最后⼀个语句时的输出) 该程序在f3函数的时候抛出了⼀个异常,但f3函数没有去捕获,f2、f1也都没有捕获,在主函数的时候被捕获到。 (2) 如果将f3函数中的catch⼦句改为catch(double),⽽程序中其他部分不变,则程序运⾏结果如下: OK3! (在f3函数中捕获异常) end3 (执⾏f3函数中最后⼀个语句时的输出)end2 (执⾏f2函数中最后⼀个语句时的输出)end1 (执⾏f1函数中最后⼀个语句时的输出)end0 (执⾏主函数中最后⼀个语句时的输出) 该程序在f3函数的时候抛出了⼀个异常,⽴刻被f3函数捕获,将执⾏完f3函数,f3函数的执⾏完成意味着主函数、f1、f2的调⽤完成。(3)如果在此基础上再将f3函数中的catch块改为catch(double){ cout<<″OK3!″< OK3! (在f3函数中捕获异常)OK0! (在主函数中捕获异常) end0 (执⾏主函数中最后⼀个语句时的输出) 该程序在f3函数的时候抛出了⼀个异常,⽴刻被f3函数捕获,然后⼜抛出⼀个新的异常,这个异常直到主函数才被捕获。 异常处理实例 编写⼀个计算三⾓形⾯积的函数,函数的参数为三⾓形三边边长a、b、c,可以⽤Heron公式计算:#include //给出三⾓形三边长,计算三⾓形⾯积 double area(double a, double b, double c) throw (invalid_argument){ //判断三⾓形边长是否为正 if (a <= 0 || b <= 0 || c <= 0) throw invalid_argument(\"the side length should be positive\");//判断三边长是否满⾜三⾓不等式 if (a + b <= c || b + c <= a || c + a <= b) throw invalid_argument(\"the side length should fit the triangle inequation\");//由Heron公式计算三⾓形⾯积 double s = (a + b + c) / 2; return sqrt(s * (s - a) * (s - b) * (s - c));} int main(){ double a, b, c; //三⾓形三边长 cout << \"Please input the side lengths of a triangle: \"; cin >> a >> b >> c; try { double s = area(a, b, c); //尝试计算三⾓形⾯积 cout << \"Area: \" << s << endl; } catch (exception &e) { cout << \"Error: \" << e.what() << endl; } return 0;} 运⾏结果1: Please input the side lengths of a triangle: 3 4 5Area: 6运⾏结果2: Please input the side lengths of a triangle: 0 5 5Error: the side length should be positive运⾏结果3: Please input the side lengths of a triangle: 1 2 4Error: the side length should fit the triangle inequation 下⾯介绍异常处理需要注意的⼏点: (1) ⾸先把可能出现异常的、需要检查的语句或程序段放在try后⾯的花括号中。被检测的函数必须放在try块中,否则不起作⽤。(2) try块和catch块作为⼀个整体出现,catch块是try-catch结构中的⼀部分,必须紧跟在try块之后,不能单独使⽤,在⼆者之间也不能插⼊其他语句。但是在⼀个try-catch结构中,可以只有try块⽽⽆catch块。 程序开始运⾏后,按正常的顺序执⾏到try块,开始执⾏try块中花括号内的语句。如果在执⾏try块内的语句过程中没有发⽣异常,则catch⼦句不起作⽤,流程转到catch⼦句后⾯的语句继续执⾏。⼀个try-catch结构中只能有⼀个try块,但却可以有多个catch块,以便与不同的异常信息匹配。 (3)try和catch块中必须有⽤花括号括起来的复合语句,即使花括号内只有⼀个语句,也不能省略花括号。 (4) 如果在执⾏try块内的语句(包括其所调⽤的函数)过程中发⽣异常,则throw运算符抛出⼀个异常信息。throw抛出异常信息后,流程⽴即离开本函数,转到其上⼀级的函数(main 函数)。 throw抛出什么样的数据由程序设计者⾃定,可以是任何类型的数据。(5) 这个异常信息提供给try-catch结构,系统会寻找与之匹配的catch⼦句。 (6) 在进⾏异常处理后,程序并不会⾃动终⽌,继续执⾏catch⼦句后⾯的语句。 (7) catch只检查所捕获异常信息的类型,⽽不检查它们的值。因此如果需要检测多个不同的异常信息,应当由throw抛出不同类型的异常信息。异常信息可以是C++系统预定义的标准类型,也可以是⽤户⾃定义的类型(如结构体或类)。 (8) 如果在catch⼦句中没有指定异常信息的类型,⽽⽤了删节号“…”,则表⽰它可以捕捉任何类型的异常信息,如:catch(…) { cout<<″OK″< 因篇幅问题不能全部显示,请点此查看更多更全内容3.⽤户⾃定义异常