对异常处理的简单总结

一.异常的概念:

  • 程序在运行的过程中可能产生异常。
  • 异常(Exception)与Bug的区别:
    异常是程序运行时可预料的执行分支。
    Bug是程序中的错误,是不被预期的运行方式。
  • 异常(Exception)和Bug的对比:
    • 异常:运行时产生除0的情况,需要打开的外部文件不存在,数组访问时越界
    • Bug:使用野指针
      堆数组使用结束未释放
      选择排序无法处理长度为0的数组

二、语言异常处理

C语言

强制终止
  • 使用标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于头文件中。(exitabort是stdlib标准库中的内置函数,函数 void abort(void) 中止程序执行,直接从调用的地方跳出。)
 #include<stdio.h>
 #include<stdlib.h>
double diva(double num1,double num2){
//两数相除函数
    double re;
    re=num1/num2;
     return re;
}
int main(){
    double a,b,result;
    printf("请输入第一个数字:");
    scanf("%lf",&a);
    printf("请输入第二个数字:");
    scanf("%lf",&b);
    if(0==b)    exit(0);/*or abort();*/  //如果除数为0终止程序17  
 	result=diva(a,b);
    printf("相除的结果是: %.2lf\n",result);
    return 0;
}
assert触发
  • 使用assert(断言)宏调用,当程序出错时,就会引发一个abort()。(assert定义在assert.h头文件中)
void assert(int expression);
expression -- 这可以是一个变量或任何 C 表达式。如果 expression 为 TRUE,
assert() 不执行任何动
作.如果 expression 为 FALSE,assert() 会在标准错误 stderr 上显示错误消
息,并中止程序执行。
#include <assert.h>
#include <stdio.h>
int main(){
   int a;
   char str[50]; 
   printf("请输入一个整数值: ");
   scanf("%d", &a);
   assert(a >= 10);
   printf("输入的整数是: %d\n", a);  
   printf("请输入字符串: ");
   scanf("%s", str);
   assert(str != NULL);
   printf("输入的字符串是: %s\n", str);
   return(0);
}
errno设置
  • 使用errno全局变量,由C运行时库函数提供,位于头文件中。 库宏 extern int errno 是通过系统调用设置的,在错误事件中的某些库函数表明了什么发生了错误。(不常用难以理解)
Goto函数内跳转
  • 使用goto语句,当出错时跳转。C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。
    注意在任何编程语言中,都不建议使用 goto 语句。因为它使得程序的控制流难以跟踪,使程序难以理解和难以修改。任何使用 goto 语句的程序可以改写成不需要使用 goto 语句的写法。
#include <stdio.h>
int main (){
   /* 局部变量定义 */
   int a = 10;
   /* do 循环执行 */
   LOOP:do{
      if( a == 15){
         /* 跳过迭代 */
         a = a + 1;
         goto LOOP;
      }
      printf("a 的值: %d\n", a);
      a++; 
   }while( a < 20 );
   return 0;
}
setjmp,longjmp跳转。
  • 我们都知道goto在c语言中会经常用到,但是goto的跳转只能再同一个函数内进行跳转。如果我不想在同一个函数内进行跳转,而是在不同函数之间进行跳转,这就需要用到setjmp 和 longjmp函数。
  • 宏 int setjmp(jmp_buf environment) :创建本地的jmp_buf缓冲区并且初始化,用于将来跳转回此处。这个子程序保存程序的调用环境于env参数所指的缓冲区,env将被longjmp使用。如果是从setjmp直接调用返回,setjmp返回值为0。如果是从longjmp恢复的程序调用环境返回,setjmp返回非零值。
  • 函数 void longjmp(jmp_buf environment, int value) 恢复最近一次调用 setjmp() 宏时保存的环境,jmp_buf 参数的设置是由之前调用 setjmp() 生成的。
#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;				//全局变量不建议定义,若无初始赋值则为0
void second(void) {
    printf("second\n");         // 打印
    longjmp(buf,1);             // 跳回setjmp的调用处 - 使得setjmp返回值为1
}
void first(void) {
    second();
    printf("first\n");          // 不可能执行到此行
}
int main() {   
    if ( ! setjmp(buf) ) {
        first();                // 进入此行前,setjmp返回0
    } else {                    // 当longjmp跳转回,setjmp返回1,因此进入此行
        printf("main\n");       // 打印
    }
    return 0;
}

输出结果:
second
main

C++

throw表达式
  • 异常检测部分使用了throw 表达式来表示它遇到了无法处理的问题。我们说throw引发了异常。
    那么如何添加自己写的错误信息呢?可以用 头文件定义的其他异常
    类,比如 runtime_error,表示只有在运行时才能检测出的问题。示
    例如下:
#include <iostream>
using namespace std;

int main(){
    int a=5,b=0;
    if(b==0)
        throw runtime_error("Error: b=0");
    return 0;
}
//output:
terminate called after throwing an instance of 'std::runtime_error'
  what():  Error: b=0
   其中 what() 是C++异常类型唯一定义的成员函数,该函数没有任何参数,返回
值是一个 const char* 的字符串。
try 语句块
  • 异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句结束。try 语句块中代码抛出的异常通常被某个catch子句处理。catch子句也被称为异常处理代码。
#include <iostream>
using namespace std;
int main(){
    double m ,n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if( n == 0)
            throw -1; //抛出int类型异常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch(double d) {
        cout << "catch(double) " << d <<  endl;
    }
    catch(int e) {
        cout << "catch(int) " << e << endl;
    }
    cout << "finished" << endl;
    return 0;
}
异常类
  • 一套异常类,用于在throw表达式和相关catch子句之间传递异常的具体信息。
	C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生
  	而来的。常用的几个异
     常类如图 1 所示。

在这里插入图片描述

bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 stdexcept。

			下面分别介绍以上几个异常类。本节程序的输出以 Visual Studio 
			2010为准,Dev C++ 编译
			的程序输出有所不同。
			1) bad_typeid
				使用 typeid 运算符时,如果其操作数是一个多态类的指针,
				而该指针的值为 NULL,则会拋出此异常。
			2) bad_cast
			    在用 dynamic_cast 进行从多态基类对象(或引用)到派生类
			    的引用的强制类型转换时,如果转换是不安全的,则会拋出此
			    异常。程序示例如下:
#include <iostream>
#include <stdexcept>
using namespace std;
class Base{
    virtual void func() {}
};
class Derived : public Base{
public:
    void Print() {}
};
void PrintObj(Base & b){
    try {
        Derived & rd = dynamic_cast <Derived &>(b);
        //此转换若不安全,会拋出 bad_cast 异常
        rd.Print();
    }
    catch (bad_cast & e) {
        cerr << e.what() << endl;
    }
}
int main(){
    Base b;
    PrintObj(b);
    return 0;
}

程序的输出结果如下:

Bad dynamic_cast!
在 PrintObj 函数中,通过 dynamic_cast 检测 b 是否引用的是一个 Derived 对象,如果是,就调用其 Print 成员函数;如果不是,就拋出异常,不会调用 Derived::Print。

		3) bad_alloc
		在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会
		引发此异常。程序示例如下:
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
    try {
        char * p = new char[0x7fffffff];  //无法分配这么多空间,会抛出异常
    }
    catch (bad_alloc & e)  {
        cerr << e.what() << endl;
    }
    return 0;
}

程序的输出结果如下:

bad allocation
ios_base::failure
在默认状态下,输入输出流对象不会拋出此异常。如果用流对象的 exceptions 成员函数设置了一些标志位,则在出现打开文件出错、读到输入流的文件尾等情况时会拋出此异常。此处不再赘述。

		4) out_of_range
		用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标
		越界,则会拋出此异常。
		例如:
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
int main()
{
    vector<int> v(10);
    try {
        v.at(100) = 100;  //拋出 out_of_range 异常
    }
    catch (out_of_range & e) {
        cerr << e.what() << endl;
    }
    string s = "hello";
    try {
        char c = s.at(100);  //拋出 out_of_range 异常
    }
    catch (out_of_range & e) {
        cerr << e.what() << endl;
    }
    return 0;
}

程序的输出结果如下:

invalid vector <T>subscript
invalid string position
如果将v.at(100)换成v[100],将s.at(100)换成s[100],程序就不会引发异常(但可能导致程序崩溃)。因为 at 成员函数会检测下标越界并拋出异常,而 operator[] 则不会。operator [] 相比 at 的好处就是不用判断下标是否越界,因此执行速度更快。

Java

java 的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Exception 对象来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性

使用try-.catch-finally捕获异常
  • Java 异常处理机制为:抛出异常,捕捉异常,处理异常。

  • Java–.catch 语法格式:

try {
//业务功能代码
}
catch(异常类1 e1){
//异常处理代码1
}
catch(异常类2 e2){
//异常处理代码2
}
catch(异常类n en){
//异常处理代码n
}

  • 在使用try…catch捕获处理异常时需要注意:

    ①不要过度使用异常,不能使用异常处理机制来代替正常的流程控制语句;

    ②异常捕获时,一定要先捕获小异常,再捕获大异常。否则小异常将无法被捕获;

    ③避免出现庞大的try块;

    ④避免使用catch (Exception e) {};

    ⑤不要略异常

class Main {
  public static void main(String[] args) {
    try {
      // code that generate exception
      int divideByZero = 5 / 0;
      System.out.println("Rest of code in try block");
    }catch (ArithmeticException e) {
      System.out.println("ArithmeticException => " + e.getMessage());
    }
  }
}
throws抛出
 throws是另一种处理异常的方式,它不同于try...catch...finally,throws仅
 仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
        throws声明:如果一个方法内部的代码会抛出检查异常(checked 
     exception),而方法自己又没有完全处理掉或并不能确定如何处理这种异常,
     则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异
     常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理,
     否则编译不通过。
        在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常
     类型可以是方法中产生的异常类型,也可以是它的父类。采取这种异常处理的
     原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,
     调用者需要为可能发生的异常负责。
    - 语法格式:
    修饰符 返回值类型 方法名() throws ExceptionType1 , ExceptionType2
     ,ExceptionTypeN{ 
        //方法内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 
        类的异常,或者他们的子类的异常对象。
    }
import java.io.*;
public class ThrowsTest {
    public static void main(String[] args) {
        ThrowsTest t = new ThrowsTest();
        try {
            t.readFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public void readFile() throws IOException {
        FileInputStream in = new FileInputStream("hello.txt");
        int b;
        b = in.read();
        while (b != -1) {
            System.out.print((char) b);
            b = in.read();
        }
        in.close();
    }
}
手动抛出throw
  • 程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:

throw new String(“你抛我试试.”);

  • throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。

public class Main {
    public static void main(String[] args) {
        try {
            Student s = new Student();
            s.regist(-1001);
            System.out.println(s);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

class Student {
    private int id;

    public void regist(int id) throws Exception {
        if (id > 0) {
            this.id = id;
        } else {
            // System.out.println("您输入的数据非法!");

            //手动抛出异常对象
            // throw new RuntimeException("您输入的数据非法!");
            throw new Exception("您输入的数据非法!");

            //错误的
            // throw new String("不能输入负数");
        }

    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }
}
throws和throw的区别:
    throw是语句抛出一个异常。
            语法:throw (异常对象);
                throw e;
    throws是方法可能抛出异常的声明。
    (用在声明方法时,表示该方法可能要抛出异常)
            语法:[(修饰符)](返回值类型)(方法名)([参数列表])
            [throws(异常类)]{......}
                public void doA(int a) throws Exception1,Exception3
                {......}

    throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
    throws出现在方法函数头,表示在抛出异常,由该方法的调用者来处理。

    throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获
    这个异常。
    throw是具体向外抛异常的动作,所以它是抛出一个异常实例。

    throws说明你有那个可能倾向。
    throw的话,那就是你把那个倾向变成真实的了。

    两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是
    抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函
    数的上层调用处理。

Python的异常机制

Python的异常机制主要依赖try 、except 、else、 finally 和raise五个关键字:try关键字后缩进的代码块简称try块,它里面放置的是可能引发异常的代码;在except后对应的是异常类型和一个代码块,用于表明该except块处理这种类型的代码块;在多个except 块之后可以放一个else块,表明程序不出现异常时还要执行else块;最后还可以跟一个finally 块,finally 块用于回收在try块里打开的物理资源,异常机制会保证finally块总被执行;raise用于引发一个实际的异常,raise 可以单独作为语句使用,引发一个具体的异常对象。

常见的Python异常
AssertionError:断言语句(assert)失败
     在以前的分支和循环章节中讲过断言语句(assert),当assert这个关键字
     后边的条件为假的时候,程序将终止并抛出AssertionError异常。assert语
     句一般是在测试程序的时候用于在代码中置入检查点:

请添加图片描述

AttributeError:尝试访问未知的对象属性
     当试图访问的对象属性不存在时抛出的异常:

请添加图片描述

IndexError:索引超出序列的范围
     在使用序列的时候就常常会遇到IndexError异常,原因是索引超出序列范围
     的内容:

请添加图片描述

KeyError:字典查找一个不存在的关键字
     当试图在字典中查找一个不存在的关键字时就会引发KeyError异常,因此建
     议使用dict.get()方法:

请添加图片描述

NameError:尝试访问一个不存在的变量
     当尝试访问一个不存在的变量时,Python会抛出NameError异常:

请添加图片描述

OSError:操作系统产生的异常
     OSError顾名思义就是操作系统产生的异常,像打开一个不存在的文件会引
     发FileNotFoundError,而这个FileNotFoundError就是OSError的子类。
     上面的例子已经演示过,这里就不再赘述了。

~~

SyntaxError:Python的语法错误
     如果遇到SyntaxError是Python的语法错误,这时Python的代码并不能继续
     执行,你应该找到并改正错误:

请添加图片描述

TypeError:不同类型间的无效操作
     有些类型不同是不能相互进行计算的,否则会抛出TypeError异常:

请添加图片描述

ZeroDivisionError:除数为零
     我们都知道除数不能为零,所以除以零就会引发ZeroDivisionError异常:

请添加图片描述

try-except语句

try-except语句格式如下:

try:
  检测范围
except Exception[as reason]:
  出现异常(Exception)后的处理代码

请添加图片描述

导致OSError异常的原因有很多(例如FileNotFoundError、FileExistsError、PermissionError等),所以可能会更在意错误的具体内容,这里可以使用as把具体的错误消息打印出来:

请添加图片描述

  • 针对不同异常设置多个except
    一个try语句可以和多个except语句搭配,分别对感兴趣的异常进行检测处理:
    请添加图片描述

  • 对多个异常统一处理
    except后面还可以跟多个异常,然后对这些异常进行统一的处理:
    请添加图片描述

  • 捕获所有异常
    如果你无法确定要对哪一类异常进行处理,只是希望在try语句块里一旦出现任何异常,可以给用户一个“看得懂”的提醒,那么可以这么做:
    请添加图片描述

在上述实验中文件打开了,但并没有执行关闭文件的命令,这是我们就可使用finally语句结束

raise语句
 有些读者可能会思考,我的代码能不能自己抛出一个异常呢?答案是可以的,你
 可以使用raise语句抛出一个异常:

请添加图片描述

 抛出的异常还可以带参数,表示异常的解释:

请添加图片描述

三、总结

你不可能总是对的
我们是人,也会经常犯错,程序员也不例外,就算是经验丰富的码农,也不能保证
写的代码百分之百没有任何问题。另外,作为一个合格的程序员,在编程时要意识
到一点,那就是永远不要相信你的用户,所以要多方面考虑,这样写出来的程序才
会更加安全稳定。

那么既然程序总会出问题,那我们就需要用适当的方法去解决问题。程序出现逻辑错误或者用户输入不合法都会引发异常,但这些异常并不是致命的,不会导致程序崩溃,可以利用语言提供的异常处理机制,在异常出现的时候及时捕获,并从内部自我消化掉。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值