异常处理机制专题

前言

1)异常是一种程序控制机制,与函数机制独立和互补

      函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.

2)异常设计目的:

    栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。

异常设计出来之后,却发现在错误处理方面获得了最大的好处。

1 异常处理的基本思想

1.1传统错误处理机制

    通过函数返回值来处理错误。

1.2异常处理的基本思想

1)C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。

2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图

3)异常超脱于函数机制,决定了其对函数的跨越式回跳。

4)异常跨越函数

2 C++异常处理的实现

2.1异常基本语法

1) 若有异常则通过throw操作创建一个异常对象并抛掷。

2) 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。

3) 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。

4) catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。

5) 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。

6)处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。

 

案例1:被零整除案例

int divide(int
x, int
y)

{

    if (y == 0)

    {

        throw
x;

    }

    return
x / y;

}

 

void main41()

{

    try

    {

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

        cout << "10/0 =" << divide(10, 0) << endl;

    }

    catch (int e)

    {

        cout << "e" << " is divided by zero!" << endl;

    }

    catch (...)

    {

        cout << "未知异常" << endl;

    }

 

    cout << "ok" << endl;

    system("pause");

    return;

}

 

案例2:

class
A {};

void f() {

    if (...) throw
A;

}

void g() {

    try {

        f();

    }

    catch (B) {

        cout << "exception B\n";

    }

}

int main() {

    g();

}

 

 

throw A将穿透函数f,g和main,抵达系统的最后一道防线——激发terminate函数.

该函数调用引起运行终止的abort函数.

最后一道防线的函数可以由程序员设置.从而规定其终止前的行为.

修改系统默认行为:

  • 可以通过set_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理:
  • void myTerminate(){cout<<"HereIsMyTerminate\n";}
  • set_terminate(myTerminate);
  • set_terminate函数在头文件exception中声明,参数为函数指针void(*)().

 

案例3:

  • 构造函数没有返回类型,无法通过返回值来报告运行状态,所以只通过一种非函数机制的途径,即异常机制,来解决构造函数的出错问题。

 

7)异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题

比如:

class
A {};

class
B {};

 

int main()

{

    try

    {

        int     j = 0;

        double     d = 2.3;

        char     str[20] = "Hello";

        cout << "Please input a exception number: ";

        int a;

        cin >> a;

        switch (a)

        {

        case 1:

            throw d;

        case 2:

            throw j;

        case 3:

            throw str;

        case 4:

            throw
A();

        case 5:

            throw
B();

        default:

            cout << "No throws here.\n";

        }

    }

    catch (int)

    {

        cout << "int exception.\n";

    }

    catch (double)

    {

        cout << "double exception.\n";

    }

    catch (char*)

    {

        cout << "char* exception.\n";

    }

    catch (A)

    {

        cout << "class A exception.\n";

    }

    catch (B)

    {

        cout << "class B exception.\n";

    }

    cout << "That's ok.\n";

    system("pause");

}//====================================

 

catch代码块必须出现在try后,并且在try块后可以出现多个catch代码块,以捕捉各种不同类型的抛掷。

异常机制是基于这样的原理:程序运行实质上是数据实体在做一些操作,因此发生异常现象的地方,一定是某个实体出了差错,该实体所对应的数据类型便作为抛掷和捕捉的依据。

8)异常捕捉严格按照类型匹配

  • 异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到.例如下列代码不会输出"int exception.",从而也不会输出"That's ok." 因为出现异常后提示退出

int main(){

try{

throw 'H';

}catch(int){

cout<<"int exception.\n";

}

cout<<"That's ok.\n";

}

2.2栈解旋(unwinding)

异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。

 

 

class
MyException {};

 

class
Test

{

public:

    Test(int
a = 0, int
b = 0)

    {

        this->a = a;

        this->b = b;

        cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl;

    }

    void printT()

    {

        cout << "a:" << a << " b: " << b << endl;

    }

    ~Test()

    {

        cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl;

    }

private:

    int a;

    int b;

};

 

void myFunc() throw (MyException)

{

    Test t1;

    Test t2;

 

    cout << "定义了两个栈变量,异常抛出后测试栈变量的如何被析构" << endl;

 

    throw
MyException();

}

 

void main()

{

    //异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,

    //都会被自动析构。析构的顺序与构造的顺序相反。

    //这一过程称为栈的解旋(unwinding)

    try

    {

        myFunc();

    }

    //catch(MyException &e) //这里不能访问异常对象

    catch (MyException) //这里不能访问异常对象

    {

        cout << "接收到MyException类型异常" << endl;

    }

    catch (...)

    {

        cout << "未知类型异常" << endl;

    }

 

    system("pause");

    return;

}

 

2.3异常接口声明

1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:

    void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常

2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:

    void func();

3)一个不抛掷任何类型异常的函数可以声明为:

    void func() throw();

4) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。

2.4异常类型和异常变量的生命周期

1)throw的异常是有类型的,可以使,数字、字符串、类对象。

2)throw的异常是有类型的,catch严格按照类型进行匹配。

3)注意 异常对象的内存模型

2.2.1 传统处理错误

//文件的二进制copy

int filecopy01(char *filename2, char *filename1)

{

    FILE *fp1 = NULL, *fp2 = NULL;

 

    fp1 = fopen(filename1, "rb");

    if (fp1 == NULL)

    {

        return 1;

    }

 

    fp2 = fopen(filename2, "wb");

    if (fp1 == NULL)

    {

        return 2;

    }

 

    char buf[256];

    int readlen, writelen;

    while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0

    {

        writelen = fwrite(buf, 1, readlen, fp2);

        if (readlen != readlen)

        {

            return 3;

        }

    }

 

    fclose(fp1);

    fclose(fp2);

    return 0;

}

测试程序

void main11()

{

    int ret;

    ret = filecopy01("c:/1.txt", "c:/2.txt");

    if (ret != 0)

    {

        switch (ret)

        {

        case 1:

            printf("打开源文件时出错!\n");

            break;

        case 2:

            printf("打开目标文件时出错!\n");

            break;

        case 3:

            printf("拷贝文件时出错!\n");

            break;

        default:

            printf("发生未知错误!\n");

            break;

        }

    }

}

2.2.2 throw int类型异常

/ 文件的二进制copy

void filecopy02(char *filename2, char *filename1)

{

    FILE *fp1 = NULL, *fp2 = NULL;

 

    fp1 = fopen(filename1, "rb");

    if (fp1 == NULL)

    {

        //return 1;

        throw 1;

    }

 

    fp2 = fopen(filename2, "wb");

    if (fp1 == NULL)

    {

        //return 2;

        throw 2;

    }

 

    char buf[256];

    int readlen, writelen;

    while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0

    {

        writelen = fwrite(buf, 1, readlen, fp2);

        if (readlen != readlen)

        {

            //return 3;

            throw 3;

        }

    }

 

    fclose(fp1);

    fclose(fp2);

    return;

}

2.2.3 throw字符类型异常

//文件的二进制copy

void filecopy03(char *filename2, char *filename1)

{

    FILE *fp1 = NULL, *fp2 = NULL;

 

    fp1 = fopen(filename1, "rb");

    if (fp1 == NULL)

    {

        throw
"打开源文件时出错";

    }

 

    fp2 = fopen(filename2, "wb");

    if (fp1 == NULL)

    {

        throw
"打开目标文件时出错";

    }

 

    char buf[256];

    int readlen, writelen;

    while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0

    {

        writelen = fwrite(buf, 1, readlen, fp2);

        if (readlen != readlen)

        {

            throw
"拷贝文件过程中失败";

        }

    }

 

    fclose(fp1);

    fclose(fp2);

    return;

}

 

2.2.4 throw类对象类型异常

//throw int类型变量

//throw 字符串类型

//throw 类类型

class
BadSrcFile

{

public:

    BadSrcFile()

    {

        cout << "BadSrcFile 构造 do " << endl;

    }

    ~BadSrcFile()

    {

        cout << "BadSrcFile 析构 do " << endl;

    }

    BadSrcFile(BadSrcFile & obj)

    {

        cout << "拷贝构造 do " << endl;

    }

    void toString()

    {

        cout << "aaaa" << endl;

    }

 

};

class
BadDestFile {};

class
BadCpyFile {};;

 

void filecopy04(char *filename2, char *filename1)

{

    FILE *fp1 = NULL, *fp2 = NULL;

 

    fp1 = fopen(filename1, "rb");

    if (fp1 == NULL)

    {

        //throw new BadSrcFile();

        throw
BadSrcFile();

    }

 

    fp2 = fopen(filename2, "wb");

    if (fp1 == NULL)

    {

        throw
BadDestFile();

    }

 

    char buf[256];

    int readlen, writelen;

    while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0

    {

        writelen = fwrite(buf, 1, readlen, fp2);

        if (readlen != readlen)

        {

            throw
BadCpyFile();

        }

    }

 

    fclose(fp1);

    fclose(fp2);

    return;

}

main测试案例

 

//结论://C++编译器通过throw 来产生对象,C++编译器再执行对应的catch分支,相当于一个函数调用,把实参传递给形参。

void main11()

{

    try

    {

        //filecopy02("c:/1.txt","c:/2.txt");

        // filecopy03("c:/1.txt","c:/2.txt");

        filecopy04("c:/1.txt", "c:/2.txt");

    }

    catch (int e)

    {

        printf("发生异常:%d \n", e);

    }

    catch (const
char * e)

    {

        printf("发生异常:%s \n", e);

    }

    catch (BadSrcFile *e)

    {

        e->toString();

        printf("发生异常:打开源文件时出错!\n");

    }

    catch (BadSrcFile &e)

    {

        e.toString();

        printf("发生异常:打开源文件时出错!\n");

    }

    catch (BadDestFile e)

    {

        printf("发生异常:打开目标文件时出错!\n");

    }

    catch (BadCpyFile e)

    {

        printf("发生异常:copy时出错!\n");

    }

    catch (...) //抓漏网之鱼

    {

        printf("发生了未知异常! 抓漏网之鱼\n");

    }

    //class BadSrcFile {};

    //class BadDestFile {};

    //class BadCpyFile {};;    

}

 

2.5异常的层次结构(继承在异常中的应用)

  • 异常是类 – 创建自己的异常类
  • 异常派生
  • 异常中的数据:数据成员
  • 按引用传递异常
    • 在异常中使用虚函数

案例:设计一个数组类 MyArray,重载[]操作,

数组初始化时,对数组的个数进行有效检查

  1. index<0 抛出异常eNegative
  2. index = 0 抛出异常 eZero

    3)index>1000抛出异常eTooBig

    4)index<10 抛出异常eTooSmall

    5)eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。

3标准程序库异常

 

案例1:

// out_of_range

#include
"iostream"

using
namespace std;

#include
<stdexcept>

 

class Teacher

{

public:

    Teacher(int age) //构造函数, 通过异常机制 处理错误

    {

        if (age > 100)

        {

            throw out_of_range("年龄太大");

        }

        this->age = age;

    }

protected:

private:

    int age;

};

 

void mainxx()

{

    try

    {

        Teacher t1(102);

    }

    catch (out_of_range e)

    {

 

        cout << e.what() << endl;

    }

 

    exception e;

    system("pause");

}

 

 

案例2

class
Dog

{

public:

    Dog()

    {

        parr = new
int[1024 * 1024 * 100]; //4MB

    }

private:

    int *parr;

};

 

int main31()

{

    Dog *pDog;

    try {

        for (int i = 1; i<1024; i++) //40GB!

        {

            pDog = new
Dog();

            cout << i << ": new Dog 成功." << endl;

        }

    }

    catch (bad_alloc err)

    {

        cout << "new Dog 失败: " << err.what() << endl;

    }

 

    return 0;

 

}

 

案例3

C++复习:异常的更多相关文章

  1. java异常复习

    如果有时学东西概念太多了,可以反着学,从结果到过程,从代码到概念,也许就不会那么枯燥了,比如学反射的时候. java异常复习 异常和错误的区别? 异常:程序或环境本身出现错误.(程序员可以捕获并处理) ...

  2. ndk学习之c++语言基础复习----C++容器、类型转换、异常与文件流操作

    继续来复习C++,比较枯燥,但是这是扎实掌握NDK开发的必经之路,不容小觑. 容器: 容器,就是用来存放东西的盒子. 常用的数据结构包括:数组array, 链表list, 树tree, 栈stack, ...

  3. JavaSE复习_7 异常

    △子父类涉及的异常问题:      1.子类在覆盖方法时,父类的方法如果抛出了异常,那么子类的方法只能抛出父类的异常或者该异常的子类,且只能抛出异常的子集      2.如果父类抛出了多个异常,子类只 ...

  4. JAVA_SE复习(异常)

    异常.调试和断言 一. 异常的分类 1. 可查异常    例: 2. 不可查异常  例:Runtime Exception 3. 异常的分类结构: 1. 不执行finally 子句的唯一情况是虚拟机关 ...

  5. Java异常类复习总结

    个人理解先行: 异常类是当在程序出现问题时抛出的一个警告.提示你程序设计或者代码有存在错误的地方. 异常类和Error都继承自Throwable, Throwable继承自Object类. Runti ...

  6. C++复习8.异常处理和RTTI

    C++异常处理和RTTI技术 20130930 1.异常处理的基本知识 C语言中是没有内置运行时错误处理机制,对于错误发生的时候使用的几种处理机制: 函数返回彼此协商后统一定义的状态编码来表示操作成功 ...

  7. JavaSE复习(三)异常与多线程

    异常 分类 编译时期异常:checked异常. 在编译时期,就会检查,如果没有处理异常,则编译失败.(如日期格式化异常) 运行时期异常:runtime异常. 在运行时期,检查异常.在编译时期,运行异常 ...

  8. .Net基础篇_学习笔记_第六天_异常捕获复习及断点调试

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  9. java复习(4)异常

    1.Java异常的分类和类结构图 1.Throwable是整个java异常体系的超类,所有的异常类都派生自这个类,包含Error和Exception这两个直接的子类,概括了所有能被当做异常跑出来的东西 ...

随机推荐

  1. 多款Android播放器源码集锦

    原帖地址:http://blog.csdn.net/jingwen3699/article/details/7765804/

  2. route

    route   添加/删除一条到192.168.3.0/24的路由,网关为192.168.1.254? route add/del  -net 192.168.3.0 netmask 255.255. ...

  3. lua 安装

    1:下载安装 curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz tar zxf lua-5.3.0.tar.gz cd lua-5.3.0 make ...

  4. 如何在ubuntu系统里面用新加装的硬盘对系统进行扩容

    我这里是用256G的固态硬盘安装了系统,想通过扩展1T的机械硬盘存储数据的,现在我们需要的就是把这个1T的硬盘进行扩容进去 使用df -h和sudo fdisk -l命令查看磁盘情况 切换到root用 ...

  5. Java 13 - Java 数组

    Java 数组 数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同. Java语言中提供的数组是用来存储固定大小的同类型元素. 你可以声明一个数组变量,如num ...

  6. jQuery实现鼠标滑过图片列表加遮罩层

    这个例子实现的功能是:有一列图片列表,鼠标滑过时,将有遮罩层的另一张图盖在该图片的上方,实现鼠标hover的效果. 一.HTML代码: <div class="home-content ...

  7. BZOJ3812主旋律

    /* 这道题其实没有看懂 所以整理一下吧 首先思想转化成所有方案减去不强联通的方案 不强联通的方案相当于很多强联通分量缩点后的dag 转化成子问题, 问很多点的dag方案数 然后枚举作为出度为0的点集 ...

  8. java正则表达式替换空格和换行符

    public class StringUtil {        public static String getStringNoBlank(String str) {            if(s ...

  9. 'pip' 不是内部或外部命令

    安装好Python,在环境变量Path中加入相应路径信息后,Python命令没问题,但是运行pip失败: 'pip' 不是内部或外部命令,Pip工具已经自带安装好了,只是跟Python命令一样,需要我 ...

  10. .NET MVC同页面显示从不同数据库(mssql、mysql)的数据

    控制器: private readonly VipViewModel _model = new VipViewModel(); public static string Msg;// GET: Sys ...