二十一. 异常处理

异常的概念

程序的错误通常包括:语法错误、逻辑错误、运行异常。

语法错误指书写的程序语句不合乎编译器的语法规则,这种错误在编译、连接时由编译器指出。

逻辑错误是指程序能顺利运行,但是没有实现预期的功能,这类错误通过调试与测试发现。

操作等。

异常处理: 程序运行异常虽然是无法避免,但是可以预料,为了保证程序的健壮性,必须要在程序中对运行异常进行预见性处理,对运行异常进行预见性处理称为异常处理

处理异常的基本思想是:在底层发生的问题,逐级上报,直到有能力可以处理异常的那级为止。或者说,在应用程序中,如果某个函数发现了错误并引发异常,这个函数就将该异常向上级调用者传递,请求调用者捕获该异常并处理该错误。如果调用者不能处理该错误,就继续向上级调用者传递,直到异常被捕获错误被处理为止。

如果程序最终没有相应的代码处理该异常,那么该异常最后被C++系统所接受,C++系统就简单地终止程序运行。 

处理异常的方法很多,其中最直接的办法是调用C++中的exit()或abort()函数终止程序的执行,exit() 与abort()函数原型在头文件stdlib.h中声明.

两者的区别是exit()在中止程序运行前,会关闭被程序打开的文件、调用全局和static类型对象的析构函数等;而abort()直接结束进程, 什么都不做。

使用exit()与abort()来处理异常显得很机械,有的异常需要进行更复杂的处理。

  • )都表示异常退出, 但一般用exit(-1)或exit(1); exit(0)表示正常退出. 另外, 在stdio.h里, EXIT_SUCCESS表示正常退出,EXIT_FAILURE表示异常退出.
  • abort()函数没有参数.

※ 用if语句处理异常的问题:

来捕获、防止异常:

float quotient(int a, int b) { return a/(float)b; }

cin>>a>>b;

if (b==0) //捕获异常

cout<<"Divide 0 !"<<endl;

else

cout<<a<<"/"<<b<<"="<<quotient(a,b);

这种处理机制有如下缺点:

(1) 每使用quotient()一次, 就必须利用if语句检查一次,使得程序对正常执行过程的描述与对异常的处理交织在一起,程序的易读性不好。

(2) 若异常信息在函数中返回,会破坏程序的逻辑性。如:原来没有返回值的函数,要定义成返回值;对原来有返回值的函数无法定义异常信息返回;象构造函数、析构函数这类由程序自动调用,又没有返回值的特殊函数,就没有办法利用返回值返回异常。

为此,C++提供了异常处理解决方案。

● 异常处理的语法

● 抛掷异常的程序段

......

throw异常类型表达式;     //也可以写成: throw(异常类型表达式); 这一表达式就是引发异常的东西

......

● 捕获并处理异常的程序段

try

可能产生错误的语句

{异常处理语句块1}

{异常处理语句块1}

catch(异常类型n)

{异常处理语句块n}

部分:

throw 表达式

try 语句块

catch 语句块

  • 异常本身

//异常处理的执行过程如下:

(1) 执行try块中的程序语句序列;

(2) 如果执行期间没有执行到throw()(没有引起异常),跳过异常处理区的catch语句块,程序向下执行;

(3) 若执行期间引起异常,执行throw()语句抛出异常,进入异常处理区,将throw() 抛出的异常类型表达式(对象)依次与catch()中的类型匹配,获得匹配的catch子句将捕获并处理异常。继续执行异常处理区后的语句;

(4) 如果未找到匹配(异常未捕获到),自动调用结束函数terminate(),其缺省功能是调用abort()终止程序。

● 处理除零异常的几种形式

#include <iostream>

using namespace std;

int divide(int x, int y)

{

if (y == 0)

throw y;    //用throw抛出异常, 即除数为0的异常(error of the divisor being zero)

return x / y;

}

int main()

{

try                //用try定义异常

{

cout << "5 / 2 = " << divide(5, 2) << endl;

cout << "8 / 0 = " << divide(8, 0) << endl;    //发生异常, 函数divide()被退栈处理, 返回地址(即下一语句的地址)也被退栈,

cout << "7 / 1 = " << divide(7, 1) << endl;    //故这一语句不再被执行

}

catch (int e)    //用catch捕获并处理异常; 异常类型为int型; 定义一个int型变量e(即0), 并将捕获的异常的值赋给e

{

cout << e << " can't be a divisor!" << endl;

}

cout << "That is ok." << endl;

return 0;

}

#include<iostream.h>                                 //包含头文件

#include<stdlib.h>

double fuc(double x, double y)                            //定义函数

{

if(y==0)

{

throw y; //也可以是throw x, 因为C++使用数据类型来捕获不同的异常(这里的x, y都是double型, 所以x和y都可以作为被抛出的异常), 但因为这里引发异常的是y, 所以最好抛出y

}

return x/y;                                        //否则返回两个数的商

}

void main()

{

double res;

try                                            //定义异常

{

res=fuc(2,3);

cout<<"The result of x/y is : "<<res<<endl;

res=fuc(4,0);                                //出现异常

}

catch(double)        //捕获并处理异常, 也可以写成catch(double y), 即catch的是类型, 而不是某个具体变量

{

cerr<<"error of diviso being zero.\n";        //cerr是接受标准错误输出的对象

exit(1);                                    //异常退出程序

}

}

# include <iostream>

using namespace std;

float quotient(int a,int b) throw(char *) //throw(char *)表示只抛出char *类型的异常

{

if (b==0)

throw "Divided by 0!";

else

return a/(float)b;

}

void main()

{

int a,b;

cout<<"Input a, b: \n";

cin>>a>>b;

try

{

cout<<a<<"/"<<b<<"="<<quotient(a,b);

}

catch(char *ErrS) //定义一个char*型变量ErrS, 并将捕获的异常的值(即字符串"Divide 0!")赋给ErrS

{

cerr<<ErrS<<endl;

}

}

● 异常接口声明

• 可以在函数的声明中列出这个函数可能抛掷的所有异常类型。

例如:

void fun() throw(A,B,C,D);    //表示fun只能抛出A,B,C,D类型的异常

• 若无异常接口声明,或者throw(...),则此函数可以抛掷任何类型的异常。

※ 直接写throw(...), 这里的省略号不代表其它内容.

• 不抛掷任何类型异常的函数声明如下:

void fun() throw();

● 异常处理中的构造和析构

#include<iostream.h>

class expt                                                    //定义类expt

{

public:                                                    //定义公有成员

expt()                                                //定义构造函数

{

cout<<"structor of expt"<<endl;

}

~ expt()                                                //定义析构函数

{

cout<<"destructor of expt"<<endl;

}

};

class demo                                                //定义类demo

{

public:

demo()                                                //定义构造函数

{

cout<<"structor of demo"<<endl;

}

~demo()                                                //定义析构函数

{

cout<<"destructor of demo"<<endl;

}

};

void fuc1()                                                //定义函数

{

int s=0;

demo d;                                                //声明demo类的对象

throw s;    //抛出异常, 这里的异常抛出没有条件, 反正就是有异常

}

void fuc2()

{

expt e;                                                //声明expt类的对象

fuc1();                                                //调用函数fuc1

}

void main()

{

try                                                    //定义异常

{

fuc2();                                            //调用函数

}

catch(int)                                                //定义异常处理

{

cout<<"catch int exception"<<endl;

}

cout<<"continue main()"<<endl;

}

//在抛出异常前, e和d这两个对象先后被创建, 在抛出异常后, 两个对象按与创建的相反顺序调用析构函数而被销毁.

● 异常处理综合案例

求一元二次方程(ax²+bx+c=0 (a≠0))的实根, 要求加上异常处理, 判断b*b-4*a*c是否大于0, 成立则求两个实根, 否则要求重新输入.

注意, 一元二次方程求根公式为:

#include <iostream>

#include <math.h>

using namespace std;

double sqrt_delta(double d)

{

if(d < 0)

throw 1;

return sqrt(d);

}

double delta(double a, double b, double c)

{

double d = b * b - 4 * a * c;

return sqrt_delta(d);

}

void main()

{

double a, b, c;

cout << "please input a, b, c" << endl;

cin >> a >> b >> c;        //接收输入

while(true)        //无限循环, 这样如果delta小于0, 我们可以重新输入系数a, b, c

{

try

{

double d = delta(a, b, c);

cout << "x1: " << (d - b) / (2 * a);

cout << endl;

cout << "x2: " << -(b + d) / (2 * a);

cout << endl;

break;    //跳出循环

}

catch(int)

{

cout << "delta < 0, please reenter a, b, c.";

cin >> a >> b >> c;

}

}

}

标准异常类

C++提供了标准异常处理类库,它用来抛出C++标准库中函数执行时的异常。C++标准异常处理类的层次结构图如下图所示:

  • exception是标准程序库异常类的公共基类, 下面的都是子类

例如: logic_error表示可以在程序中被预先检测到的异常(如果小心地编写程序,这类异常能够避免); runtime_error表示难以被预先检测的异常

※ 基类exception提供了一个成员函数what(), 用于返回错误信息(返回类型为const char*)该函数在exception的派生类中可以被重载.

● 标准异常类的使用

# include <iostream>

//# include <new> //在Visual C++6.0中可以不包含

# include <string>

//# include <stdexcept> //在Visual C++6.0中可以不包含

using namespace std;

void main()

{

string* S;    //S是对象

try

{

S=new string("ABCD"); //可能抛出bad_alloc异常

cout<<S->substr(5,2); //可能抛出out_of_range异常

}

catch(bad_alloc& NoMemory)    //bad_alloc&是异常类型, 异常的值传给了对象NoMemory

{

cout<<"Exception occurred: "<<NoMemory.what()<<endl;    //what()是对象NoMemory的成员函数

}

catch(out_of_range& OutOfRange) // out_of_range&是异常类型, 异常的值传给了对象OutOfRange

{

cout<<"Exception occurred: "<<OutOfRange.what()<<endl;        //what()是对象OutOfRange的成员函数

}

}

(C/C++学习笔记) 二十一. 异常处理的更多相关文章

  1. python3.4学习笔记(二十一) python实现指定字符串补全空格、前面填充0的方法

    python3.4学习笔记(二十一) python实现指定字符串补全空格.前面填充0的方法 Python zfill()方法返回指定长度的字符串,原字符串右对齐,前面填充0.zfill()方法语法:s ...

  2. Java基础学习笔记二十一 多线程

    多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...

  3. 过滤器(web基础学习笔记二十一)

    一.过滤器简介 二.在Eclipse中创建过滤器 三.使用过滤器设置全部web字符编码 public void doFilter(ServletRequest request, ServletResp ...

  4. Java学习笔记二十一:Java面向对象的三大特性之继承

    Java面向对象的三大特性之继承 一:继承的概念: 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类. 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方 ...

  5. Python学习日记(二十一) 异常处理

    程序中异常的类型 BaseException 所有异常的基类 SystemExit 解释器请求退出 KeyboardInterrupt 用户中断执行(通常是输入^C) Exception 常规错误的基 ...

  6. PHP学习笔记二十一【全局变量】

    <?PHP //定义全局变量 global $a; $a=9; //给全局变量赋值 function test1() { global $a; $a=45; } test1(); echo $a ...

  7. angular学习笔记(二十一)-$http.get

    基本语法: $http.get('url',{}).success(function(data,status,headers,config){}).error(function(data,status ...

  8. python cookbook第三版学习笔记二十一:利用装饰器强制函数上的类型检查

    在演示实际代码前,先说明我们的目标:能对函数参数类型进行断言,类似下面这样: @typeassert(int, int) ... def add(x, y): ...     return x + y ...

  9. python3.4学习笔记(二) 类型判断,异常处理,终止程序

    python3.4学习笔记(二) 类型判断,异常处理,终止程序,实例代码: #idle中按F5可以运行代码 #引入外部模块 import xxx #random模块,randint(开始数,结束数) ...

随机推荐

  1. Memcached遇到的问题及解决办法

    1. memcached make: *** No targets specified and no makefile found. Stop. 其实是因为在安装libevent时增加了版本号导致的, ...

  2. tomcat ----> 源码关联/编译/....

    今天在搞Servlet时想看下tomcat Servlet-API.jar的源码,按照惯性思维用以往关联SSH2框架的源码的方式去做但是结果没有成功,尝试过换不同的文件夹,起初怀疑可能是路径太深关联不 ...

  3. English Voice of <<Bye Bye Bye>>

    Bye Bye Bye - Lovestoned When i see you, looking back at me 当我看到你回首看我时 Watching this eye still 彼此凝视 ...

  4. English trip M1 - PC6 Likes and Dislike Teacher:Jade

    In this lesson you will learn to talk about likes and dislikes. 课上内容(Lesson) # 通常在习惯性的表达式用 it's 来表达w ...

  5. Lombok插件

    Lombok简介 Lombok是一款好用顺手的工具,就像Google Guava一样,在此予以强烈推荐,每一个Java工程师都应该使用它.Lombok是一种Java™实用工具,可用来帮助开发人员消除J ...

  6. Mycat安装教程

      1.下载:   https://github.com/MyCATApache/Mycat-download 具体下载哪个版本以发布为准,推荐1.4,1.5.   2.安装:   安全前,在Linu ...

  7. Android Studio 一直卡在building解决办法

    1.随便找一个你能运行的as项目 2.打开gradle-wrapper.properties,文件目录:项目/gradle/wrapper/gradle-wrapper.properties 3.复制 ...

  8. CF-831D Office Keys 思维题

    http://codeforces.com/contest/831/problem/D 题目大意是在一条坐标轴上,给出n个人,k把钥匙(k>=n)以及终点的坐标,所有人都可以同时运动,但不可以公 ...

  9. P2469 [SDOI2010]星际竞速

    在何Au的讲解下终于搞明白了这个以前的坑. 首先考虑最小路径覆盖. 这个题意又要求我们求出的最大流为n-1(这样才能保证路径为1条) 考虑高速航行模式的图怎么建,只需要保证最大流的同时费用最小即可,显 ...

  10. poj2891 扩展中国剩余定理

    求a1x1+r1=y...anxn+rn=y,crt合并 //#pragma GCC optimize(2) //#pragma GCC optimize(3) //#pragma GCC optim ...