本文最后更新于:2021年2月8日 晚上
概览:C++异常,自定义异常类型以及栈解旋。
c++异常之简单的语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream>
using namespace std;
int division(int a, int b) {
if (b == 0) throw b; else return a / b; }
int main() {
try{ division(10, 0); } catch (int e) { cout << "被除数为" << e << endl; }
return 0; }
|
C++异常的优点
- C++函数的返回值可以被忽略,但是异常是不能被忽略的。如果程序出现了异常,但是没有被捕获,程序就会终止。(C++异常必须被处理)这样可以使得程序更加健壮。
- 函数返回值可能没有什么语义信息,但是异常却可以包含语义信息,例如异常类名就可以。
- 异常作为一个类,可以拥有自身的成员,这些成员足以传递信息。
- 异常的处理可以跳级调用,当在多个函数的调用栈时出现了异常,不需要每级函数都进行处理,只需要在某一处进行处理即可。
C++异常接口声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| void excpt1() throw (int,float,double) {
throw "abc";
}
void excpt2() throw() { }
void excpt3() { }
void test3() {
try { excpt1(); } catch(...){ cout << "1发生异常" << endl; }
excpt2();
excpt3(); }
|
异常类型自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class MyException { public: MyException(string err) { m_err = err; } MyException(const MyException& other) { m_err = other.m_err; } MyException& operator=(const MyException& other) { if (this == &other) return *this;
m_err = other.m_err; return *this; } void showError() { cout << this->m_err << endl; }
private: string m_err; };
void throwEcp() { throw MyException("异常发生"); }
void test4() { try { throwEcp(); } catch (MyException e) { e.showError(); } }
|
我在Applied C++这本书中看到了可以在类的成员中定义一个异常类,然后发生异常时抛出这个异常类的对象。
C++异常跨函数
C++的异常机制可以跨函数进行处理,但是C++异常必须是需要处理的,否则就会报错。
函数逐层调用时,一个函数抛出异常,则该异常会逐级向上抛出,如果该异常到了顶层都没有被处理的话,程序就会出错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int division(int a, int b) {
if (b == 0) throw b; else return a / b; }
int driver(int a, int b) {
return division(a, b); }
void test2() { try{ driver(10,0); } catch(int e){ cout << "被除数为" << e << endl; } }
|
栈解旋
异常被抛出后,从进入try块起,到异常被抛出前的这段时间里,在栈上构造的所有对象,都会被自动析构。
析构的顺序与构造的顺序相反,这就是栈解旋。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Person {
public: Person() { cout << "对象创建"<<endl; } ~Person() { cout << "对象析构" << endl; }
};
int personDriver(int a, int b) {
Person p; return division(a, b); }
void test4() {
try { personDriver(10, 0); } catch (int e) { cout << "捕获异常: " << e << endl; }
}
|
输出结果:
异常变量的生命周期
- catch捕获的异常变量如果是普通类型对象,非指针非引用,会调用拷贝构造,异常处理完之后就会析构。
- 而引用类型,也不会调用拷贝构造,异常处理之后就会析构。
- 指针类型则比较特别,先构造然后析构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class MyException { public: MyException() { cout << "普通构造函数" << endl; } MyException(const MyException& other) { cout << "拷贝构造函数" << endl; } MyException& operator=(const MyException& other) {
cout << "重载=函数" << endl; return *this; } ~MyException() { cout << "析构函数" << endl; }
void showError() { cout << "抛出异常" << endl; } };
void excp1() { throw MyException(); }
void excp2() { throw &MyException(); }
void excp3() { throw new MyException(); }
|
1. 普通类型对象
1 2 3 4 5 6 7 8 9 10
| void test1() {
try { excp1(); } catch (MyException e) { e.showError(); }
}
|
执行结果:
1 2 3 4 5
| 普通构造函数 拷贝构造函数 抛出异常 析构函数 析构函数
|
2.引用
1 2 3 4 5 6 7 8 9
| void test2() {
try { excp1(); } catch (MyException &e) { e.showError(); } }
|
执行结果:
3.指针
对于使用指针来捕获异常对象,需要使用excp3()
这样的方式,而不是excp2()
这样的方式。函数2会产生错误。
1 2 3 4 5 6 7 8 9 10 11
| void test3() {
try { excp3(); } catch (MyException *e) { e->showError(); delete e; }
}
|
执行结果:
编写自己的异常类
- 建议自己的异常类要继承标准异常类,不容易导致混乱。
- 当继承标准异常类的时候,应当重载父类的
what函数
以及虚析构函数。
- 在栈展开过程中,要复制异常类型,即会有拷贝操作。那么要根据类的成员来判断是否需要自行提供拷贝构造函数。
标准异常类名:exception
,其所在头文件为#include <stdexcept>
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <iostream> #include <stdexcept>//标准异常的头文件 using namespace std;
class MyException :public exception { public: MyException(const char* error) { myError = new char[strlen(error) + 1]; strcpy_s(myError, strlen(error) + 1, error); } MyException(const MyException& e) { myError = new char[strlen(e.myError) + 1]; strcpy_s(myError, strlen(e.myError) + 1, e.myError); } MyException& operator=(const MyException& e) { myError = new char[strlen(e.myError) + 1]; strcpy_s(myError, strlen(e.myError) + 1, e.myError); } ~MyException() { if (myError != nullptr) { delete[] myError; myError = nullptr; } } virtual const char* what() const { return myError; } public: char* myError; };
class Person { public: Person() { mAge = 0; }
void setAge(int age) { if (age < 0 || age >= 120) { throw MyException("年龄超出正常人类!"); } else this->mAge = age; } public: int mAge; };
int main() { Person p1; try { p1.setAge(-45); } catch (exception& e) { cout << e.what() << endl; } return 0; }
|