查看: 89|回复: 0

【C++10】C++异常处理

[复制链接]

6

主题

9

帖子

21

积分

新手上路

Rank: 1

积分
21
发表于 2022-11-28 14:05:38 | 显示全部楼层 |阅读模式
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)。在C语言的世界中,对错误的处理总是围绕着两种方法:

  • 一是使用整型的返回值标识错误;
  • 二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。当然C++中仍然是可以用这两种方法的。
        这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。  
        还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。
1、异常的基本语法


  • C++异常的处理关键字   try  throw  catch
  • 可能出现异常的代码 放到 try块
  • 利用throw抛出异常
  • 利用catch捕获异常
  • catch( 类型) 如果想捕获其他类型 catch(…)
  • 如果捕获到的异常不想处理,而继续向上抛出,利用 throw
int myDivision(int a, int b)
{
        if (b == 0)
        {
                //return -1;
                //throw 1; //抛出int类型的异常
                //throw 'a'; //抛出char类型的异常
                //throw 3.14; //抛出double类型的异常
                string str = "abc";
                throw str;
        }
        return a / b;
}

void test01()
{
        int a = 10;
        int b = 0;
        try
        {
                myDivision(a, b);
        }
        catch (int)
        {
                cout << "int类型异常捕获" << endl;
        }
        catch (char)
        {
                cout << "char类型异常捕获" << endl;
        }
        catch (double)
        {
                //异常必须有函数进行处理,如果没有任何处理,程序自动调用 terminate 函数,让程序中断
                throw;   //捕获到了异常,但是不想处理,继续向上抛出这个异常
                cout << "double类型异常捕获" << endl;
        }
        catch (MyException e)
        {
                e.printError();
        }
        catch (...)
        {
                cout << "其他类型异常捕获" << endl;
        }
}

int main() {
        try
        {
                test01();
        }
        catch (double)
        {
                cout << "double函数中 double类型异常捕获" << endl;
        }
        catch (...)
        {
                cout << "main函数中 其他类型异常捕获" << endl;
        }
        system("pause");
        return EXIT_SUCCESS;
}

  • 异常必须有函数进行处理,如果都不去处理,程序自动调用 terminate函数,中断掉
  • 异常可以是自定义数据类型
class MyException
{
public:
        void printError()
        {
                cout << "我自己的异常" << endl;
        }
};

int myDivision(int a, int b)
{
        if (b == 0)
        {
                throw MyException(); //抛出 MyException的匿名对象
        }
        return a / b;
}

void test01()
{
        int a = 10;
        int b = 0;
        try
        {
                myDivision(a, b);
        }
        catch (MyException e)
        {
                e.printError();
        }
}
2、栈解旋


  • 从try代码块开始,到throw抛出异常之前,所有栈上的数据都会被释放掉,
  • 释放的顺序和创建顺序相反的,这个过程我们称为栈解旋
class MyException
{
public:
        void printError()
        {
                cout << "我自己的异常" << endl;
        }
};

class Person
{
public:
        Person()
        {
                cout << "Person的默认构造函数调用" << endl;
        }
        ~Person()
        {
                cout << "Person的析构函数调用" << endl;
        }
};

int myDivision(int a, int b)
{
        if (b == 0)
        {
                //从try代码块开始,到throw抛出异常之前,所有栈上的数据都会被释放掉,
                //释放的顺序和创建顺序相反的,这个过程我们称为栈解旋
                Person p1;
                Person p2;
                throw MyException(); //抛出 MyException的匿名对象
        }
        return a / b;
}

void test01()
{
        int a = 10;
        int b = 0;
        try
        {
                myDivision(a, b);
        }
        catch (MyException e)
        {
                e.printError();
        }
}


3、异常的接口声明


  • 在函数中 如果限定抛出异常的类型,可以用异常的接口声明
  • 语法: void func()throw(int ,double)
  • throw(空)代表 不允许抛出异常
//异常接口申明  throw(空)代表不允许抛出异常

void func() throw(int, double)
{
    throw 3.14;
}
4、异常变量的生命周期


  • 抛出的是 throw MyException();  catch (MyException e) 调用拷贝构造函数 效率低
  • 抛出的是 throw MyException();  catch (MyException &e)  只调用默认构造函数 效率高 推荐
  • 抛出的是 throw &MyException(); catch (MyException *e) 对象会提前释放掉,不能在非法操作
  • 抛出的是 new MyException();   catch (MyException *e) 只调用默认构造函数 自己要管理释放,次推荐
class MyException
{
public:
        MyException()
        {
                cout << "MyException默认构造函数调用" << endl;
        }
        MyException(const MyException& e)
        {
                cout << "MyException拷贝构造函数调用" << endl;
        }
        ~MyException()
        {
                cout << "MyException析构函数调用" << endl;
        }
};

void doWork()
{
        throw  new MyException();
}

void test01()
{
        try
        {
                doWork();
        }
        //抛出的是 throw MyException();  catch (MyException e) 调用拷贝构造函数 效率低
        //抛出的是 throw MyException();  catch (MyException &e)  引用方式接收,只调用默认构造函数 效率高 推荐
        //抛出的是 throw &MyException(); catch (MyException *e) 对象方式接收,对象会提前释放掉,不能在非法操作
        //抛出的是 new MyException();   catch (MyException *e) 只调用默认构造函数 自己要管理释放
        catch (MyException* e)  //这里根据上面的可选不同方法,这里是最后一种
        {
                cout << "自定义异常捕获" << endl;
                delete e;
        }
}
5、异常的多态使用


  • 提供基类异常类

    • class BaseException
    • 纯虚函数  virtual void printError() = 0;

  • 子类空指针异常 和  越界异常 继承 BaseException
  • 重写virtual void printError()
  • 测试 利用父类引用指向子类对象
//异常的基类
class BaseException
{
public:
        virtual void printError() = 0;
};

//空指针异常
class NULLPointerException :public BaseException
{
public:
        virtual void printError()
        {
                cout << "空指针异常" << endl;
        }
};

//越界异常
class OutOfRangeException :public BaseException
{
public:
        virtual void printError()
        {
                cout << "越界异常" << endl;
        }
};

void doWork()
{
        //throw NULLPointerException();
        throw OutOfRangeException();
}

void test01()
{
        try
        {
                doWork();
        }
        catch (BaseException& e)
        {
                e.printError();
        }
}
6、系统标准异常

标准库中也提供了很多的异常类,它们是通过类继承组织起来的。异常类继承层级结构图如下:明细的需要查表。


异常描述
std::exception该异常是所有标准 C++ 异常的父类。
std::bad_alloc该异常可以通过 new 抛出。
std::bad_cast该异常可以通过 dynamic_cast 抛出。
std::bad_exception这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid该异常可以通过 typeid 抛出。
std::logic_error理论上可以通过读取代码来检测到的异常。
std::domain_error当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument当使用了无效的参数时,会抛出该异常。
std::length_error当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator。
std::runtime_error理论上不可以通过读取代码来检测到的异常。
std::overflow_error当发生数学上溢时,会抛出该异常。
std::range_error当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error当发生数学下溢时,会抛出该异常。


  • 引入头文件  #include
  • 抛出越界异常 throw out_of_range(“…”)
  • 获取错误信息  catch( exception & e )     e.what();
class Person
{
public:
        Person(int age)
        {
                if (age < 0 || age > 150)
                {
                        throw out_of_range("年龄必须在 0 ~ 150之间");
                        //throw length_error("年龄必须在 0 ~ 150之间");
                }
                else
                {
                        this->m_Age = age;
                }
        }
        int m_Age;
};

void test01()
{
        try
        {
                Person p(151);
        }
        //catch ( out_of_range &e)  //这个是具体的异常,配合上面throw out_of_range
        catch (exception& e)        //这个是具体的异常的基类,可以输出任何异常
        {
                cout << e.what() << endl;
        }
}
7、编写自己的异常类


  • 编写myOutofRange 继承 Exception类
  • 重写 virtual const char *  what() const
  • 将sting 转为 const char *         .c_str()
  • const char * 可以隐式类型转换为 string  反之不可以
  • 测试,利用多态打印出错误提示信息
class MyOutOfRangeException :public exception  // 这里继承了exception,下面的函数都是多态实现
{
public:

        MyOutOfRangeException(const char* str)
        {
                //const char * 可以隐式类型转换为 string  反之不可以
                this->m_errorInfo = str;
                cout << "const char* str 形式" << endl;
        }
        MyOutOfRangeException(string str)
        {
                this->m_errorInfo = str;
                cout << "string str 形式" << endl;
        }
        virtual const char* what() const
        {
                //将string 转为 const char *
                return m_errorInfo.c_str();
        }
        string m_errorInfo;
};

class Person
{
public:
        Person(int age)
        {
                if (age < 0 || age > 150)
                {
                        //throw MyOutOfRangeException(string("年龄必须在0到150之间")); //MyOutOfRangeException(string str)
                        throw MyOutOfRangeException("年龄必须在0到150之间"); //MyOutOfRangeException(const char* str)
                }
                else
                {
                        this->m_Age = age;
                }
        }

        int m_Age;
};

void test01()
{
        try
        {
                Person p(1000);
        }
        catch (exception& e)
        {
                cout << e.what() << endl;
        }
}


8、其他

个人觉得c++的异常的设计也有些问题,是历史遗留的,目前各个平台和编译器都支持的不完善。谷歌的编程规范里有详细的解释为什么不要用c++的异常。所以非必要时,尽量不要用异常(特指c++项目)
从C++17开始,不能再从函数写可能抛出某种异常。只能声明不能抛出某种异常noexcept,如果一个函数声明不会抛异常,结果抛出异常,C++运行时会调用std::terminate来终止应用程序。
个人感悟:学是为了掌握知识点,为了更好地不使用它。
相关链接


  • 【C++00】C++从入门到实践

若觉得有用,欢迎 点赞/收藏  ~~~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表