C++学习笔记-异常处理
程序设计的要求之一就是程序的健壮性。希望程序在运行时能够不出或者少出问题。但是,在程序的实际运行时,总会有一些因素会导致程序不能正常运行。异常处理(Exception Handling)就是要提出或者是研究一种机制,能够较好的处理程序不能正常运行的问题
异常和异常处理
异常及其特点
- 异常(Exceptions)是程序在运行时可能出现的会导致程序运行终止的错误
- 编译系统检查出来的语法错误,导致程序运行结果不正确的逻辑错误,都不属于异常的范围
- 异常是一个可以正确运行的程序在运行中可能发生的错误
常见的异常:
- 系统资源不足。如内存不足,不可以动态申请内存空间;磁盘空间不足,不能打开新的输出文件,等
- 用户操作错误导致运算关系不正确。如出现分母为0,数学运算溢出,数组越界,参数类型不能转换,等
异常有以下的一些特点:
- 偶然性。程序运行中,异常并不总是会发生的。
- 可预见性。异常的存在和出现是可以预见的。
- 严重性。一旦异常发生,程序可能终止,或者运行的结果不可预知
异常处理方法及举例
对于程序中的异常,通常有三种处理的方法:
- 不作处理。很多程序实际上就是不处理异常的。
- 发布相应的错误信息,然后,终止程序的运行。在C语言的程序中,往往就是这样处理的。
- 适当的处理异常,一般应该使程序可以继续运行
一般来说,异常处理(Exception Handling)就是在程序运行时对异常进行检测和控制。
而在C++中,异常处理(EH)就是用C++提供的try-throw-catch的模式进行异常处理的机制
e.g.
#include <iostream>
#include <stdlib.h>using namespace std;double divide(double a, double b)
{if (b == 0) //检测分母是不是为0{cout << "除数不可以等于0 !" << endl;abort(); //调用abort函数终止运行}return a / b;
}void main()
{double x, y, z;cout << "输入两个实数 x 和 y :";while (cin >> x >> y){z = divide(x, y);cout << "x 除以 y 等于 " << z << "\n";cout << "输入下一组数 <q 表示结束>: ";}cout << "Bye!\n";
}
这个程序中,对于除数为0的处理有这样的特点:
- 异常的检测和处理都是在一个程序模块(divide函数)中进行的;
- 由于函数的返回值是double型的数据,因此,即使检测到除数为0的情况,也不能通过返回值来反映这个异常。只能调用函数abort终止程序的运行
C++异常处理机制
C++处理异常有两个基本的做法
- 异常的检测和处理是在不同的代码段中进行的。一般的说法是在“try”部分检测异常,“catch”部分处理异常。
- 由于异常的检测和处理不是在同一个代码段中进行的,在检测异常和处理异常的代码段之间需要有一种传递异常信息的机制,在C++中是通过“对象”来传递异常的。这种对象可以是一种简单的数据(如整数),也可以是系统定义或用户自定义的类的对象
C++异常处理的语法可以表述如下:
try
{受保护语句;throw 异常;其他语句;
}
catch(异常类型)
{异常处理语句;
}
在C++术语中,异常(Exception,注意结尾没有s)是作为专用名词出现的。就是将异常检测程序所抛掷的“带有异常信息的对象”称为“异常”。
而将捕获异常的处理程序称为异常处理程序(Exception Handler)
在try复合语句中,可以调用其他函数,在所调用的函数中检测和抛掷异常,而不是在try复合语句中直接抛掷异常。这个所调用的函数,仍然是属于这个try模块的,所以这个模块中的catch部分,仍然可以捕获它所抛掷的异常并进行处理
#include <iostream>
#include <stdlib.h>using namespace std;double divide(double a, double b)
{if (b == 0){throw "输入错误:除数不可以等于0 !";}return a / b;
}
void main()
{double x, y, z;cout << "输入两个实数 x 和 y :";while (cin >> x >> y){try{z = divide(x, y);}catch (const char * s) // start of exception handler{cout << s << "\n";cout << "输入一对新的实数: ";continue;} // end of handlercout << "x 除以 y 等于 " << z << "\n";cout << "输入下一组数 <q 表示结束>: ";}cout << "程序结束,再见!\n";
}
阅读这个程序,可以注意以下几点:
- 在try的复合语句中,调用了函数divide。因此,尽管divide函数是在try模块的外面定义的,它仍然是属于try模块:在try语句块中运行
- divide函数检测到异常后,抛掷出一个字符串作为异常对象,异常的类型就是字符串类型
- catch程序块指定的异常对象类型是char*,可以捕获字符串异常。捕获异常后的处理方式是通过continue语句,跳过本次循环,也不输出结果,直接进入下一次循环,要求用户再输入一对实数
另外,在编写带有异常处理的程序时,还要注意:
- try语句块和catch语句块是一个整体,两者之间不能有其他的语句
- 一个try语句块后面可以有多个catch语句,但是,不可以几个try语句块后面用一个catch语句
用类的对象传递异常
- throw语句所传递的异常,可以是各种类型的:整型、实型、字符型、指针,等等。也可以用类对象来传递异常
- 对象就是既有数据属性,也有行为属性。使用对象来传递异常,就是既可以传递和异常有关的数据属性,也可以传递和处理异常有关的行为或者方法
- 专门用来传递异常的类称为异常类。异常类可以是用户自定义的,也可以是系统提供的exception类
用户自定义类的对象传递异常
- 用栈类模板来作为例子,类模板中两个主要的函数push和pop的定义中,都安排了错误检查的语句,以检查栈空或者栈满的错误。由于pop函数是有返回值的,在栈空的条件下,是没有数据可以出栈的。尽管pop函数可以检测到这种错误,但是,也不可能正常的返回,于是只好通过exit函数调用结束程序的执行
- 用C++异常处理的机制,改写这个程序。要求改写后的程序不仅有更好的可读性,而且在栈空不能出栈时,程序也可以继续运行,使得程序有更好的健壮性。
- 可以定义两个异常类:一个是“栈空异常”类,另一个是“栈满异常”类。在try块中,如果检测到“栈空异常”,就throw一个“StackEmptyException”类的对象。如果检测到“栈满异常”,就throw一个“StackOverflowException”类的对象
#include <iostream>using namespace std;class StackOverflowException //栈满异常类
{public:StackOverflowException() {}~StackOverflowException() {}void getMessage(){cout << "异常:栈满不能入栈。" << endl;}
};class StackEmptyException //栈空异常类
{public:StackEmptyException() {}~StackEmptyException() {}void getMessage(){cout << "异常:栈空不能出栈。" << endl;}
};
template <class T, int i> //类模板定义
class MyStack
{T StackBuffer[i];int size;int top;
public:MyStack(void) : size(i){top = i;};void push(const T item);T pop(void);
};
template <class T, int i> //push成员函数定义
void MyStack< T, i >::push(const T item)
{if (top > 0)StackBuffer[--top] = item;elsethrow StackOverflowException(); //抛掷对象异常return;
}
template <class T, int i> //pop成员函数定义
T MyStack< T, i >::pop(void)
{if (top < i)return StackBuffer[top++];elsethrow StackEmptyException();//抛掷另一个对象异常
}
void main() //带有异常处理的类模板测试程序
{MyStack<int, 5> ss;for (int i = 0; i < 10; i++){try{if (i % 3)cout << ss.pop() << endl;else ss.push(i);}catch (StackOverflowException &e){e.getMessage();}catch (StackEmptyException &e){e.getMessage();}}cout << "Bye\n";system("pause");
}
用C++异常处理机制来处理栈操作中的“栈空异常”和“栈满异常”。定义两个相应的异常类。通过异常类对象来传递检测到的异常,并且对异常进行处理。要求在栈空的时候用pop函数出栈失败时,程序的运行也不终止
- 通过对象传递参数。具体来说,是在throw语句中直接调用异常类的构造函数,生成一个无名对象(如:throw StackEmptyException();),来传递异常的
- 在catch语句中规定的异常类型则是异常类对象的引用。当然,也可以直接用异常类对象作为异常
- 通过异常类对象的引用,直接调用异常类的成员函数getMessage,来处理异常
- 在try语句块后面直接有两个catch语句来捕获异常。也就是说,要处理的异常增加时,catch语句的数目也要增加。
- 运行结果表明,10次循环都已经完成。没有出现因为空栈时不能出栈而退出运行的情况
用exception类对象传递异常
C++提供了一个专门用于传递异常的类:exception类。可以通过exception类的对象来传递异常
class exception
{
public:exception(); //默认构造函数exception(char *); //字符串作参数的构造函数exception(const exception&);exception& operator= (const exception&);virtual ~exception(); //虚析构函数virtual char * what() const; //what()虚函数
private:char * m_what;
};
其中和传递异常最直接有关的函数有两个:
- 带参数的构造函数。参数是字符串,一般就是检测到异常后要显示的异常信息。
- what()函数。返回值就是构造exception类对象时所输入的字符串。可以直接用插入运算符“<<”在显示器上显示
如果捕获到exception类对象后,只要显示关于异常的信息,则可以直接使用exception类。如果除了错误信息外,还需要显示其他信息,或者作其他的操作,则可以定义一个exception类的派生类,在派生类中可以定义虚函数what的重载函数,以便增加新的信息的显示
#include <iostream>
#include <exception>using namespace std;class ArrayOverflow : public exception //exception类的派生类
{
public:ArrayOverflow::ArrayOverflow(int i) : exception("数组越界异常!\n"){k = i;}const char * what() //重新定义的what()函数{cout << "数组下标" << k << "越界\n";return exception::what();}
private:int k;
};class MyArray //数组类的定义
{int *p; //数组首地址int sz; //数组大小
public:MyArray(int s) //构造函数{p = new int[s];sz = s;}~MyArray(){delete[] p;}int size(){return sz;}int& operator[ ] (int i); //重载[]运算符的原型
};int& MyArray :: operator[ ] (int i) //重载[]运算符
{if (i >= 0 && i < sz)return p[i];throw ArrayOverflow(i);
}void f(MyArray& v)
{for (int i = 0; i < 3; i++){try {if (i != 1) { v[i] = i; cout << v[i] << endl; }else v[v.size() + 10] = 10;}catch (ArrayOverflow &r){cout << r.what();}}//for循环结束
}void main()
{MyArray A(10);f(A);system("pause");
}
- 定义一个简单的数组类。在数组类中重载“[ ]”运算符,目的是对于数组元素的下标进行检测。如果发现数组元素下标越界,就抛掷一个对象来传递异常。并且要求处理异常时可以显示越界的下标值
- 使用exception类的对象来传递对象。但是,直接使用exception类对象还是不能满足例题的要求。因为不能传递越界的下标值
- 为此,可以定义一个exception类的派生类ArrayOverflow。其中包含一个数据成员k。在构造ArrayOverflow类对象时,用越界-的下标值初始化这个数据k。在catch块中捕获到这个对象后,可以设法显示对象的k值
异常处理中的退栈和对象析构
在函数调用时,函数中定义的自动变量将在堆栈中存放。结束函数调用时,这些自动变量就会从堆栈中弹出,不再占用堆栈的空间,这个过程有时被称为“退栈”(Stack unwinding)。其他的结束动作还包括调用析构函数,释放函数中定义的对象
但是,如果函数执行时出现异常,并且只是采用简单的显示异常信息,然后退出(exit)程序的做法,则程序的执行就会突然中断,结束函数调用时必须完成的退栈和对象释放的操作也不会进行。这样的结果是很不希望的
float function3(int k) //function3中可能有异常
{if (k == 0){cout << "function3中发生异常\n"; //显示异常信息exit(1);} //退出执行else return 123 / k;
}void function2(int n)
{ForTest A12;function3(n); //第三次调用
}void function1(int m)
{ForTest A11;function2(m); //第二次调用
}void main()
{function1(0); //第一次调用
}
程序运行后显示:function3中发生异常
在function1和fuction2中分别定义了ForTest类的对象。如果函数可以正常退出,这些对象将被释放。
但是,程序运行后只显示了异常信息。没有析构函数被调用的迹象。说明所创建的对象没有被释放。
如果采用C++的异常处理机制来进行处理。情况就会完全不同
如果在function3中用throw语句来抛掷异常,就会开始function3的退栈。
然后,返回到函数function2开始function2的退栈,,并且调用ForTest类析构函数,释放对象A12。
接着,返回到函数function1开始function1的退栈,,并且调用ForTest类析构函数,释放对象A11
#include <iostream>
#include <stdlib.h>
#include <exception>using namespace std;class ForTest
{
public:~ForTest() //析构函数{cout << "ForTest类析构函数被调用\n";}
}; //ForTest类定义结束float function3(int k) //function3中可能有异常
{if (k == 0)throw exception("function3中出现异常\n"); //抛掷异常类对象else return 123 / k;
}void function2(int n)
{ForTest A12;function3(n); //第三次调用
}void function1(int m)
{ForTest A11;function2(m); //第二次调用
}void main()
{try{function1(0); //第一次调用}catch (exception &error){cout << error.what() << endl;}system("pause");
}
异常总结
- 在程序设计中使用这样的异常处理机制,有助于提高程序的健壮性、可读性。而且可以防止因为程序不正常结束而导致的资源泄漏,如创建的对象不能释放等。
- try模块在异常处理中有着极其重要的作用
- 可以通过用户自定义类的对象来传递异常
转载于:https://www.cnblogs.com/cj5785/p/10664715.html
C++学习笔记-异常处理相关推荐
- 云计算学习笔记---异常处理---hadoop问题处理ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.lang.NullPoin
云计算学习笔记---异常处理---hadoop问题处理ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.lang.NullPoin ...
- (JAVA学习笔记) 异常处理
文章目录 什么是异常 异常分类 异常体系结构 Error Exception Error和Exception的区别: 异常处理机制 代码演示 自定义异常 代码演示 什么是异常 异常指程序运行中出现的不 ...
- python基础学习笔记——异常处理
异常处理流程图 一,异常和错误 part1:程序中难免出现错误,而错误分成两种 1.语法错误(这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正) #语法错误示范一 if #语法 ...
- Python学习笔记-异常处理
异常处理 Python Errors and Exceptions 官方文档 引发异常: 语法: 使用raise 关键字, raise either an exception instance or ...
- Go学习笔记 -- 异常处理
Golang 中没有内置的类似 (try-catch-finally) 的异常处理手段,但是有其他的替代方案. 文章目录 关键的内置函数和关键字 panic defer defer特性 defer执行 ...
- JAVA学习笔记——异常处理与调试
目录 错误与异常 异常分类 声明受查异常 抛出异常 创建异常类 异常捕获与处理 基本结构 再次抛出异常与异常链 带资源的 try 语句 使用异常机制的技巧 断言的使用 启用和禁用断言 使用断言完成参数 ...
- Java学习笔记 --- 异常处理
一.基本介绍 异常处理就是当异常发生时,对异常处理的方式 二.异常处理的方式 1.try - catch - finally 程序员在代码中捕获发生的异常,自行处理 处理机制示意: try {代码// ...
- Java学习笔记——异常处理
目录 一.异常 (一)异常的相关概念 (二)异常产生的原因 (三)异常的分类 二.Exception (一)检查性异常 (二)非检查性异常 (三) 异常方法 三.Java处理异常的关键字 (一)捕获异 ...
- asp.net学习笔记异常处理001---.framework4.0中asp.net页面ValidateRequest=false 无效的问题
在做牛腩新闻发布系统的时候,部分同学可能会遇到这样的情况: 从客户端(ContentPlaceHolder1_m_ContentPlaceHolder_ftbContent="<P&g ...
- 秒杀项目学习笔记-异常处理
目的:将校验失败的错误信息在客户端显示出来. 实现:新建一个全局异常拦截器,拦截绑定异常BindException,输出错误信息,并且优化返回信息的过程. GlobleExceptionHandler ...
最新文章
- Linux 编译内核
- Promise.race 的原理
- HDU 1874 SPFA算法Dijkstra算法
- python 2x可以打么_15分钟让你了解Python套路,看你能不能坚持的住
- nginx和tomcat之间的简单配置
- TCP/IP协议栈模型分析
- keil5函数 默认返回值_C++ 函数的定义
- ubuntu Gitolite管理git server代码库权限
- Linux查看系统的负载
- Java第二十七篇:二维数组打印九九乘法表(三角形、矩形、菱形)
- jsp与jspx文件
- SAP ERP 与 Oracle ERP 比较(转)
- 打开oracle dmp,详细教您怎么打开dmp文件
- NO_PROXY is not set
- redis的安装教程(单机、win10)
- 基于javaweb+mysql的个人日记管理系统
- Leetcode:面试题 01.06. 字符串压缩
- 优朋普乐酝酿上市前融资 百度腾讯欲投互联网TV
- php实现大文件断点续传
- 魔方APP项目-07-客户端提交登录信息、在APICloud中集成防水墙验证码,前端获取显示并校验验证码、服务端校验验证码、保存用户登录状态,APICloud提供的数据存储、客户端保存用户登陆数据