什么叫异常(Exception)?顾名思义就是非正常的情况,出现了不希望出现的意外,异常处理就是遇到这种意外时准备的对策和解决方案。比如您开着一辆劳斯莱斯在公路上行走,突然前面出现一个小孩,幸好您眼疾手快紧急刹车,而避免了一场交通事故。在这个例子中突然出现的小孩就是异常,紧急刹车就是异常处理(面对这种突发情况采取的解决方案)。
程序来源于现实,是现实的抽象和模拟,也会有异常,因此异常的处理就显示的极为重要。试想如果您的手机的某个应用使用两下就崩溃了,或都出现各种意想不到情况,您是不有种想扔手机的冲动,您还会再用这种App吗?
C++中的异常处理主要有两种实现方式:(1).返回错误码,(2).try...catch机制捕获异常。
返回错误码是传统的C语言的处理异常的方式。在一个函数中,如果发生某种不该发生的错误或异常,则直接返回一个错误码,函数的调用方在调用该函数的时候根据返回的错误码的类型进行相应的处理。由于C++的历史原因(由C发展而来),为了兼容性,现在的C++程序中仍能看到很多用错误码的方式向上抛出异常信息。
这种方式一般用于对性能的要求较高的地方,常用在系统库和接口的实现中。因为这种方式可以精确控制逻辑,让程序员只关注逻辑和性能,而程序的完整性和健壮性,交给上层调用方去处理。 其实现方式如下:
例【1】对两个浮点数进行除法运算,如果除数为0则抛出错误码。
1.定义错误码 RetCode.h
#ifndef RETCODE_H
#define RETCODE_H
typedef long ReturnCode;
//成功
#define RT_OK 0L
//失败
#define RT_FAILED 1L
//参数错误
#define RT_PARAM_ERROR 2L
//无法预知的错误
#define RT_UNEXPECTED_ERROR 3L
//空指针
#define RT_NULL_PTR 4L
//分配内存错误
#define RT_ALLOCATE_MEMORY_FAILED 5L
//不支持的操作
#define RT_UNSUPPORT_OPERATE 6L
#endif /* RETCODE_H */
错误码的实现方式:
#include "RetCode.h"
#include
//判断一个浮点数是否为0
#define DEQUALZEOR(X) ((X) <= 0.0001 && (X) > -0.0001)
//除法运算
ReturnCode division(double dividend, double divisor, double& result)
{
if(DEQUALZEOR(divisor))
return RT_PARAM_ERROR;
else
{
result = dividend / divisor;
return RT_OK;
}
}
int main(int argc, char** argv)
{
double r = 0;
ReturnCode ret = division(5, 0, r);
if(RT_PARAM_ERROR == ret)
{
std::cout << "参数错误,检查是否除数为0。" << std::endl;
} else
{
std::cout << r << std::endl;
}
return 0;
}
上面这种返回错误码的方式,可能有效地定义和控制各种错误和异常,但是每个调用都要检查错误值,极不方便,也容易让程序规模加倍。其实C++有一种专门的机制用于处理异常,那就是try...catch机制。
try { // 抛出异常,或可能抛出异常的调用 } catch (ExceptioinObject e) { // 处理异常 } catch (...) { // 捕获所有类型的异常 } |
说明:
1.try中的代码块用于抛出(throw)异常,或调用可能抛出异常的函数、对象;
2.throw关键字可用于抛出任意类型的对象,可以是类的对象,也可以是内置数据类型的对象(常称为变量),还可以是指针(指针本身就是一个对象,是一个特殊的对象,用于指向另外一个对象的地址);
3.第一个catch括号中的e表示异常对象,这个对象也可以是任意类型的对象。当throw出的对象类型与e的类型相同时,则捕获到异常,进行catch代码块中的异常处理。
4.第二个catch括号中的”...”表示任意类型,可以捕获任意类型的异常。
5.一个try可以对应一个或多个catch,catch子句被检查的顺序与它们在try块之后排列顺序相同,一旦找到了一个匹配,则后续的catch子句将不再检查,按此规则,catch_all子句(catch(...){})处理表前面所列各种异常之外的异常。
6.catch子句可以包含返回语句(return),也可不包含返回语句。包含返回语句,则整个调用函数结束,后面的语句不再执行。而不包含返回语句,则执行catch列表之后的下一条语句。
异常处理,把正常逻辑和错误处理分离开来,由函数实现方抛出异常,由调用者捕获这个异常,调用者就可以知道程序函数调用出现错误了,并去处理,而是否终止程序就把握在调用者手里了。
我们将用上面的例子用try...catch...方式实现
例【2】对两个浮点数进行除法运算,如果除数为0则抛出异常。
#include
//判断一个浮点数是否为0
#define DEQUALZEOR(X) ((X) <= 0.0001 && (X) > -0.0001)
using namespace std;
//除数不为0异常
class DivisorZeorException{
public:
DivisorZeorException(double value) : m_value(value){}
void showInfo()
{
cout << "the divisor " << m_value << " is wrong." << endl;
}
private:
double m_value;
};
double division(double dividend, double divisor)
{
if ( DEQUALZEOR(divisor) )
{
throw DivisorZeorException(divisor);
}
return dividend / divisor;
}
int main()
{
try
{
double result = division(10, 0);
cout << "result: " << result << endl;
} catch (DivisorZeorException e)
{
e.showInfo();
} catch(...)
{
cout << "all exception" << endl;
}
return 0;
}
(1).异常规范
可在函数的后面用throw列出可能抛出的异常,并保证该函数不会抛出任何其他类型的异常。如上面的division函数改为:
double division(double dividend, double divisor) throw(DivisorZeorException)
{
if ( DEQUALZEOR(divisor) )
{
throw DivisorZeorException(divisor);
}
return dividend / divisor;
}
如果在运行时,函数抛出了一个没有被列在它的异常规范中的异常(并且函数中所抛出的异常,没有在该函数内部处理),则系统调用C++标准库中定义的函数unexpected()。如果异常规范形式为throw(),则表示不得抛出任何异常。
(2).异常类的继承
异常类也可以继承,在catch捕获异常的时候应按照由子类到父类的顺序,因为atch子句被检查的顺序与它们在try块之后排列顺序相同,所以在catch子句列表中最特化的(匹配条件最严格的,即子类)catch子句必须先出现 。假设有三个异常类,ExceptionC是ExceptionB的子类,ExceptionB是ExceptionA的子类,try...catch...就写成:
try
{
//可能抛出异常的语句
}
catch (ExceptionC c)
{
//处理ExceptionC异常
}
catch (ExceptionB b)
{
//处理ExceptionB异常
}
catch (ExceptionA a)
{
//处理ExceptionA异常
}
参考资料:http://www.weixueyuan.net/view/5881.html
C++标准中已经定义了一套常用的异常类,它们之间的层次关系如下:
exception是所有异常类的父类,仅仅定义了拷贝构造函数、拷贝赋值运算符、一个虚析构函数和一个名为what的虚成员,what函数返回一个const char*,用于返回一些异常信息。
C++标准中定义的类虽然不多,但我们在定义自己的异常类的时候还是应该尽量利用已有的异常类,至少就继承自exception类,保持结构的统一性。
Java有一套非常完备的异常处理机制,使用起来简单而灵活。JDK把一些常见的异常都封装成了一个一个具体的类,java.lang.Throwable是所有异常类的父类。异常处理类的主要层次关系如下:
Throwable 类是 Java 语言中所有错误或异常的超类。Error是应用程序不应该试图捕获的严重问题,比如OutOfMemoryError、ThreadDeath等,在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error 的任何子类,因为这些错误可能是再也不会发生的异常条件。Exception 是所有异常类的父类,程序本身可以处理的异常。Exception又分为运行时异常(发生在程序运行过程中,又叫uncheckException)和非运行时异常(发生在编译阶段,又称checkException)。
RuntimeException及其所有子类都属于运行时异常,这类异常在编译时不会被检测,只有在运行时才会发生。程序中可以选择捕获处理,也可以不处理(不捕获程序也能编译通过),但基于程序健壮性方面考虑,对有可能出现的异常一定要捕获处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
在Exception范围内,除了运行时异常的类都属于非运行时异常,又叫受检查异常类。这类异常在编译时会被检测,调用了会抛出这类异常的方法,如果调用方不捕获异常又不向上抛出异常,编译将会报错。这类异常一般是没有遵守Java语言规范的代码造成的,容易看的出来,并且较容易解决,常见的如IOException,SQLException。
我们再看看一些常见异常的继承关系,如下图
抓取异常的方法(或说语法)是try、catch、finally,其结构如下:
try { //1.可能会抛出异常的代码块 } catch (Throwable throwable) { //2.异常发生时的处理方案 } finally { //3.不管有没有捕获到异常(异常有没有发生),都会执行的代码 //此部分也可省略 } |
说明:catch括号中的条件必须是Throwable或其子类的对象。try后面必须跟catch或finally(catch和finally必须至少有一个或两都有)。 finally括起来的代码不管有没有捕获到异常,都会执行,IO操作、数据库操作的close方法一般都放在这里,因为不管有没有发生异常,在程序退出前都需要释放资源。
1.用Java实现文件的拷贝,将制定文件的内容复制到另一个文件。
public void copyFile(String source, String destination) {
File file = new File(source);
BufferedReader in = null;
BufferedWriter out = null;
try {
if (file.exists() && file.isFile()) {
in = new BufferedReader(new FileReader(file));
out = new BufferedWriter(new FileWriter(new File(destination)));
String line;
StringBuffer str = new StringBuffer();
while (null != (line = in.readLine())) {
str.append(line + "\r\n");
}
out.write(str.toString());
} else {
throw new FileNotFoundException("此文件不存在或此象路径名表示的文件不是一个标准文件");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.自定义异常
需求:要从控制台上输入一串内容,但输入的必须是数字,如果输入的不是数字,抛出异常。
分析:JDK的库中好像并没有针对控制台输入的异常类,因此需要自己定义一个类来监控输入的异常。
import java.io.IOException;
import java.util.Scanner;
//自定义异常类
class ConsoleInputException extends IOException {
public ConsoleInputException() {
super();
System.err.println("The input content is not number.");
}
public ConsoleInputException(String message) {
super(message);
}
}
public class Test {
//控制台输入数字
public static void inputFromConsole() throws ConsoleInputException{
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()) {
String line = scanner.next();
if(line.matches("\\d+")) { //判断是否为数字
System.out.println("contet:" + line);
} else {
throw new ConsoleInputException();
}
}
scanner.close();
}
public static void main(String[] args) {
try {
inputFromConsole();
} catch (ConsoleInputException e) {
e.printStackTrace();
}
}
}
JavaScript不像C++和Java一样,有专门的语法去定义和实现异常处理,JavaScript的异常处理主要体现在对页面输入内容的校验。JavaScript的异常处理的实现方式主要有两种,一种是alert弹出警告框,一种是在页面上给出错误提示。(可能还有其它方式,本人才疏学浅没掌握全,如果您知道请告诉我)
如下面这样一个登录页面,在点击登录按钮时,检查输入的用户名格式是否正确,用户名要求是邮箱格式。您可以有如下两种实现方式。
上面这种方式虽然能实现功能,但每次都弹出提示框,是不很难看,且很不友好?其实我们可以有更优雅的提示方式,就是在页面上专门预留一个位置来提示用户错误的操作。如下
function checkForm() {
//username为用户名表单的ID
var usertName = document.getElementById("username").value;
if(!isEmail(usertName)) {
document.getElementById( "showTipsInfo").innerHTML = "请输入正确的邮箱!"
document.getElementById("infoTips").style.display = "block";
} else {
document.getElementById("infoTips").style.display = "none";
var form = document.getElementById("loginForm");
form.submit();
}
}
网页HTML:(这里使用了第三方库bootstrap,如果有些样式看不懂请忽略这些样式)
当输入正确时,什么也不提示,保持页面整洁:
当输入错误时,显示错误信息:
1.使程序更稳定,更健壮;
2.友好提示,改善用户体验;
3.给出错误信息,方便程序维护;
4.可以处理一些原子性的操作
(比如交易等数据操作时,如果发生错误,可在捕获到异常时重置成操作之前的状态,即叫回滚,数据库中的概念在特定的情况下也可以由代码来实现)
如果您有什么疑惑和想法,请在评论处给予反馈,您的反馈就是最好的测评师!由于本人技术和能力有限,如果本博文有错误或不足之处,敬请谅解并给出您宝贵的建议!
========================编程思想系列文章回顾========================
编程思想之正则表达式
编程思想之迭代器
编程思想之递归
编程思想之回调