异常基本概念
Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。也就是《C++ primer》中说的:将问题检测和问题处理相分离。
一种思想:在所有支持异常处理的编程语言中(例如java),要认识到的一个思想:在异常处理过程中,由问题检测代码可以抛出一个对象给问题处理代码,通过这个对象的类型和内容,实际上完成了两个部分的通信,通信的内容是“出现了什么错误”。当然,各种语言对异常的具体实现有着或多或少的区别,但是这个通信的思想是不变的。
| 一句话:异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)。 |
回顾一下:我们以前编写程序是如何处理异常?
在C语言的世界中,对错误的处理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。当然C++中仍然是可以用这两种方法的。
- 这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。
- 还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。
c++异常机制相比C语言异常处理的优势?
函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。
整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。
整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。
什么是跳级:

| |
| |
| |
| int A_MyDivide(int a,int b){ |
| if (b == 0){ |
| return -1; |
| } |
| |
| return a / b; |
| } |
| |
| |
| int B_MyDivide(int a,int b){ |
| |
| int ba = a + 100; |
| int bb = b; |
| |
| int ret = A_MyDivide(ba, bb); |
| |
| return ret; |
| } |
| |
| |
| int C_MyDivide(){ |
| |
| int a = 10; |
| int b = 0; |
| |
| int ret = B_MyDivide(a, b); |
| if (ret == -1){ |
| return -1; |
| } |
| else{ |
| return ret; |
| } |
| } |
| |
| |
| |
| |
异常语法
异常基本语法
| int A_MyDivide(int a, int b){ |
| if (b == 0){ |
| throw 0; |
| } |
| |
| return a / b; |
| } |
| |
| |
| int B_MyDivide(int a, int b){ |
| |
| int ba = a; |
| int bb = b; |
| |
| int ret = A_MyDivide(ba, bb) + 100; |
| |
| return ret; |
| } |
| |
| |
| int C_MyDivide(){ |
| |
| int a = 10; |
| int b = 0; |
| |
| int ret = 0; |
| |
| |
| #if 1 |
| ret = B_MyDivide(a, b); |
| |
| |
| #else |
| try{ |
| ret = B_MyDivide(a, b); |
| } |
| catch (int e){ |
| cout << "C_MyDivide Call B_MyDivide 除数为:" << e << endl; |
| } |
| #endif |
| |
| return ret; |
| } |
| |
| int main(){ |
| |
| C_MyDivide(); |
| |
| system("pause"); |
| return EXIT_SUCCESS; |
| } |
总结:
- 若有异常则通过throw操作创建一个异常对象并抛出。
- 将可能抛出异常的程序段放到try块之中。
- 如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。
- catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常)
- 如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。
- 处理不了的异常,可以在catch的最后一个分支,使用throw,向上抛。
c++异常处理使得异常的引发和异常的处理不必在一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。
| try试图执行try中的内容,在可能出现异常的地方,抛出异常,try下面 catch捕获异常 |
| catch{捕获的类型} ...代表所有的其他类型 |
| 如果不想处理异常,继续向上抛出,throw |
| 如果没有任何处理异常的地方,那么程序调用terminate函数,使程序中断 |
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<iostream> |
| #include<string> |
| |
| |
| using namespace std; |
| |
| int myDevide(int a, int b) |
| { |
| if (b == 0) |
| { |
| |
| throw 3.14; |
| } |
| return a / b; |
| } |
| |
| |
| void test01() |
| { |
| int a = 10; |
| int b = 0; |
| |
| |
| |
| |
| |
| try |
| { |
| |
| myDevide(a, b); |
| } |
| catch (int) |
| { |
| cout << "int类型异常捕获" << endl; |
| } |
| catch (double) |
| { |
| |
| throw; |
| cout << "double类型异常捕获" << endl; |
| } |
| catch (...) |
| { |
| cout << "其他类型异常捕获" << endl; |
| } |
| } |
| |
| int main() |
| { |
| try |
| { |
| test01(); |
| } |
| |
| catch (char) |
| { |
| cout << "main中的double类型异常捕获" << endl; |
| } |
| catch (...) |
| { |
| cout << "main函数中,其他类型异常捕获" << endl; |
| } |
| |
| |
| system("pause"); |
| return 0; |
| } |
| |
| |
对自定义异常进行捕获
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<iostream> |
| #include<string> |
| |
| using namespace std; |
| |
| class myException |
| { |
| public: |
| void printError() |
| { |
| cout << "自定义的异常" << endl; |
| } |
| }; |
| |
| int myDevide(int a, int b) |
| { |
| if (b == 0) |
| { |
| |
| |
| throw myException(); |
| } |
| return a / b; |
| } |
| |
| |
| void test01() |
| { |
| int a = 10; |
| int b = 0; |
| |
| |
| |
| |
| |
| try |
| { |
| |
| myDevide(a, b); |
| } |
| catch (int) |
| { |
| cout << "int类型异常捕获" << endl; |
| } |
| catch (double) |
| { |
| |
| throw; |
| cout << "double类型异常捕获" << endl; |
| } |
| catch (myException e) |
| { |
| e.printError(); |
| } |
| catch (...) |
| { |
| cout << "其他类型异常捕获" << endl; |
| } |
| } |
| |
| int main() |
| { |
| try |
| { |
| test01(); |
| } |
| |
| catch (char) |
| { |
| cout << "main中的double类型异常捕获" << endl; |
| } |
| catch (...) |
| { |
| cout << "main函数中,其他类型异常捕获" << endl; |
| } |
| |
| |
| system("pause"); |
| return 0; |
| } |
| |
栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
| class Person{ |
| public: |
| Person(string name){ |
| mName = name; |
| cout << mName << "对象被创建!" << endl; |
| } |
| ~Person(){ |
| cout << mName << "对象被析构!" << endl; |
| } |
| public: |
| string mName; |
| }; |
| |
| void TestFunction(){ |
| |
| Person p1("aaa"); |
| Person p2("bbb"); |
| Person p3("ccc"); |
| |
| |
| throw 10; |
| } |
| |
| int main(){ |
| |
| try{ |
| TestFunction(); |
| } |
| catch (...){ |
| cout << "异常被捕获!" << endl; |
| } |
| |
| system("pause"); |
| return EXIT_SUCCESS; |
| } |
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<iostream> |
| #include<string> |
| |
| using namespace std; |
| |
| |
| class Person |
| { |
| public: |
| Person() |
| { |
| cout << "Person构造" << endl; |
| } |
| |
| ~Person() |
| { |
| cout << "Person析构" << endl; |
| } |
| }; |
| |
| |
| class myException |
| { |
| public: |
| void printError() |
| { |
| cout << "自定义的异常" << endl; |
| } |
| }; |
| |
| int myDevide(int a, int b) |
| { |
| if (b == 0) |
| { |
| |
| |
| |
| |
| |
| |
| |
| Person p1; |
| Person p2; |
| |
| throw myException(); |
| } |
| return a / b; |
| } |
| |
| |
| |
| void test01() |
| { |
| int a = 10; |
| int b = 0; |
| |
| |
| |
| |
| |
| try |
| { |
| |
| myDevide(a, b); |
| } |
| catch (int) |
| { |
| cout << "int类型异常捕获" << endl; |
| } |
| catch (double) |
| { |
| |
| throw; |
| cout << "double类型异常捕获" << endl; |
| } |
| catch (myException e) |
| { |
| e.printError(); |
| } |
| catch (...) |
| { |
| cout << "其他类型异常捕获" << endl; |
| } |
| } |
| |
| int main() |
| { |
| try |
| { |
| test01(); |
| } |
| |
| catch (char) |
| { |
| cout << "main中的double类型异常捕获" << endl; |
| } |
| catch (...) |
| { |
| cout << "main函数中,其他类型异常捕获" << endl; |
| } |
| |
| |
| system("pause"); |
| return 0; |
| } |

异常接口声明
- 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类型的异常。
- 如果在函数声明中没有包含异常接口声明,则此函数可以抛任何类型的异常,例如:void func()
- 一个不抛任何类型异常的函数可声明为:void func() throw()
- 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
| |
| void TestFunction01(){ |
| throw 10; |
| } |
| |
| |
| void TestFunction02() throw(int,char,char*){ |
| string exception = "error!"; |
| throw exception; |
| } |
| |
| |
| void TestFunction03() throw(){ |
| throw 10; |
| } |
| |
| int main(){ |
| |
| try{ |
| |
| |
| |
| } |
| catch (...){ |
| cout << "捕获异常!" << endl; |
| } |
| |
| system("pause"); |
| return EXIT_SUCCESS; |
| } |
请分别在qt vs linux下做测试! Qt and Linux 正确!
| 如果像抛出特定的类型异常,可以利用异常的接口声明 |
| void func() throw(int) |
| void func() throw(int,double,char) |
| void func() throw() |
| |
| QT下: |
| |
| #include "mainwindow.h" |
| #include <QApplication> |
| #include <QDebug> |
| |
| |
| void func()throw() |
| { |
| |
| throw 3.14; |
| } |
| |
| int main(int argc, char* argv[]) |
| { |
| |
| |
| |
| |
| |
| |
| try |
| { |
| func(); |
| } |
| catch (int) |
| { |
| qDebug() << "int类型的异常捕获"; |
| } |
| catch (...) |
| { |
| qDebug() << "其它异常捕获"; |
| } |
| |
| return a.exec(); |
| } |
异常变量的生命周期
- 如果是//MyException e,调用了拷贝构造,就多开销了一份数据,
- 如果是//MyException *e,需要new对象,如果不new会提前释放对象,如果new,则要自己管理delete
- 推荐://MyException &e 容易写,而且就一份数据
- throw的异常是有类型的,可以是数字、字符串、类对象。
- throw的异常是有类型的,catch需严格匹配异常类型。
| class MyException |
| { |
| public: |
| MyException(){ |
| cout << "异常变量构造" << endl; |
| }; |
| MyException(const MyException & e) |
| { |
| cout << "拷贝构造" << endl; |
| } |
| ~MyException() |
| { |
| cout << "异常变量析构" << endl; |
| } |
| }; |
| void DoWork() |
| { |
| throw new MyException(); |
| } |
| |
| void test01() |
| { |
| try |
| { |
| DoWork(); |
| } |
| catch (MyException e) |
| { |
| cout << "捕获 异常" << endl; |
| } |
| } |
| void test02() |
| { |
| try |
| { |
| DoWork(); |
| } |
| catch (MyException &e) |
| { |
| cout << "捕获 异常" << endl; |
| } |
| } |
| |
| void test03() |
| { |
| try |
| { |
| DoWork(); |
| } |
| catch (MyException *e) |
| { |
| cout << "捕获 异常" << endl; |
| delete e; |
| } |
| } |
点击查看代码
| #include<iostream> |
| #include<string> |
| using namespace std; |
| |
| class MyException |
| { |
| public: |
| MyException() |
| { |
| cout << "MyException的默认构造" << endl; |
| } |
| |
| MyException(const MyException& e) |
| { |
| cout << "MyException拷贝构造" << endl; |
| } |
| |
| ~MyException() |
| { |
| cout << "MyException的析构函数" << endl; |
| } |
| |
| }; |
| |
| void doWork() |
| { |
| throw MyException(); |
| } |
| |
| |
| |
| |
| |
| void test01() |
| { |
| try |
| { |
| doWork(); |
| } |
| catch (MyException &e) |
| { |
| cout << "捕获自定义异常" << endl; |
| } |
| } |
| |
| int main() |
| { |
| test01(); |
| |
| system("pause"); |
| return 0; |
| } |


异常的多态使用:
利用多态实现 printError同一个接口调用,排除不同的错误对象。
| |
| class BaseException{ |
| public: |
| virtual void printError(){}; |
| }; |
| |
| |
| class NullPointerException : public BaseException{ |
| public: |
| virtual void printError(){ |
| cout << "空指针异常!" << endl; |
| } |
| }; |
| |
| class OutOfRangeException : public BaseException{ |
| public: |
| virtual void printError(){ |
| cout << "越界异常!" << endl; |
| } |
| }; |
| |
| void doWork(){ |
| |
| throw NullPointerException(); |
| } |
| |
| void test() |
| { |
| try{ |
| doWork(); |
| } |
| catch (BaseException& ex){ |
| ex.printError(); |
| } |
| } |
| |
点击查看代码
| #include<iostream> |
| #include<string> |
| using namespace std; |
| |
| |
| class BaseException |
| { |
| public: |
| virtual void printError() |
| { |
| cout << "error" << endl; |
| } |
| |
| }; |
| |
| class NullPointerException : public BaseException |
| { |
| public: |
| virtual void printError() |
| { |
| cout << "空指针异常" << endl; |
| } |
| }; |
| |
| class OutOfRangeException : public BaseException |
| { |
| public: |
| virtual void printError() |
| { |
| cout << "越界异常" << endl; |
| } |
| }; |
| |
| void doWork() |
| { |
| |
| throw OutOfRangeException(); |
| } |
| |
| void test01() |
| { |
| try |
| { |
| doWork(); |
| } |
| catch(BaseException &e) |
| { |
| e.printError(); |
| } |
| } |
| |
| int main() |
| { |
| test01(); |
| |
| system("pause"); |
| return 0; |
| } |
C++标准异常库
标准库介绍
标准库中也提供了很多的异常类,它们是通过类继承组织起来的。异常类继承层级结构图如下:

每个类所在的头文件在图下方标识出来。
标准异常类的成员:
- ① 在上述继承体系中,每个类都有提供了构造函数、复制构造函数、和赋值操作符重载。
- ② logic_error类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述
- ③ 所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息。
标准异常类的具体描述:

logic_error的子类:

runtime_error的子类:

| #include<stdexcept> |
| class Person{ |
| public: |
| Person(int age){ |
| if (age < 0 || age > 150){ |
| throw out_of_range("年龄应该在0-150岁之间!"); |
| } |
| } |
| public: |
| int mAge; |
| }; |
| |
| int main(){ |
| |
| try{ |
| Person p(151); |
| } |
| catch (out_of_range& ex){ |
| cout << ex.what() << endl; |
| } |
| |
| system("pause"); |
| return EXIT_SUCCESS; |
| } |
点击查看代码
| #include<stdexcept> |
| throw out_of_range("aaa") ... |
| catch(out_of_range & e) |
| |
| cout << e.what() << endl; |
| |
| |
| |
| #include<iostream> |
| #include<string> |
| #include<stdexcept> |
| using namespace std; |
| |
| |
| |
| class Person |
| { |
| public: |
| Person(string name,int age) |
| { |
| this->m_Name = name; |
| |
| if (age < 0 || age > 200) |
| { |
| |
| |
| throw length_error("长度越界"); |
| } |
| this->m_Age; |
| } |
| string m_Name; |
| int m_Age; |
| }; |
| |
| |
| void test01() |
| { |
| try |
| { |
| Person p1("tom",-1); |
| } |
| catch (out_of_range & e) |
| { |
| cout << e.what() << endl; |
| } |
| catch (length_error& e) |
| { |
| cout << e.what() << endl; |
| } |
| } |
| |
| int main() |
| { |
| test01(); |
| |
| system("pause"); |
| return 0; |
| } |
编写自己的异常类
- ① 标准库中的异常是有限的;
- ② 在自己的异常类中,可以添加自己的信息。(标准库中的异常类值允许设置一个用来描述异常的字符串)。
如何编写自己的异常类?
- ① 建议自己的异常类要继承标准异常类。因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。
- ② 当继承标准异常类时,应该重载父类的what函数和虚析构函数。
- ③ 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供自己的复制构造函数。
| |
| class MyOutOfRange:public exception |
| { |
| public: |
| MyOutOfRange(const string errorInfo) |
| { |
| this->m_Error = errorInfo; |
| } |
| |
| MyOutOfRange(const char * errorInfo) |
| { |
| this->m_Error = string( errorInfo); |
| } |
| |
| virtual ~MyOutOfRange() |
| { |
| |
| } |
| virtual const char * what() const |
| { |
| return this->m_Error.c_str() ; |
| } |
| |
| string m_Error; |
| |
| }; |
| |
| class Person |
| { |
| public: |
| Person(int age) |
| { |
| if (age <= 0 || age > 150) |
| { |
| |
| |
| |
| |
| |
| throw MyOutOfRange(("我的异常 年龄必须在0~150之间")); |
| } |
| else |
| { |
| this->m_Age = age; |
| } |
| |
| } |
| |
| int m_Age; |
| }; |
| |
| |
| void test01() |
| { |
| try |
| { |
| Person p(151); |
| } |
| catch ( out_of_range & e ) |
| { |
| cout << e.what() << endl; |
| } |
| catch (length_error & e) |
| { |
| cout << e.what() << endl; |
| } |
| catch (MyOutOfRange e) |
| { |
| cout << e.what() << endl; |
| } |
| } |
点击查看代码
| 编写自己的异常类: |
| 1.自己的异常类,需要继承于 exception |
| 2.重写虚析构、what |
| 3.内部维护一个错误信息的字符串 |
| 4.构造是传入错误信息字符串,what返回 |
| |
| |
| |
| #include<iostream> |
| #include<string> |
| using namespace std; |
| |
| class MyOutOfRangeException : public exception |
| { |
| public: |
| |
| MyOutOfRangeException(string errorInfo) |
| { |
| this->m_errorInfo = errorInfo; |
| } |
| virtual ~MyOutOfRangeException() |
| { |
| |
| } |
| virtual const char* what() const |
| { |
| |
| |
| return this->m_errorInfo.c_str(); |
| } |
| |
| string m_errorInfo; |
| }; |
| |
| class Person |
| { |
| public: |
| Person(string name,int age) |
| { |
| this->m_Name = name; |
| |
| if (age < 0 || age > 200) |
| { |
| |
| throw MyOutOfRangeException(string("我自己的年龄越界异常")); |
| |
| } |
| this->m_Age; |
| } |
| string m_Name; |
| int m_Age; |
| }; |
| |
| |
| void test01() |
| { |
| try |
| { |
| Person p1("tom",-1); |
| } |
| catch (MyOutOfRangeException & e) |
| { |
| cout << e.what() << endl; |
| } |
| } |
| |
| int main() |
| { |
| test01(); |
| |
| system("pause"); |
| return 0; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)