ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++ 异常机制详解

2021-10-22 15:33:28  阅读:170  来源: 互联网

标签:exception int 抛出 C++ 详解 catch 机制 异常 throw


一、异常处理入门

程序的错误大致分为三种:

  1. 语法错误,在编译和链接阶段就能发现;
  2. 逻辑错误,可以通过调试解决;
  3. 运行时错误,异常机制是为解决此类错误引入。

一个运行时错误的例子

int main(){
    string str = "asdfa";
    char ch1 = str[10];     // 越界
    cout << ch1 << endl;    // 程序崩溃
    char ch2 = str.at(100);     // 越界,抛出异常
    cout << ch2 << endl;
    return 0;
}

修改代码,使用异常机制捕获异常:

int main(){
    string str = "asdfa";
    try{
        char ch1 = str[10];     // 越界
        cout << ch1 << endl;    // 程序崩溃
    }catch(exception &e){       // 不会捕获,因为[]不会检查下标,不会抛出异常
        cout << "[1]out of bound!" << endl;
    }

    try{
        char ch2 = str.at(100);     // 越界,抛出异常
        cout << ch2 << endl;      
    }catch(exception &e){
        cout << "[2]out of bound!" << endl;
    }

    return 0;
}
运行结果:
[2]out of bound!

try-catch 语法:

try{
// 可能抛出异常的语句
} catch(exceptionType variable){
// 处理异常的语句
}

发生异常时必须将异常明确地抛出,try 才能检测到。

当异常点跳转到 catch 所在位置时,位于异常点之后,且在当前 try 语句块内的语句都不会再执行,即使 catch 成功处理了错误。

异常可以发生在当前 try 块中,也可以发生在 try 块中调用的某个函数,或者所调用函数调用的另外一个函数中。发生异常后,程序的执行流会沿着函数的调用链往前回退,直到遇到 try 才停止,调用链中剩下的未被执行代码都会跳过,没有执行机会。

二、异常类型及多级 catch 匹配

C++ 语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的异常。也就是说,抛出异常时,会创建一个 exception 类或其子类的对象。

可以将 catch 看做一个没有返回值的函数,当异常发生后 catch 会被调用,并且会接收实参(异常数据)。

但 catch 和真正的函数调用相比又有区别,多了一个「在运行阶段将实参和形参匹配」的过程。

如果不希望 catch 处理异常数据,也可以将 variable 省略掉,也即写作:

try{
// 可能抛出异常的语句
}catch(exceptionType){
// 处理异常的语句
}

多级 catch

try{
    //可能抛出异常的语句
}catch (exception_type_1 e){
    //处理异常的语句
}catch (exception_type_2 e){
    //处理异常的语句
}
//其他的 catch
catch (exception_type_n e){
    //处理异常的语句
}

异常发生时,程序会按照从上到下的顺序,将异常类型和 catch 所能接受的类型逐个匹配,一旦找到类型匹配的 catch 就停止,如果没有找到,会交给系统处理,终止程序。

class Base{};
class Derived : public Base{};

int main(){
    try{
        throw Derived();    // 抛出异常,实际上是创建一个 Derived 类型的匿名对象
        cout << "此语句不会再执行" << endl;
    }catch(int){
         cout<<"Exception type: int"<<endl;
    }catch(Base){   // 匹配成功,向上转型
         cout<<"Exception type: Base"<<endl;
    }catch(Derived){
         cout<<"Exception type: Derived"<<endl;
    }
    return 0;
}

catch 匹配过程中的类型转换

普通函数(非函数模板)实参和形参类型转换:

  • 算数转换:例如 int 转换为 float,char 转换为 int,double 转换为 int 等。
  • 向上转型:也就是派生类向基类的转换,请猛击《C++向上转型(将派生类赋值给基类)》了解详情。
  • const 转换:也即将非 const 类型转换为 const 类型,例如将 char * 转换为 const char *。
  • 数组或函数指针转换:如果函数形参不是引用类型,那么数组名会转换为数组指针,函数名也会转换为函数指针。
  • 用户自定的类型转换。

catch 异常匹配中的转换只包括:1)「向上转型」 2)「const 转换」 3)「数组或函数指针转换」。其他的都不能应用于 catch。

三、throw(抛出异常)

异常处理流程:

抛出(Throw)--> 检测(Try) --> 捕获(Catch)

通过 throw 关键字来显式抛出异常,语法为:

throw exceptionData;

exceptionData 是“异常数据”,可以是基本类型,也可以是聚合类型。

string str = "fasdf";
string *pstr = str;

class Base{};
Base obj;

throw 100;
throw str;
throw pstr;
throw obj;

动态数组例子

// 自定义异常类
class OutOfRange{
public:
    OutOfRange():m_flag(1){};
    OutOfRange(int len, int index):m_len(len), m_index(index), m_flag(2){}
    void what() const; // 获取具体错误信息
private:
    int m_flag;     // 错误类型标识
    int m_len;      // 当前数组长度
    int m_index;    // 当前使用数组下标
};

void OutOfRange::what() const {
    if(m_flag == 1){
        cout<<"Error: empty array, no elements to pop."<<endl;
    }else if(m_flag == 2){
        cout<<"Error: out of range( array length "<<m_len<<", access index "<<m_index<<" )"<<endl;
    }else{
        cout<<"Unknown exception."<<endl;
    }
}

// 动态数组
class Array{
public:
    Array();
    ~Array(){free(m_p);}
    int operator[](int i) const;    //重载[]
    int push(int ele);              // 末尾插入元素
    int pop();                      // 末尾删除元素
    int length() const { return m_len; }    // 获取数组长度
private:
    int m_len;          // 数组长度
    int m_capacity;     // 当前内存还能容纳元素个数
    int *m_p;           // 内存指针
    static const int m_stepSize = 50;   // 每次扩容步长
};

Array::Array(){
    m_p = (int*)malloc( sizeof(int) * m_stepSize );
    m_capacity = m_stepSize;
    m_len = 0;
}

int Array::operator[](int index) const {
    if( index<0 || index>=m_len )        //判断是否越界
        throw OutOfRange(m_len, index);  //抛出异常(创建一个匿名对象)
    return *(m_p + index);
}

int Array::push(int ele){
    if(m_len >= m_capacity){ //如果容量不足就扩容
        m_capacity += m_stepSize;
        m_p = (int*)realloc( m_p, sizeof(int) * m_capacity ); //扩容
    }
    *(m_p + m_len) = ele;
    m_len++;
    return m_len-1;
}

int Array::pop(){
    if(m_len == 0)
        throw OutOfRange(); //抛出异常(创建一个匿名对象)
    m_len--;
    return *(m_p + m_len);
}

void printArray(Array &arr){
    int len = arr.length();
    if(len == 0){       //判断数组是否为空
        cout<<"Empty array! No elements to print."<<endl;
        return;
    }
    for(int i=0; i<len; i++){
        if(i == len-1)
            cout<<arr[i]<<endl;
        else
            cout<<arr[i]<<", ";
    }
}

int main(){
    Array nums;
    for(int i=0; i<10; i++)        // 向数组中添加十个元素
        nums.push(i);
    printArray(nums);
    
    try{        //尝试访问第 20 个元素
        cout<<nums[20]<<endl;
    }catch(OutOfRange &e){
        e.what();       // Error: out of range( array length 10, access index 20 )
    }
    
    try{        // 尝试弹出 20 个元素
        for(int i=0; i<20; i++)
            nums.pop();
    }catch(OutOfRange &e){
        e.what();       // Error: empty array, no elements to pop.
    }
    
    printArray(nums);   // Empty array! No elements to print.
    return 0;
}

throw 用作异常规范(C++11 后弃用)

throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范(Exception specification),也称为异常指示符或异常列表。

double func(char param) throw (int);

如果函数会抛出多种类型的异常,那么可以用逗号隔开:

double func (char param) throw (int, char, exception);

如果函数不会抛出任何异常,那么( )中什么也不写,这样函数不能抛出任何异常,即使抛出 try 也检测不到:

double func (char param) throw ();

1. 虚函数中的异常规范

派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格,或者更严格。

class Base{
public:
    virtual int fun1(int) throw();
    virtual int fun2(int) throw(int);
    virtual string fun3() throw(int, string);
};

class Derived:public Base{
public:
    int fun1(int) throw(int);   //错!异常规范不如 throw() 严格
    int fun2(int) throw(int);   //对!有相同的异常规范
    string fun3() throw(string); //对!异常规范比 throw(int,string) 更严格
}

2. 异常规范与函数定义和函数声明

异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致,不能更加严格或者更加宽松。

四、C++异常的基类 exception

C++语言本身或者标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception)。

try{
    // ...
}catch(exception &e){   // 使用引用是为了提高效率,不使用引用会执行一次对象拷贝
    // ...
}
class exception{
public:
    exception () throw(); //构造函数
    exception (const exception&) throw(); //拷贝构造函数
    exception& operator= (const exception&) throw();    //运算符重载
    virtual ~exception() throw(); //虚析构函数
    virtual const char* what() const throw(); //虚函数
};

下图展示了 exception 类的继承层次:

exception 类的直接派生类:

异常名称 说 明
logic_error 逻辑错误。
runtime_error 运行时错误。
bad_alloc 使用 new 或 new[ ] 分配内存失败时抛出的异常。
bad_typeid 使用 typeid 操作一个 NULL 指针,而且该指针是带有虚函数的类,这时抛出 bad_typeid 异常。
bad_cast 使用 dynamic_cast 转换失败时抛出的异常。
ios_base::failure io 过程中出现的异常。
bad_exception 这是个特殊的异常,如果函数的异常列表里声明了 bad_exception 异常,当函数内部抛出了异常列表中没有的异常时,如果调用的 unexpected() 函数中抛出了异常,不论什么类型,都会被替换为 bad_exception 类型。

logic_error 的派生类:

异常名称 说明
length_error 试图生成一个超出该类型最大长度的对象时抛出该异常,例如 vector 的 resize 操作。
domain_error 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数。
out_of_range 超出有效范围。
invalid_argument 参数不合适。在标准库中,当利用 string 对象构造 bitset 时,而 string 中的字符不是 0 或 1 的时候,抛出该异常。

runtime_error 的派生类:

异常名称 说 明
range_error 计算结果超出了有意义的值域范围。
overflow_error 算术计算上溢。
underflow_error 算术计算下溢。

标签:exception,int,抛出,C++,详解,catch,机制,异常,throw
来源: https://www.cnblogs.com/cscshi/p/15439099.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有