07-C++ 异常

异常

1. 概念

  • 异常事件(如:除 0 溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)

  • 在C 语言对错误的处理是两种方法:

    • 一是使用整型的 返回值标识错误
    • 二是使用 errno 宏(可以简单的理解为一个全局整型变量)去记录错误。
  • c++异常 不可忽略 (如果忽略,进程结束)。

    • 异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
    • 抛出异常 ----> 捕获异常

示例:

c 复制代码
int main(int argc, char *argv[])
{ 
    int num = 10 / 0;
    cout << "OVER" << endl;
    return 0;
} 
//不会显示OVER,程序异常结束

2. 抛出异常

语法:

throw 值或变量;  

例如:

c 复制代码
throw 0;

throw 1.1;

throw 'a';

throw "abc";

3. 捕获异常

语法:

try{
    可能会产生异常的代码
    111
    222 出现异常
    333
} 
catch(数据类型1 变量名)
{
	当throw的值与数据类型1相同进入此处
} 
catch(数据类型2 变量名)
{
	当throw的值与数据类型2相同进入此处
} 
...
catch(...)
{
	当throw的值以上数据类型都不相同进入此处
}

4. 示例

c 复制代码
#include <iostream>

using namespace std;

class A{

};
void my_error(int a, int b)
{
    if(b == 0)
    {
        //抛出异常
        //throw 0;
        //throw 1.2;
        //throw 'a';
        //throw "abc";
        throw new A();
    }
    cout << a / b << endl;
}
void fun01()
{
    try
    {
        my_error(10, 0);
    }
    catch(int e)
    {
        cout << "int抛出的异常值为:" << e << endl;
    }
    catch(double e)
    {
        cout << "double抛出的异常值为:" << e << endl;
    }
    catch(char e)
    {
        cout << "char抛出的异常值为:" << e << endl;
    }
    catch(...)
    {
        cout << "除以上类型外所有异常" << endl;
    }
}

int main(int argc, char *argv[])
{
    fun01();
    cout << "Hello World!" << endl;
    return 0;
}
//除以上类型外所有异常
//Hello World!

5. 栈解旋

概念:

  • 异常被抛出后,从进入 try 块起,到异常被抛掷前,这期间 在栈上构造的所有对象,都会被自动析构。

  • 析构的顺序与构造的顺序相反,这一过程称为栈的解旋 。

问题:在创建对象的过程中,抛出异常,此时 还 没来的及释放对象,所以会出现错误。

解决办法:用try...catch 捕获异常,就会自动释放内存空间

示例1:没有释放没存

c 复制代码
#include <iostream>

using namespace std;
class B{
private:
    int x;
public:
    B(int x){
        this->x = x;
        cout << "B." << x << "被创建了" << endl;
    }
    ~B()
    {
        cout << "B." << x << "被销毁了" << endl;
    }
};
void fun02()
{
    B b1(1);
    B b2(2);
    B b3(3);
    //抛出异常
    throw 0;
}

int main(int argc, char *argv[])
{
    fun02();
    cout << "Hello World!" << endl;
    return 0;
}

示例2:使用try...catch后,自动释放内存

c 复制代码
#include <iostream>

using namespace std;
class B{
private:
    int x;
public:
    B(int x){
        this->x = x;
        cout << "B." << x << "被创建了" << endl;
    }
    ~B()
    {
        cout << "B." << x << "被销毁了" << endl;
    }

};
void fun02()
{
    B b1(1);
    B b2(2);
    B b3(3);
    //抛出异常
    throw 0;
}

int main(int argc, char *argv[])
{
    try
    {
        fun02();
    }
    catch(int e)
    {
        
    }

    cout << "Hello World!" << endl;
    return 0;
}

6. 异常的接口声明

作用:限定异常抛出的类型

语法:

返回值类型 函数名(形参列表)throw(数据类型1,数据类型2,...)
{
	函数体
} 

注意:

  • 声明异常后,当前函数中只能抛出指定类型的异常
  • throw() 括号中啥也不写,表示不允许抛出任何异常

示例:

c 复制代码
#include <iostream>

using namespace std;

class A{

};

void my_error(int a, int b)
{
    if(b == 0)
    {
        //抛出异常
        //throw 0;
        //throw 1.2;
        //throw 'a';
        //throw "abc";
        throw new A();
    }
    cout << a / b << endl;
}
void fun01()
{
    try
    {
        my_error(10, 0);
    }
    catch(int e)
    {
        cout << "int抛出的异常值为:" << e << endl;
    }
    catch(double e)
    {
        cout << "double抛出的异常值为:" << e << endl;
    }
    catch(char e)
    {
        cout << "char抛出的异常值为:" << e << endl;
    }
    catch(...)
    {
        cout << "除以上类型外所有异常" << endl;
    }
}

class B{
private:
    int x;
public:
    B(int x){
        this->x = x;
        cout << "B." << x << "被创建了" << endl;
    }
    ~B()
    {
        cout << "B." << x << "被销毁了" << endl;
    }

};
void fun02()
{
    B b1(1);
    B b2(2);
    B b3(3);
    //抛出异常
    throw 0;
}
//当前函数只能抛出int或char类型的异常
//void fun03() throw(int,char)
//throw() 说明当前函数不会抛出任何异常
void fun03() throw()
{
    throw 0;
}
int main(int argc, char *argv[])
{
//    try
//    {
//        fun03();
//    }
//    catch(int e)
//    {
//    }
    fun03();
    cout << "Hello World!" << endl;
    return 0;
}
// 此时会报错
//terminate called after throwing an instance of 'int'
//抛出'int'实例后调用终止

7. 异常对象的生命周期

  • 抛出异常对象
    • 多次 调用对象的构造和析构
  • 抛出异常对象指针
    • 只调用 构造函数,没有析构
  • 抛出异常对象引用 (推荐使用)
    • 只会调用一次构造,一次析构
    • 注意:隐式创建对象,不然会触发拷贝构造

7.1 示例1:抛出异常对象

c 复制代码
#include <iostream>

using namespace std;
class MyError
{
public:
    MyError(){
        cout << "构造函数" << endl;
    }
    MyError(const MyError& e){
        cout << "拷贝构造" << endl;
    }
    ~MyError(){
        cout << "析构函数" << endl;
    }
};

void test01()throw(MyError)
{
    throw MyError(); //调用构造 
}
void fun01()
{
    try{
        test01();
    }
    catch(MyError e) //调用拷贝构造
    {

    }
}

int main(int argc, char *argv[])
{
    fun01();
    return 0;
}
//构造函数
//拷贝构造
//析构函数
//析构函数

注意:显示创建对象 会调用 构造和拷贝构造

7.2 示例2:抛出异常对象指针

c 复制代码
#include <iostream>

using namespace std;
class MyError
{
public:
    MyError(){
        cout << "构造函数" << endl;
    }
    MyError(const MyError& e){
        cout << "拷贝构造" << endl;
    }
    ~MyError(){
        cout << "析构函数" << endl;
    }
};

void test02()
{
    //new 返回的是error对象的指针
    throw new MyError();
}

void fun02()
{
    try{
        test02();
    }
    catch(MyError *e)
    {

    }
}

int main(int argc, char *argv[])
{
    fun02();
    return 0;
}
//构造函数

7.3 示例3:抛出异常对象引用 (推荐使用)

c 复制代码
#include <iostream>

using namespace std;
class MyError
{
public:
    MyError(){
        cout << "构造函数" << endl;
    }
    MyError(const MyError& e){
        cout << "拷贝构造" << endl;
    }
    ~MyError(){
        cout << "析构函数" << endl;
    }
};

void test03()
{
    throw MyError();
}

void fun03()
{
    try{
        test03();
    }
    catch(MyError& e)
    {

    }
}
int main(int argc, char *argv[])
{
    fun03();
    return 0;
}
//构造函数
//析构函数

8. 异常的多态

概念:子类异常对象 可以被 父类异常类型捕获 ,原理是上行,子传父,多态。

示例1:

c 复制代码
#include <iostream>

using namespace std;

class BaseException{};
class MyException01:public BaseException{};
class MyException02:public BaseException{};
void test05()
{
    try{
        throw MyException01();
    }
    catch(BaseException)
    {
        cout << "可以捕获子类异常" << endl;
    }
}
int main(int argc, char *argv[])
{
    test05();
    return 0;
}
//可以捕获子类异常

示例2:重写父类虚函数

c 复制代码
#include <iostream>

using namespace std;
class BaseException{
public:
    virtual void printMsg(){}
};
class NullException:public BaseException{
public:
    void printMsg(){
        cout << "空指针异常" << endl;
    }
};
class ArrOutException:public BaseException{
public:
    void printMsg(){
        cout << "数组下标越界异常" << endl;
    }
};
void test05()
{
    try{
        throw NullException();
    }
    catch(BaseException &e)
    {
        e.printMsg();
    }
}
int main(int argc, char *argv[])
{
    test05();
    return 0;
}
//空指针异常

9. 标准异常库

9.1 简介

标准库中也提供了很多的 异常类,它们是通过类 继承组织起来 的。

异常类继承层级结构图所示 :

标准异常类的成员:

① 在上述继承体系中,每个类都有提供了构造函数、复制构造函数、和赋值操作符重载。

② logicerror 类及其子类、 runtimeerror 类及其子类,它们的构造函数是接受一个string 类型的形式参数,用于异常信息的描述

③ 所有的异常类都有一个 what()方法,返回 const char* 类型(C 风格字符串)的值,描述异常信息。

标准异常类的具体描述:

异常名称 描述
exception 所有标准异常类的父类
bad_alloc 当 operator new and operator new[],请求分配内存失败时
bad_exception 这是个特殊的异常,如果函数的异常抛出列表里声明了 badexception 异常,当函数内部抛出了异常抛出列表中没有的异 常,这是调用的 unexpected 函数中若抛出异常,不论什么类 型,都会被替换为 badexception 类型
bad_typeid 使用 typeid 操作符,操作一个 NULL 指针,而该指针是带有虚函数的类,这时抛出 bad_typeid 异常
bad_cast 使用 dynamic_cast 转换引用失败的时候
ios_base::failure io 操作过程出现错误
logic_error 逻辑错误,可以在运行前检测的错误
runtime_error 运行时错误,仅在运行时才可以检测的错误

logic_error 的子类:

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

runtime_error 的子类:

异常名称 描述
range_error 计算结果超出了有意义的值域范围
overflow_error 算术计算上溢
underflow_error 算术计算下溢
invalid_argument 参数不合适。在标准库中,当利用 string 对象构造 bitset 时, 而 string 中的字符不是'0'或'1'的时候,抛出该异常

9.2 标准异常使用

示例:

c 复制代码
#include <iostream>

using namespace std;
void test02()
{
    //throw runtime_error("使用系统提供的运行时异常类");
     throw logic_error("逻辑错误");
}

void fun02()
{
    try{
        test02();
    }
//    catch(runtime_error &e)
//    {
//        const char * msg = e.what();
//        cout << msg << endl;
//    }
    catch(exception &e)
    {
        const char * msg = e.what();
        cout << msg << endl;
    }
}

int main(int argc, char *argv[])
{
    fun02();
    cout << "Hello World!" << endl;
    return 0;
}
//逻辑错误

10. 自定义异常

步骤:

  1. 定义一个类
  2. 继承与异常类
  3. 重写wait方法
方式1: 继承总异常类 exception 需要冲写what() 函数
方式2: 继承总异常类Exception下的某一个 异常类,此处是 runtime_error

示例:

c 复制代码
#include <iostream>
using namespace std;
//方式1: 继承总异常类 exception 需要冲写what() 函数
class my_error:public exception{
private:
    //记录异常信息
    char *msg;
public:
    my_error(char *msg){
        this->msg = msg;
    }
    //重写父类 what() 函数
    const char* what() const _GLIBCXX_USE_NOEXCEPT
    {
        return msg;
    }
};

//方式2: 继承总异常类Exception下的某一个 异常类,此处是 runtime_error
class my_error02:public runtime_error{
public:
    my_error02(char *msg):runtime_error(msg){}
};
//调用方式1
void test()
{
    throw my_error("自定义异常");
}
//调用方式2
void test02()
{
    throw my_error02("自定义异常2");
}
void fun01()
{
    try{
        test02();
    }
    catch(exception &e)
    {
        cout << e.what() << endl;
    }
}

int main(int argc, char *argv[])
{
    fun01();
    return 0;
}
//自定义异常2

11. 练习总结

11.1 示例1

c 复制代码
#include <iostream>
#include <cstdio>
using namespace std;

void test03(int a,int b)
{
    if(b == 0)
    {
        throw "除数不能为0";
    }
    cout << a / b << endl;
}

void test04()
{
    FILE* f = fopen("xxx","r");
    if(f == NULL)
    {
        throw "文件路径不正确";
    }
}

void fun03()
{
    try{
        test03(10,0);
    }
    catch(char const* e)
    {
        cout << e << endl;
    }
}
void fun04()
{
    try{
        test04();
    }
    catch(char const* e)
    {
        cout << e << endl;
    }
}
int main(int argc, char *argv[])
{
    fun03();
    fun04();
    return 0;
}
//除数不能为0
//文件路径不正确

11.2 示例2:ArrayList

arraylisat.hpp

c 复制代码
#include <cstring>
template<class X>
class ArrayList{
    X *data;
    int size;
    int count;
public:
    ArrayList();
    ~ArrayList();
    void add(X& x);
    X& get(int index);
    int getSize();
};


template<class X>
ArrayList<X>::ArrayList()
{
    data = new X[2];
    count = 2;
    size = 0;
}

template<class X>
ArrayList<X>::~ArrayList()
{
    delete [] data;
}

template<class X>
void ArrayList<X>::add(X& x)
{
    if(size == count)
    {
        count *= 2;
        X* newData = new X[count];
        memcpy(newData,data,size*sizeof(X));
        delete [] data;
        data = newData;
    }
    data[size] = x;
    size++;
}

template<class X>
X& ArrayList<X>::get(int index)
{
    //判断传入的参数是否合规,应该 >0,<size;
    //否则抛出异常 0
    if(index < 0 || index >= size)
    {
        throw 0;
    }
    return data[index];
}

template<class X>
int ArrayList<X>::getSize()
{
    return size;
}

main.cpp

c 复制代码
#include <iostream>
#include "arraylist.hpp"

using namespace std;

class A{
    int num;
public:
    A(){}
    A(int num):num(num){}
    void print()
    {
        cout << num << endl;
    }
};
int main(int argc, char *argv[])
{

    ArrayList<A> list;
    A d01 = 1;
    A d02 = 2;
    A d03 = 3;
    A d04 = 4;
    A d05 = 5;
    list.add(d01);
    list.add(d02);
    list.add(d03);
    list.add(d04);
    list.add(d05);
    //捕获异常,此时ArrayList中只有5个数据,参数10>size
    try{
        A& a = list.get(10);
        a.print();
    }
    catch(int e)
    {
        cout << "有bug" << endl;
    }

    return 0;
}
//有bug

11.3 示例3:读取文件

① myutils.h

c 复制代码
#ifndef MYFILE_UTILS_H
#define MYFILE_UTILS_H

//声明读取文件函数
extern char *get_file_text(char *filepath);
#endif // MYFILE_UTILS_H

② filepath_error.h

c 复制代码
#ifndef FILEPATH_ERROR_H
#define FILEPATH_ERROR_H
#include <iostream>
//定义路径错误函数,继承runtime_error
#include <iostream>
using namespace std;
class filepath_error:public runtime_error
{
public:
    filepath_error();
};
#endif // FILEPATH_ERROR_H

③ filepath_error.cpp

c 复制代码
#include "filepath_error.h"

filepath_error::filepath_error():runtime_error("文件路径有误")
{

}

④ myutils.cpp

c 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include "myfileutils.h"
#include "filepath_error.h"

//根据文件名读取文件中的内容
char *get_file_text(char *filepath)
{
    FILE *f = fopen(filepath, "r");
    if(f == NULL)
    {
        //throw 0;
        throw filepath_error();
    }

    //计算文本长度
    fseek(f, 0, 2);
    int len = ftell(f);
    //创建存放读取的文件的数组
    char *c = new char[len];
    //将数组数据置零
    memset(c, 0, len);
    //游标恢复置开始
    fseek(f, 0, 0);
    //读取数据
    fread(c, len, 1, f);
    fclose(f);
    return c;

}
//甲乙丙丁戊己庚辛壬癸
//子丑寅卯陈思武威申酉戌亥

⑤ main.cpp

c 复制代码
#include <iostream>
#include "myfileutils.h"
#include "filepath_error.h"

using namespace std;

int main(int argc, char *argv[])
{
    try
    {
        //char *content = get_file_text("D:/io");
        char *content = get_file_text("D:/a.txt");
        cout << content << endl;
    }
    catch(exception &e)
    {
        cout << e.what() << endl; //文件路径有误
    }

    return 0;
}
相关推荐
qq_172805594 分钟前
RUST学习教程-安装教程
开发语言·学习·rust·安装
wjs202411 分钟前
MongoDB 更新集合名
开发语言
monkey_meng15 分钟前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
一只小小汤圆19 分钟前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz40 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE1 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
tangliang_cn1 小时前
java入门 自定义springboot starter
java·开发语言·spring boot
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
新知图书1 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
威威猫的栗子1 小时前
Python Turtle召唤童年:喜羊羊与灰太狼之懒羊羊绘画
开发语言·python