C++异常处理

简介: C++异常处理

栈解旋

 

异常抛出后,从进入try块起,到异常被处理,这期间在栈上的构造的所有对象都会被自动析构。析构的顺序与构造的顺序相反。这一过程叫做解旋。

【也就是说栈模型先进后出并没有被打破,当异常throw抛掷,栈上的对象会被析构】

#include <iostream>
using namespace std;
class MyException{};
class Test
{
public:
    Test(int a=0,int b=0)
    {
        this->a = a;
        this->b = b;
        cout<<"构造函数被执行"<<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(1,2);
    cout<<"定义了两个变量,异常抛出后测试栈变量如何被析构"<<endl;
    throw MyException();
}
int main(void)
{
    try
    {
        myFunc();
    }
    catch(MyException)
    {
        cout<<"正在处理异常类型 MyException"<<endl;
    }
    catch(...)
    {
        cout<<"未知异常类型"<<endl;
    }
    return 0;
}

异常接口声明

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

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

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

3.一个不抛出任何异常的的函数可以声明为

 void func throw();

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


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

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

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

#include <iostream>
using namespace std;
//throe int
void filecopy02(char *filename2,char *filename1)
{
    FILE *fp1 = nullptr;
    FILE *fp2 = nullptr;
    fp1 = fopen(filename1,"rb");
    if(fp1 == nullptr)
    {
        throw 1;
    }
    fp2 = fopen(filename2,"wb");
    if(fp2 == nullptr)
    {
        throw 2;
    }
    char buf[256];
    int readlen,writelen;
    readlen=fread(buf,1,256,fp1);
    while(!feof(fp1))
    {
        writelen = fwrite(buf,1,readlen,fp2);
        if(readlen != writelen)
        {
            throw 3;
        }
    }
    fclose(fp1);
    fclose(fp2);
}
//throw string
void filecopy03(char *filename2,char *filename1)
{
    FILE *fp1 = nullptr;
    FILE *fp2 = nullptr;
    fp1 = fopen(filename1,"rb");
    if(fp1 == nullptr)
    {
        throw "打开源文件失败";
    }
    fp2 = fopen(filename2,"wb");
    if(fp2 == nullptr)
    {
        throw "打开目标文件失败";
    }
    char buf[256];
    int readlen,writelen;
    readlen=fread(buf,1,256,fp1);
    while(!feof(fp1))
    {
        writelen = fwrite(buf,1,readlen,fp2);
        if(readlen != writelen)
        {
            throw "文件拷贝过程失败";
        }
    }
    fclose(fp1);
    fclose(fp2);
}
//throw class
class BadSrcFile
{
public:
    void toString()
    {
        cout<<"aaaaaa"<<endl;
    }
};
class BadDestFile
{
};
class BadCpyFile
{
};
void filecopy04(char *filename2,char *filename1)
{
    FILE *fp1 = nullptr;
    FILE *fp2 = nullptr;
    fp1 = fopen(filename1,"rb");
    if(fp1 == nullptr)
    {
        throw BadSrcFile();
    }
    fp2 = fopen(filename2,"wb");
    if(fp2 == nullptr)
    {
        throw BadDestFile();
    }
    char buf[256];
    int readlen,writelen;
    readlen = fread(buf,1,256,fp1);
    while (!feof(fp1))
    {
        writelen = fwrite(buf,1,readlen,fp2);
        if(writelen != readlen)
        {
            throw BadCpyFile();
        }
    }
    fclose(fp1);
    fclose(fp2);
}
int main(void)
{
    try
    {
        filecopy02("01.txt","02.txt");
    }
    catch(int e)
    {
       cout<<"发生异常:"<<e<<endl;
    }
    catch(const char *e)
    {
        cout<<"发生异常:"<<e<<endl;
    }
    catch(BadSrcFile *e)
    {
        e->toString();
        cout<<"发生异常,打开源文件失败"<<endl;
    }
    catch(BadSrcFile &e)
    {
        e.toString();
         cout<<"发生异常,打开源文件失败"<<endl;
    }
    catch(BadDestFile e)
    {
        cout<<"发生异常,打开目的文件失败"<<endl;
    }
    catch(BadCpyFile e)
    {
        cout<<"拷贝文件时发生异常"<<endl;
    }
    catch(...)
    {
        cout<<"未知异常..."<<endl;
    }
    return 0;
}

C++编译器通过throw来产生对象,C++编译器在执行对应的catch分支,相当于一个函数应用,把实参传递给形参。【当然会通过解螺旋把栈清空】

不妨把catch看作一个模板函数,会进行严格的类型匹配


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

  • 异常是类(自己创建的异常类)
  • 异常派生
  • 异常中的数据:数据成员
  • 按引用传递异常
  • 在异常中使用虚函数
class eSize
{
public:
    eSize(int index)
    {
        this->index = index;
    }
    virtual void printErr()
    {
    } 
private:
    int index;
};
class eNegative:public eSize
{
public:
    eNegative(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index<0"<<endl;
    } 
}; 
class eZero:public eSize
{
public:
    eZero(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index=0"<<endl;
    } 
}; 
class eTooBig:public eSize
{
public:
    eTooBig(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index>10000"<<endl;
    } 
}; 
class eTooSmall:public eSize
{
public:
    eTooSmall(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index<10"<<endl;
    } 
};

标准程序库异常

       C++标准提供了一组标准异常类,这些类以基类Exception开始,标准程序库抛出的所有异常,都派生于该基类,这些类构成如下图的异常类的派生继承关系。该基类提供一个成员函数what()用于返回错误信息(返回类型为const char*)。在Exception类中,what()函数声明如下:

       virtual const char * what()const throw();

上面包含了各个具体异常类的含义以及它们的头文件。runtime_error和logic_error是一些具体的异常类的基类,它们分别表示两大类异常。logic_error表示那些可以在程序中被预先检测到的异常,也就是说如果小心得编写程序,这些类异常能够避免,而runtime_error则表示那些难以被预先检测的异常。

一些编程语言规定只能抛掷某个类的派生类(例如java中允许抛掷的类必须派生自Exception类),C++虽然没有这项强制的要求,但仍然可以选择这样实践。例如:在程序中可以使得所有抛出的异常皆派生自Exception(或者直接抛出标准程序库提供的异常类型,或者从标准程序库提供的异常类派生出新的类),这样会带来很多方便.(因为多态)

logic_error和runtime_error两个类及其派生类,都有一个接收const string &型参数的改造函数。在构造异常对象时需要将具体的错误信息传递给该函数,如果调用该对象的what函数,就可以得到构造时提供的错误信息。

#include <iostream>
#include <stdexcept>
using namespace std;
class Teacher
{
public:
    Teacher(int age)
    {
        if(age > 100)
        {
            throw out_of_range("年龄太大");
        }
        this->age = age;
    }
private:
    int age;
};
class Dog
{
public:
    Dog()
    {
        p = new int[1024*1024*100];  //4MB
    }
private:
    int *p;
};
int main(void)
{
    // try
    // {
    //     Teacher t1(30);
    // }
    // catch(exception &e)
    // {
    //     cout<<e.what()<<endl;
    // }
    try
    {
        Dog *pdog;
        for(int i=1;i<=10000000;i++)
        {
            pdog = new Dog();
        }
    }
    catch(exception& e)
    {
        std::cerr << e.what() << '\n';
    }
    return 0;
}
相关文章
|
7月前
|
C++
西安石油大学C++上机实验 上机五:模板和异常处理程序设计(2 学时)
西安石油大学C++上机实验 上机五:模板和异常处理程序设计(2 学时)
23 0
|
2月前
|
安全 编译器 程序员
【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用(一)
【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用
31 1
|
3天前
|
存储 C++
C++ 异常处理机制详解:轻松掌握异常处理技巧
C++ 异常处理提供结构化错误管理,增强程序健壮性。通过`throw`抛出异常,`try-catch`捕获并处理。示例展示了当年龄小于18时抛出异常。优点包括提高健壮性和代码可维护性,但可能降低性能并复杂化代码。另外,介绍了四种在C++中相加两个数的方法,包括使用运算符、函数、类、STL函数和lambda表达式。
11 0
|
8天前
|
安全 编译器 C++
C++从入门到精通:3.2异常处理——掌握C++的异常处理机制,提高程序健壮性
C++从入门到精通:3.2异常处理——掌握C++的异常处理机制,提高程序健壮性
|
8天前
|
程序员 编译器 C语言
【C++高阶(七)】C++异常处理的方式
【C++高阶(七)】C++异常处理的方式
|
24天前
|
C++
C++语言异常处理学习应用案例
C++异常处理保证程序在运行时遇到错误(如除数为0)时不崩溃。以下是一个示例:程序接收用户输入的两个整数并进行除法运算。若除数为0,则抛出`std::runtime_error`异常。`try-catch`结构用来捕获并处理异常,当出现异常时,输出错误信息,使程序能继续执行。
13 4
|
1月前
|
C++
C++异常处理try和throw以及catch的使用
C++异常处理try和throw以及catch的使用
|
2月前
|
C++
11. C++异常处理
11. C++异常处理
18 0
11. C++异常处理
|
2月前
|
监控 安全 Linux
Linux C++ 环境下的FTP远程升级实现及异常处理策略
Linux C++ 环境下的FTP远程升级实现及异常处理策略
66 0
|
2月前
|
自然语言处理 安全 程序员
【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用(二)
【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用
26 0