文章目录

  • 1 异常的基本概念
    • 1.1 C语言中的异常处理
    • 1.2 C++中的异常处理
    • 1.3 异常严格类型匹配
  • 2 栈解旋(unwinding)
  • 3 异常的接口声明【C++11已废弃】
  • 4 异常变量的生命周期
  • 5 异常的多态使用
  • 6 C++标准异常库
  • 7 练习:自定义异常类

1 异常的基本概念

基本思想:当函数出现自身无法处理的错误时,抛出(throw)异常,由该函数的直接或间接的调用者处理异常,即问题检测与问题处理相分离

异常处理:处理程序执行期间的错误,即程序运行过程产生的异常事件,如除0溢出、数组下标越界、读取的文件不存在、空指针、内存不足(内存溢出或内存泄露)等。


1.1 C语言中的异常处理

方法
(1)使用整型的返回值标识错误,如0标识正常、-1标识错误。
(2)使用errno宏全局整型变量)记录错误:当程序出错时,errno宏的值发生改变。

缺点
①函数的返回值不一致,无统一标准,如某些函数返回1 / 0表示成功/错误,某些函数返回0 / 非0表示成功/错误。
②函数的返回值只有1个,无法判断返回值表示错误代码正常结果,可能产生二义性


1.2 C++中的异常处理

关键字
try:try块中的代码为保护代码(即可能抛出异常的代码),通常后跟一个或多个catch块。

注:若try块代码执行期间未产生异常,则try块后的所有catch子句均不会执行。


catch:捕获异常,在希望处理问题的地方,通过异常处理程序捕获异常。

catch(内置基本数据类型)可捕获抛出的内置基本数据类型的数据,如catch(int)可捕获所有整型数据。
catch(MyException e)可捕获自定义异常类的对象,如捕获自定义异常类的匿名对象。
catch(...)可捕获任何类型的异常,通常用在最后一个catch语句,表示捕获除其它catch块异常类型之外的任何类型异常,类似于switch结构default子句

注1:多个catch子句会根据先后顺序依次被检查,匹配异常类型的catch子句会捕获并处理异常继续抛出异常
注2:若不存在与异常类型相匹配的catch语句,则系统会自动调用terminate函数,该函数内部调用abort()函数,使程序终止/中断。


throw:throw语句可在代码块的任何地方抛出异常。

throw 内置基本数据类型数据;表示抛出内置基本数据类型的异常,如throw 3.14;表示抛出double类型异常。
throw 自定义异常类的匿名对象;表示抛出自定义异常类的异常,如throw MyException();表示抛出MyException类的异常。
注:若当前catch语句捕获的异常不想处理或无法处理,可使用throw;继续抛出异常

语法

try{//保护代码(可能抛出异常的代码)
}catch(ExceptionName e1)
{//处理ExceptionName异常的代码
}catch(MyException e)
{//处理自定义异常MyException的代码
}catch(...)     //省略号...,表示捕获任何类型的异常
{//处理任何异常的代码
}/* 若程序运行期间出现异常,且未被捕获,则系统会自动调用terminate函数,使程序终止/中断 */

优点:C++异常处理机制使异常引发异常处理不必在同一个函数中,则底层函数可关注解决具体问题,而无需过多考虑异常处理;由上层调用者在合适的位置,针对不同类型的异常设计合理的处理方法。

①函数的返回值可忽略,但异常不可忽略。若程序出现异常,且无任何地方捕获并处理,则系统会自动调用terminate函数,使程序会终止/中断。
②整型返回值不包含任何语义信息,但异常可包含语义信息,具有见名知意的效果。
③整型返回值缺乏相关的上下文信息;异常类可拥有成员,通过类成员可传递充足的信息。
④异常处理可在调用跳级。当多个函数的调用栈均出现某个错误时,可利用异常处理的栈展开机制,只需在某一处进行异常处理,无需在每级函数均处理。

注:C语言中若程序出现异常,且无任何地方进行处理,则程序无影响;
C++中若程序出现异常,则必须在某处进行处理,若异常未被捕获,则程序会终止/中断。

示例:除数为0的异常案例

#include <iostream>
using namespace std;//自定义异常类
class MyException {public:void printErrorInfo() {cout << "自定义类型的异常" << endl;}
};int division(int a, int b) {if (b == 0) {/* 抛出异常,表示抛出的异常类型 *///throw 1;              //表示抛出int类型异常//throw 3.14;          //表示抛出double类型异常//throw 'q';          //表示抛出char类型异常//throw true;         //表示抛出bool类型异常//throw MyException();    //使用匿名对象,表示抛出自定义异常类的异常//throw "const char*类型异常";   //表示抛出const char*类型异常string ex = "字符串类型异常";  throw ex;               //表示抛出字符串类型异常}return a / b;
}void func() {try {division(5, 0);}catch (int) {        //捕获int类型数据cout << "int类型异常" << endl;}catch (double) {    //捕获double类型数据cout << "double类型异常" << endl;}catch (char) {        //捕获char类型数据cout << "char类型异常" << endl;}catch (bool) {        //捕获bool类型数据cout << "bool类型异常" << endl;}catch (const char*) { //捕获const char*类型数据cout << "const char*类型异常" << endl;}catch (MyException e) { //捕获MyException类对象//通过异常类对象调用成员函数e.printErrorInfo();}catch (...) {  //捕获任何类型的异常(本例中,可捕获string类型异常)//不进行处理,继续抛出throw;        //本例中,继续抛出string类型异常}
}int main(){try {func();        //本例中,继续抛出string类型异常}catch(string){cout << "main函数中处理string类型异常..." << endl;}/* 若程序运行期间出现异常,且未被捕获,则系统会自动调用terminate函数,使程序会终止/中断 */return 0;
}

1.3 异常严格类型匹配

C++异常机制与函数机制互不干涉,但异常的捕捉方式通过严格类型匹配。
例:
throw抛出字符串常量(const char*类型)类型的异常时,如throw "error";,则catch子句需严格使用catch(const char*)捕获,而不能使用char*类型catch(char*)或string类型catch(string)捕获。

throw抛出字符串类型(string类型)的异常时,如string err = "error";throw err;,则catch子句需严格使用catch(string)捕获,而不能使用const char*类型catch(const char*)捕获。

字符串类型:const char*类型(C语言)与string类型(C++)的关系
const char*类型可隐式转换为string类型string类型不可隐式转换为const char*类型
string类型对象可调用成员函数c_str()转换为const char*类型
函数声明const char* string::c_str() const;

#include <iostream>
using namespace std;int division(int a, int b) {if (b == 0) {/* 抛出异常,表示抛出的异常类型 *///string ex = "error";//throw ex;              //表示抛出string字符串类型异常//throw ex.c_str();      //表示抛出const char*类型异常throw "error";       //表示抛出const char*类型异常}return a / b;
}void func() {try {division(5, 0);}catch (char*) {          //捕获char*类型数据cout << "char*类型异常" << endl;}catch (const char*) {   //捕获const char*类型数据cout << "const char*类型异常" << endl;}catch (string) {        //捕获string类型数据cout << "string类型异常" << endl;}
}int main() {func();        //"const char*类型异常"return 0;
}

2 栈解旋(unwinding)

栈解旋:异常被抛出后,从进入try块开始,到异常被抛出(throw)前,该段期间栈上创建的所有对象,均会被自动析构,且析构顺序与构造顺序相反【栈的特点:先进后出】。

注:智能指针:使用类模板托管new操作符创建的堆区对象,避免堆区内存泄露。
C++98auto_ptr<Object> 指针变量名(new Object);
C++11unique_ptr<Object> 指针变量名(new Object);,需包含头文件#include <memory>

示例:栈解旋

#include <iostream>
using namespace std;#include <memory> //智能指针头文件class Object {public:int index;Object() {cout << "Object默认无参构造" << endl;}Object(int idx) {this->index = idx;cout << "Object带参构造:" << this->index << endl;}~Object() {cout << "Object析构函数:" << this->index << endl;}
};int main() {try {/* 栈解旋:异常被抛出后,从进入try块开始,到异常被抛出前,栈上数据会被自动释放,且释放顺序与创建顺序相反 */Object obj1(1);Object obj2(2);//使用智能指针托管堆区创建的对象,避免堆区内存泄露//C++98智能指针auto_ptr<Object> obj3(new Object(3)); //C++11智能指针unique_ptr<Object> obj4(new Object(4));throw 3.14;}catch (double) {cout << "捕获double类型异常..." << endl;}return 0;
}

输出结果

Object带参构造:1
Object带参构造:2
Object带参构造:3
Object带参构造:4
Object析构函数:4
Object析构函数:3
Object析构函数:2
Object析构函数:1
捕获double类型异常...

3 异常的接口声明【C++11已废弃】

使用场景:为加强程序的可读性,可在函数声明中显式列出可能抛出异常的全部类型。若某个类或函数只允许抛出指定类型的异常,可使用异常接口声明

(1)抛出指定类型的异常:使用throw关键字,并指定异常类型。
语法void func() throw(T1, T2, T3); 该函数只允许抛出类型T1、T2、T3及其子类型的异常。

(2)不允许抛出任何类型的异常:使用throw关键字,指定异常类型列表为空。
语法void func() throw(); 该函数不允许抛出任何类型的异常。

(3)允许抛出任何类型的异常:不使用throw关键字。
语法void func(); 该函数可抛出任何类型的异常。

注1:若某个函数抛出其异常接口声明类型之外的其它类型异常,则系统会调用unexcepted函数,该函数内部会调用terminate函数,使程序终止/中断。
注2:C++11已废弃dynamic exception specifications,建议使用noexcept

示例:异常接口声明(C++11已废弃,VS不支持,VS code暂支持)

#include <iostream>
using namespace std;//异常的接口声明:只允许抛出特定类型的异常
void func() throw(int, double) {//报错:terminate called after throwing an instance of 'char'//throw 'c';
}int main() {try {func();}catch (int) {cout << "捕获int类型异常" << endl;}catch (double) {cout << "捕获double类型异常" << endl;}catch (...) {cout << "捕获其它类型异常" << endl;}return 0;
}

4 异常变量的生命周期

自定义异常类对象的生命周期(4种情况):

(1)以值方式抛出及捕获匿名异常对象【不建议】
抛出对象throw MyException();
捕获对象catch(MyException e)
特点:catch语句捕获抛出的匿名异常对象时,会调用拷贝构造函数创建匿名对象的拷贝副本产生额外内存开销

//1.以【值方式】抛出和捕获匿名异常对象:调用拷贝构造函数创建匿名对象的拷贝
/* 输出结果 */
//MyException类的无参构造函数
//MyException类的拷贝构造函数
//捕获自定义异常...
//MyException类的析构函数
//MyException类的析构函数
void func1() {try {//抛出匿名对象throw MyException();}catch (MyException e) {     //以值方式接收匿名对象cout << "捕获自定义异常..." << endl;}
}

(2)以地址值方式(指针类型)抛出及捕获栈区异常对象【不建议】
抛出对象MyException me;throw &me;
捕获对象catch(MyException *e)
特点:异常对象抛出后立即被释放,在异常捕获前对象即不存在,对象指针指向已被释放的内存,若操作该内存则为非法操作

注:无法抛出匿名对象的地址(throw &MyException();),编译器报错:&要求左值(无法对匿名对象取地址)。

//2.以【地址值方式】抛出和捕获异常对象:对象抛出立即被释放,对象指针指向被释放的内存
/* 输出结果 */
//MyException类的无参构造函数
//MyException类的析构函数
//捕获自定义异常...
void func2() {try {//抛出对象的地址MyException me;throw &me;//错误:抛出匿名对象的地址//throw &MyException();   //报错:&要求左值(无法对匿名对象取地址)}catch (MyException *e) {    //以地址值方式(指针)接收对象cout << "捕获自定义异常..." << endl;}
}

(3)以引用方式(起别名)抛出及捕获匿名异常对象【最建议】
抛出对象throw MyException();
捕获对象catch(MyException &e)
特点:匿名对象的生命周期延续至左值(引用),在程序结束后释放,即匿名对象不会立即释放。
优点
①不会调用拷贝构造函数创建对象的拷贝,即不会产生额外内存开销
②不会提前释放(匿名)异常对象;
③不需要手动释放堆区内存,即不会导致堆区内存泄露

//3.以【引用方式】抛出和捕获匿名异常对象:匿名对象的生命周期延续至左值(引用)
/* 输出结果 */
//MyException类的无参构造函数
//捕获自定义异常...
//MyException类的析构函数
void func3() {try {//抛出匿名对象throw MyException();}catch (MyException &e) {    //以引用方式接收匿名对象cout << "捕获自定义异常..." << endl;}
}

(4)以地址值方式(指针类型)抛出及捕获堆区匿名异常对象【建议】
抛出对象throw new MyException();
捕获对象catch(MyException *e)
特点:堆区匿名对象不会立即释放,需使用delete手动释放堆区内存,否则导致堆区内存泄露。效果同引用方式

//4.以【地址值方式】(指针类型)抛出及捕获堆区匿名异常对象:堆区匿名对象需手动释放
/* 输出结果 */
//MyException类的无参构造函数
//捕获自定义异常...
void func4() {try {//抛出堆区匿名对象throw new MyException();}catch (MyException *e) {  //以地址值方式接收堆区匿名对象cout << "捕获自定义异常..." << endl;//delete e;      //手动释放堆区内存}
}

示例:异常对象的生命周期(4种情况)

#include <iostream>
using namespace std;/* 自定义异常类 */
class MyException {public://无参构造函数MyException() {cout << "MyException类的无参构造函数" << endl;} //拷贝构造函数MyException(const MyException &me) {cout << "MyException类的拷贝构造函数" << endl;}//析构函数~MyException() {cout << "MyException类的析构函数" << endl;}
};//1.以【值方式】抛出和捕获匿名异常对象:调用拷贝构造函数创建匿名对象的拷贝
/* 输出结果 */
//MyException类的无参构造函数
//MyException类的拷贝构造函数
//捕获自定义异常...
//MyException类的析构函数
//MyException类的析构函数
void func1() {try {//抛出匿名对象throw MyException();}catch (MyException e) {     //以值方式接收匿名对象cout << "捕获自定义异常..." << endl;}
}//2.以【地址值方式】抛出和捕获异常对象:对象抛出立即被释放,对象指针指向被释放的内存
/* 输出结果 */
//MyException类的无参构造函数
//MyException类的析构函数
//捕获自定义异常...
void func2() {try {//抛出对象的地址MyException me;throw &me;//错误:抛出匿名对象的地址//throw &MyException();   //报错:&要求左值(无法对匿名对象取地址)}catch (MyException *e) {    //以地址值方式接收匿名对象cout << "捕获自定义异常..." << endl;}
}//3.以【引用方式】抛出和捕获匿名异常对象:匿名对象的生命周期延续至左值(引用)
/* 输出结果 */
//MyException类的无参构造函数
//捕获自定义异常...
//MyException类的析构函数
void func3() {try {//抛出匿名对象throw MyException();}catch (MyException &e) {    //以引用方式接收匿名对象cout << "捕获自定义异常..." << endl;}
}//4.以【地址值方式】(指针类型)抛出及捕获堆区匿名异常对象:堆区匿名对象需手动释放
/* 输出结果 */
//MyException类的无参构造函数
//捕获自定义异常...
void func4() {try {//抛出堆区匿名对象throw new MyException();}catch (MyException *e) {  //以地址值方式接收堆区匿名对象cout << "捕获自定义异常..." << endl;//delete e;      //手动释放堆区内存}
}int main() {//func1();//func2();//func3();func4();
}

5 异常的多态使用

异常的多态使用:catch语句中,使用基类的引用类型捕获子类异常对象。

示例

#include <iostream>
using namespace std;/* 异常的基类 */
class BaseException {public://纯虚函数或虚函数virtual void printErrorInfo() = 0;
};//异常的子类1:空指针异常
class NullPointerException : public BaseException{public://重写纯虚函数或虚函数virtual void printErrorInfo() {cout << "空指针异常..." << endl;}
};//异常的子类2:索引越界
class IndexOutOfRangeException : public BaseException {public://重写纯虚函数或虚函数virtual void printErrorInfo() {cout << "索引越界异常..." << endl;}
};void main() {try {throw NullPointerException();//throw IndexOutOfRangeException();}catch (BaseException& e) { //多态使用:基类的引用类型捕获子类异常对象e.printErrorInfo();}
}

6 C++标准异常库

C++标准异常库中,异常的根基类为exception类,不同异常子类需包含不同头文件
通过异常类的成员函数const char* exception::what()获取字符串标识异常

C++异常类exception的继承层次及对应头文件

示例

#include<iostream>
using namespace std;
#include<stdexcept>       //包含头文件class Person {public:int age;Person(int age) {//成员属性的有效性校验if (age < 0 || age > 120) {//std::out_of_range(const char*);throw out_of_range("年龄值无效(0~120)");}else {this->age = age;}}
};void main() {try {Person p(150);}catch (exception& e) {   //多态:使用父类引用接收子类异常对象//const char* exception::what()   获取字符串标识异常cout << e.what() << endl;  //年龄值无效(0~120)}
}

7 练习:自定义异常类

案例练习:
(1)自定义异常类,继承自exception基类
(2)使用多态,父类引用捕获子类对象
(3)string类型与const char*类型的互相转换

字符串类型:const char*类型(C语言)与string类型(C++)的关系
const char*类型可隐式转换为string类型string类型不可隐式转换为const char*类型
string类型对象可调用成员函数c_str()转换为const char*类型
函数声明const char* string::c_str() const;

#include <iostream>
using namespace std;
#include <stdexcept>class MyOutOfRange : public exception {/*//基类exception的构造函数exception();explicit exception(char const* const _Message);exception(char const* const _Message, int);exception(exception const& _Other);exception& operator=(exception const& _Other);//虚析构或纯虚析构:多态调用时,父类指针释放时默认不会调用子类析构函数virtual ~exception();//虚成员函数const char* what() const;//私有成员属性const char* _Mywhat;*/public:string errInfo; //记录错误提示信息//带参构造函数MyOutOfRange(const char* err) {//const char*可隐式转换为stringthis->errInfo = err;}//带参构造函数-重载MyOutOfRange(const string& err) {this->errInfo = err;}//重写父类虚成员函数virtual const char* what() const {//string不可隐式转换为const char*//string类型对象可调用成员函数c_str()转换为const char*类型//const char* string::c_str() const return this->errInfo.c_str();}
};class Person {public:int age;Person(int age) {//成员属性的有效性校验if (age < 0 || age > 120) {//std::out_of_range(const char*);//throw out_of_range("年龄值无效(0~120)");//const char*类型参数//throw MyOutOfRange("const char*型参数:年龄值无效(0~120)");//string类型参数string errStr = "string型参数:年龄值无效(0~120)";throw MyOutOfRange(errStr);}else {this->age = age;}}
};void main() {try {Person p(150);}catch (exception& e) {   //多态:使用父类引用接收子类异常对象//const char* exception::what()   获取字符串标识异常cout << e.what() << endl;  //年龄值无效(0~120)}
}

C++异常(异常的基本语法、栈解旋unwinding、异常接口声明、异常变量的生命周期、异常的多态使用、C++系统标准异常库)相关推荐

  1. c++中的异常--1(基本概念, c语言中处理异常,c++中处理异常,异常的基本使用,栈解旋)

    异常基本概念 异常处理就是处理程序中的错误,所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0退出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等) c语言中处理异常 两种方法: 使 ...

  2. c++中的异常---3(系统标准异常库,编写自己异常类)

    系统标准异常库 #incldue out_of_range 等- #include<iostream>#include<string>using namespace std;/ ...

  3. c++中的异常---2(异常接口声明,异常变量的生命周期,异常的多态使用)

    异常接口声明 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类的异常 如果 ...

  4. 异常处理——栈解旋(unwinding)

    异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构.析构的顺序与构造的顺序相反.这一过程称为栈的解旋(unwinding). #pragma warning( ...

  5. 栈解旋(unwinding)

    异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构.析构的顺序与构造的顺序相反.这一过程称为栈的解旋(unwinding). 例如: 1 #include&l ...

  6. C++ 异常变量的生命周期

    #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;class MyException { pub ...

  7. 栈解旋unwinding

    传智扫地僧课程学习笔记. #include "iostream" using namespace std;class test { public:test( int a = 0,i ...

  8. Android系列之Fragment(二)----Fragment的生命周期和返回栈

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

  9. C++异常之栈解旋(unwinding)

    栈解旋:当发生异常时,从进入try块后,到异常被抛掷前,这期间在栈上的构造的所有对象都会被自动析构.析构的顺序与构造的顺序相反,这一过程被称为栈的解旋(unwinding) (注意栈解旋发生的时间段~ ...

最新文章

  1. Spring Cloud Feign 熔断机制填坑
  2. Unity5.1 新的网络引擎UNET(十五) Networking 引用--下
  3. vue 引用src中的文件_Vue中引用第三方JS文件
  4. 返回表对象的方法之一--bulk collect into
  5. Java 面试——字符串操作、值传递、重载与重写
  6. unrecognized selector sent to instance的一类解决办法
  7. oracle数据库三大日志,Oracle 数据库日志和用户日志位置
  8. oracle计算本年第几周,详细讲解“Oracle”数据库的“周数计算”
  9. itunes无法安装到win7系统更新服务器失败怎么办啊,Win7系统安装iTunes失败出错无法安装的解决方法...
  10. [PED08]Self-paced Clustering Ensemble自步聚类集成论文笔记
  11. 关于利用Klayout查看GDS需要导入工艺库的layer properties file(.lyp)
  12. 数据分析36计(24):因果推断结合机器学习估计个体处理效应
  13. STM32踩坑1-SWD下载失败
  14. Hadoop之——基于3台服务器搭建Hadoop3.x集群(实测完整版)
  15. 如何成为一个软件构架师
  16. 带你入门多目标跟踪(一)领域概述
  17. CobaltStrike脚本
  18. canvas实现绘画板
  19. 计算机屏幕出现条纹w7,win7电脑屏幕出现条纹四种原因和解决方法
  20. 俄罗斯方块、坦克大决战、雷电、魔法门、冒险岛——别告诉我你懂数组(0)...

热门文章

  1. 国产服务器Kylin(aarch64)安装mysql8.0.27
  2. 使用filter()方法进行数据过滤
  3. mysql插入百万级_百万级数据插入mysql
  4. CCF-CSP—2017.12.—4 行车路线(spfa) 题解
  5. 17 -> 详解 openWRT 的 gpio 配置关系说明
  6. 拿到软考高级证书就是高级职称了吗?
  7. CSS(层叠样式表)知识
  8. 先验分布,后验分布,共轭分布的关系
  9. 绝对干货3000字,手把手带你用Python实现一个量化炒股策略,小白也能看得懂!...
  10. 树莓派安装Ubuntu系统详细过程