C++异常(异常的基本语法、栈解旋unwinding、异常接口声明、异常变量的生命周期、异常的多态使用、C++系统标准异常库)
文章目录
- 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++98:auto_ptr<Object> 指针变量名(new Object);
。
C++11:unique_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++系统标准异常库)相关推荐
- c++中的异常--1(基本概念, c语言中处理异常,c++中处理异常,异常的基本使用,栈解旋)
异常基本概念 异常处理就是处理程序中的错误,所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0退出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等) c语言中处理异常 两种方法: 使 ...
- c++中的异常---3(系统标准异常库,编写自己异常类)
系统标准异常库 #incldue out_of_range 等- #include<iostream>#include<string>using namespace std;/ ...
- c++中的异常---2(异常接口声明,异常变量的生命周期,异常的多态使用)
异常接口声明 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类的异常 如果 ...
- 异常处理——栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构.析构的顺序与构造的顺序相反.这一过程称为栈的解旋(unwinding). #pragma warning( ...
- 栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构.析构的顺序与构造的顺序相反.这一过程称为栈的解旋(unwinding). 例如: 1 #include&l ...
- C++ 异常变量的生命周期
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;class MyException { pub ...
- 栈解旋unwinding
传智扫地僧课程学习笔记. #include "iostream" using namespace std;class test { public:test( int a = 0,i ...
- Android系列之Fragment(二)----Fragment的生命周期和返回栈
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...
- C++异常之栈解旋(unwinding)
栈解旋:当发生异常时,从进入try块后,到异常被抛掷前,这期间在栈上的构造的所有对象都会被自动析构.析构的顺序与构造的顺序相反,这一过程被称为栈的解旋(unwinding) (注意栈解旋发生的时间段~ ...
最新文章
- Spring Cloud Feign 熔断机制填坑
- Unity5.1 新的网络引擎UNET(十五) Networking 引用--下
- vue 引用src中的文件_Vue中引用第三方JS文件
- 返回表对象的方法之一--bulk collect into
- Java 面试——字符串操作、值传递、重载与重写
- unrecognized selector sent to instance的一类解决办法
- oracle数据库三大日志,Oracle 数据库日志和用户日志位置
- oracle计算本年第几周,详细讲解“Oracle”数据库的“周数计算”
- itunes无法安装到win7系统更新服务器失败怎么办啊,Win7系统安装iTunes失败出错无法安装的解决方法...
- [PED08]Self-paced Clustering Ensemble自步聚类集成论文笔记
- 关于利用Klayout查看GDS需要导入工艺库的layer properties file(.lyp)
- 数据分析36计(24):因果推断结合机器学习估计个体处理效应
- STM32踩坑1-SWD下载失败
- Hadoop之——基于3台服务器搭建Hadoop3.x集群(实测完整版)
- 如何成为一个软件构架师
- 带你入门多目标跟踪(一)领域概述
- CobaltStrike脚本
- canvas实现绘画板
- 计算机屏幕出现条纹w7,win7电脑屏幕出现条纹四种原因和解决方法
- 俄罗斯方块、坦克大决战、雷电、魔法门、冒险岛——别告诉我你懂数组(0)...
热门文章
- 国产服务器Kylin(aarch64)安装mysql8.0.27
- 使用filter()方法进行数据过滤
- mysql插入百万级_百万级数据插入mysql
- CCF-CSP—2017.12.—4 行车路线(spfa) 题解
- 17 -> 详解 openWRT 的 gpio 配置关系说明
- 拿到软考高级证书就是高级职称了吗?
- CSS(层叠样式表)知识
- 先验分布,后验分布,共轭分布的关系
- 绝对干货3000字,手把手带你用Python实现一个量化炒股策略,小白也能看得懂!...
- 树莓派安装Ubuntu系统详细过程