c++异常机制(3) -- 异常类型和生命周期

目录

抛出的异常类型大致可以分为三种。 

第一种    基本类型

1. 可以直接抛出常量 

2. 也可以抛出定义好的变量 

3. 如果我们使用const,会不会影响到异常的匹配。

第二种    字符串类型以及指针类型

1. 使用字符指针 

注意: 

2. 使用string类型 

第三种  自定义类型(类类型) 

 

注意事项:

 

catch是根据我们抛出的异常信息类型来捕获的。 

抛出的异常类型大致可以分为三种。 

第一种    基本类型

 int, char, float,double等类型。

以抛出int类型的数据为例 

1. 可以直接抛出常量 
void func1() {throw - 1;printf("func1");
}int main(void) {try {func1();}catch (int error) {printf("异常处理 %d\n",error);}system("pause");return 0;
}
2. 也可以抛出定义好的变量 
void func1() {int err = -1;   // 定义变量throw err;printf("func1");
}int main(void) {try {func1();}catch (int error) {printf("异常处理 %d\n",error);}system("pause");return 0;
}
3. 如果我们使用const,会不会影响到异常的匹配。

我们对上面的代码进行修改, 我们在func1定义err时和catch参数中两个地方加上const,或者一个加一个不加。执行代码,会发现依然能匹配成功。所以对于普通类型的数据,有没有const都不会影响到异常的匹配的。

第二种    字符串类型以及指针类型

首先C语言的字符串类型是字符指针,c++的字符串类型string(当然c++中包含C语言指针)。 

1. 使用字符指针 

第一种:  直接抛出字符串常量 

对于字符串常量,我们直接使用非const指针指向它是不安全的,但是有的编译器允许这么做。所以有的编译器char*类型也可以与字符串常量匹配,但是有的编译器认为字符串常量必须使用const的字符指针指向才行,所以只能与const char*匹配。

void func1() {throw "异常";printf("func1");
}int main(void) {try {func1();}catch (const char* error) {printf("异常处理 %s\n",error);}system("pause");return 0;
}
注意: 

虽然在c++中我们可以使用string定义字符串,但是string只是c++封装的一个类型。字符串常量或者字符指针,本身表示的是一个地址,所以它是没有办法与string类型匹配成功的,需要使用字符指针。 

第二种:  使用字符指针指向或者字符数组

 字符数组:  

void func1() {char arr[] = "异常";throw arr;printf("func1");
}int main(void) {try {func1();}catch (const char* error) {printf("异常处理 %s\n",error);}system("pause");return 0;
}

字符指针:   

void func1() {char* arr = (char*)"异常";throw arr;printf("func1");
}int main(void) {try {func1();}catch (char* error) {printf("异常处理 %s\n",error);}system("pause");return 0;
}

 无论是字符数组还是字符指针:

1. 如果我们抛出的是const 修饰的,那么只能和catch中const修饰的char*匹配。 

2. 如果我们抛出的是非const修饰的,那么catch中用不用const修饰都可以匹配成功。 

3. 其实和赋值时,const修饰的不能赋值给非const修饰的,非const修饰的可以赋值给const修饰的是一个道理的。

4. 当然如果const在*后面修饰,那么就不会影响匹配。char*const可以和char*匹配成功。

2. 使用string类型 

string类型其实和普通类型差不多,string*和char*也类似。但是string类型和char*类型是无法匹配的,虽然它们都可以表示字符串,但是是不同的类型。

第三种  自定义类型(类类型) 

我们可以将相应的异常封装成一个类,类中封装一些方法,在出现这类异常之后, 可以抛出一个此类的对象。

 看下面这段代码,将打开文件异常和写入文件异常封装成两个类,然后抛出它们的对象。

#define BUFFER_SIZE 1024class OpenFileError {
public:OpenFileError(int err) :errorData(err) {};void print() {switch (errorData) {case -1:printf("源文件打开失败 %d\n", errorData);break;case -2:printf("目的文件打开失败 %d\n", errorData);break;}}
private:int errorData;
};class WriteFileError {
public:void print() {printf("文件写入失败");}
};// 将一个文件中的内容拷贝到另外一个文件中去
int makeFile(const char* dest, const char* src) {// 定义文件指针FILE* fp1 = NULL, * fp2 = NULL;// 打开文件, 以只读二进制形式打开文件,打开失败返回NULLfp1 = fopen(src, "rb"); // 判断文件是否成功打开if (!fp1) {throw OpenFileError(-1);}// 打开文件,以只写二进制形式打开文件,打开失败返回NULLfp2 = fopen(dest, "wb");// 判断文件是否成功打开if (!fp2) {throw OpenFileError(-2);    //  返回错误标记,表示目标文件打开失败}// 进行文件的拷贝char buffer[BUFFER_SIZE];    // 1024字节的缓存int readLen, writeLen;       // 每次读取的长度和写入的长度// 读取的长度大于0,说明有内容可以写入,执行循环体的写入内容while ((readLen = fread(buffer, 1, BUFFER_SIZE, fp1)) > 0) {writeLen = fwrite(buffer, 1, readLen, fp2);// 如果一次写入的长度和读取的长度不等,那么说明写入失败if (readLen != writeLen) {throw WriteFileError(); }}// 关闭文件fclose(fp1);fclose(fp2);return 0;  // 一切正常返回0
}int makeFile2(const char* dest, const char* src) {int ret;ret = makeFile(dest, src);printf("makeFile2 函数被调用");return ret;
}int main(void) {int ret = 0;try {ret = makeFile2("dest.txt", "src.txt");}catch (OpenFileError& error) {error.print();}catch (WriteFileError& error) {error.print();}system("pause");return 0;
}

其实抛出类对象的写法不止一种,但是我们为什么选择使用上面的方式呢?   

我们使用下面的代码进行说明。

class Error {
public:Error(int err) :errorData(err) {cout << "构造函数" << errorData << endl;};~Error() {cout << "析构函数" << errorData << endl;};Error(const Error& error) {errorData = error.errorData;cout << "拷贝构造函数"<< errorData << endl;};void print() {printf("异常:%d", errorData);}
public:int errorData;
};void func1() {Error err(-1);throw err;printf("func1");
}int main(void) {try {func1();}catch (Error error) {error.errorData = 10;printf("异常处理 %d\n",error);}system("pause");return 0;
}

运行结果: 

 

分析: 

上面代码,我们抛出异常时是抛出的定义的对象,在catch接收的时候也是直接使用的普通参数形式(Error error)。 我们使用类对象的构造,析构,拷贝来观察抛出的过程。

运行结果:  

第一个构造函数是用来构造我们的func1函数中的error对象的。 

第一个拷贝构造函数是在抛出的时候,编译器会根据我们抛出的error对象,创建一个匿名对象进行抛出,所以会调用一次拷贝构造函数。 

第二个拷贝构造函数是我们抛出的匿名对象,在与catch中的参数Error error配对之后,直接将匿名对象在error初始化时赋值给它。 

第一个析构函数是我们抛出创建的匿名对象后,func1函数就执行结束了,error是其内部的局部变量,就会被销毁,所以这个析构函数是用来销毁func1中的error对象的。 

异常处理 10是我们配对成功,之后执行的异常处理代码。 

析构函数 10是我们在和catch匹配的时候,根据其参数创建了对象error,其作用域就是这个catch开始到结束,catch的代码执行完,结束的时候,这个对象的生命周期也就结束了,所以调用析构函数 

析构函数 -1是用来销毁系统抛出的匿名对象,而调用的构造函数。

 

说明: 

最后两个析构函数我们怎么能确定是来销毁哪个对象的呢?在代码中我们在对应的构造函数中打印了数据errorData的值,我们在func1中创建对象时将其初始化为-1,然后编译器会将其拷贝给匿名对象,匿名对象内的属性值我们无法处理,但是在catch接收匿名对象的时候,定义了另外一个对象error,我们将这个对象的值显示修改为10。所以,析构函数 10就是用来析构它的。

对比: 

我们将第二段代码进行修改 --  在抛出时,使用匿名对象,接收是使用引用

class Error {
public:Error(int err) :errorData(err) {cout << "构造函数" << errorData << endl;};~Error() {cout << "析构函数" << errorData << endl;};Error(const Error& error) {errorData = error.errorData;cout << "拷贝构造函数"<< errorData << endl;};void print() {printf("异常:%d", errorData);}
public:int errorData;
};void func1() {throw Error(-1);printf("func1");
}int main(void) {try {func1();}catch (Error& error) {error.errorData = 10;printf("异常处理 %d\n",error);}system("pause");return 0;
}

运行结果: 

 

分析:   

首先第一眼看,这个运行的效率就比前面的效率高很多。 就是在抛出类对象的时候,直接抛出匿名对象,在catch的参数中写使用类的引用接收。

 

运行结果: 

构造函数 -1:   创建匿名对象并抛出。 

异常处理 10:   匹配成功,执行异常处理代码。 

析构函数 10:   调用析构函数,销毁匿名对象。

 

说明:  

为什么构造函数和析构函数打印出来的errorData不一样?因为我们在构造匿名函数时将errorData初始化为-1,但是在catch捕获异常的时候,我们将其修改为了10。因为我们catch中使用的是引用,所以error还是表示哪个匿名对象,所以在析构时errorData变为了10。 

 

那么为什么可以使用引用来接收函数抛出的匿名对象呢?

因为,我们在第二段代码中知道,在异常机制中,函数抛出的匿名对象析构要在catch中创建的对象析构之后。所以,我们使用引用来接收函数返回的匿名对象之后,在catch语句结束时,是引用的量先被释放,匿名对象后被释放。这样就不会存在引用指向局部变量的问题了。

 

总结:    

综上所述,我们在使用自定义类抛出异常的时候,应该直接抛出其匿名对象,并且使用引用来接收。这样就会少调用几次构造函数和析构函数,那么就大大提高了效率。

 

注意事项:

1.  上面说到,catch的参数中对象的引用对象的定义都可以与抛出的匿名对象进行匹配,那么如果同时存在两个catch,参数分别为这两种,那么发生什么? 

会出错,因为两个都能匹配,编译器区分不了,自然就报错了。 

2.   1.中的情况,不仅是类类型,对于普通类型和字符串类型也是一样的。 

3.   上面说到,普通类型,catch中参数加const和不加const都能匹配,所以两者都存在的话,编译器也无法区分,会报错。 但是对于指针就不会报错。

4.   其它情况也还类似。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/711310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机专业大学生的简历,为何会出现在垃圾桶

为什么校招过后垃圾桶里全是简历&#xff0c;计算机专业的学生找工作有多难&#xff1f; 空哥这么跟你说吧&#xff0c;趁现在还来得及&#xff0c;这些事情你一定要听好了。 第一&#xff0c;计算机专业在学校学的东西是非常有限的&#xff0c;985211的还好&#xff0c;如果…

GPS历史轨迹优化算法的研究与实现

GPS历史轨迹优化算法的研究与实现 摘要 本研究提出了一种综合利用数据清洗、密度聚类、卡尔曼滤波和地图匹配的新算法,命名为“DSKF-Match”。该算法旨在处理GPS轨迹数据,通过清洗、聚类、平滑和匹配等步骤,提高数据的质量和准确性。首先,算法利用时间窗口法进行数据清洗…

D365:LookUp

文章目录 前言一、复制onLookUp事件方法二、LookUp方法 前言 在Form的字段的onLookUp方法中&#xff0c;添加下拉框。 一、复制onLookUp事件方法 二、LookUp方法 [FormControlEventHandler(formControlStr(EcoResProductDetailsExtended, VyaKeyItemType_VyaMaterialSubCode…

Vue2:路由守卫实现权限管理之独享路由守卫

一、情景说明 单独给某个路由组件配置守卫 二、案例 给news路由配置独享路由守卫 在进入该路由组件前&#xff0c;会触发相关函数 函数内编写鉴权功能的相关代码即可 关键配置&#xff1a;beforeEnter {name:xinwen,path:news,component:News,meta:{isAuth:true,title:新闻}…

【PyTorch知识点汇总】

PyTorch是一个广泛使用的深度学习框架&#xff0c;它提供了许多功能强大的工具和函数&#xff0c;用于构建和训练神经网络。以下是一些PyTorch的常用知识点和示例说明&#xff1a; 张量&#xff08;Tensors&#xff09; 创建张量&#xff1a;使用torch.tensor()​、torch.Tenso…

面试经典150题——用最少数量的箭引爆气球

"The only person you are destined to become is the person you decide to be." - Ralph Waldo Emerson 1. 题目描述 2. 题目分析与解析 这个题目开始读题的时候是有点不好理解题意的&#xff0c;因此我先做个图让大家对于题意有更好更直观的理解再来分析题目。 …

如何使用Portainer创建Nginx容器并搭建web网站发布至公网可访问【内网穿透】

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

SQL 常见命令及规范

常见命令 1. 查看当前所有数据库 show databases; 2. 打开指定的库 use 库名 ; 3. 查看当前库的所有表 show tables; 4. 查看其他库的所有表 show tables from 库名 ; 5. 创建表 cerate table 表名 ( 列名 列类型&#xff0c; 列名 列类型&#xff0c; ..... …

基于YOLO家族最新模型YOLOv9开发构建自己的个性化目标检测系统从零构建模型完整训练、推理计算超详细教程【以自建数据酸枣病虫害检测为例】

在我前面的系列博文中,对于目标检测系列的任务写了很多超详细的教程,目的是能够读完文章即可实现自己完整地去开发构建自己的目标检测系统,感兴趣的话可以自行移步阅读: 《基于官方YOLOv4-u5【yolov5风格实现】开发构建目标检测模型超详细实战教程【以自建缺陷检测数据集为…

C# OpenVINO Crack Seg 裂缝分割 裂缝检测

目录 效果 模型信息 项目 代码 数据集 下载 C# OpenVINO Crack Seg 裂缝分割 裂缝检测 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-02-29T16:35:48.364242 author&#xff1a;Ultralytics task&#xff1a;segment version&…

去掉WordPress网页图片默认链接功能

既然是wordpress自动添加的&#xff0c;那么我们在上传图片到wordpress后台多媒体的时候&#xff0c;就可以手动改变链接指向或者删除掉&#xff0c;问题是每次都要这么做很麻烦&#xff0c;更别说有忘记的时候。一次性解决这个问题有两种方法&#xff0c;一种是No Image Link插…

【生成式AI】ChatGPT原理解析(1/3)- 对ChatGPT的常见误解

Hung-yi Lee 课件整理 文章目录 误解1误解2ChatGPT真正在做的事情-文字接龙 ChatGPT是在2022年12月7日上线的。 当时试用的感觉十分震撼。 误解1 我们想让chatGPT讲个笑话&#xff0c;可能会以为它是在一个笑话的集合里面随机地找一个笑话出来。 我们做一个测试就知道不是这样…

C# Post数据或文件到指定的服务器进行接收

目录 应用场景 实现原理 实现代码 PostAnyWhere类 ashx文件部署 小结 应用场景 不同的接口服务器处理不同的应用&#xff0c;我们会在实际应用中将A服务器的数据提交给B服务器进行数据接收并处理业务。 比如我们想要处理一个OFFICE文件&#xff0c;由用户上传到A服务器…

中国汽车电子行业发展现状分析及投资前景预测报告

全版价格&#xff1a;壹捌零零 报告版本&#xff1a;下单后会更新至最新版本 交货时间&#xff1a;1-2天 第一章 汽车电子相关概述 1.1 汽车的相关介绍 1.1.1 汽车的概念 我国国家最新标准《汽车和挂车类型的术语和定义》&#xff08;GB/T3730&#xff0e;1—2001&…

基于springboot+vue的贸易行业crm系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

Flink分区相关

0、要点 Flink的分区列不会存数据&#xff0c;也就是两个列有一个分区列&#xff0c;则文件只会存另一个列的数据 1、CreateTable 根据SQL的执行流程&#xff0c;进入TableEnvironmentImpl.executeInternal&#xff0c;createTable分支 } else if (operation instanceof Crea…

Java-nio

一、NIO三大组件 NIO的三大组件分别是Channel&#xff0c;Buffer与Selector Java NIO系统的核心在于&#xff1a;通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如&#xff1a;文件、套接字)的连接。若需要使用 NIO 系统&#xff0c;需要获取用于连接 IO 设备的通…

Spring的简单使用及内部实现原理

在现代的Java应用程序开发中&#xff0c;Spring Framework已经成为了不可或缺的工具之一。它提供了一种轻量级的、基于Java的解决方案&#xff0c;用于构建企业级应用程序和服务。本文将介绍Spring的简单使用方法&#xff0c;并深入探讨其内部实现原理。 首先&#xff0c;让我们…

mysql8.0使用MGR实现高可用

一、三节点MGR集群的安装部署 1. 安装准备 准备好下面三台服务器&#xff1a; IP端口角色192.168.150.213306mgr1192.168.150.223306mgr2192.168.150.233306mgr3 配置hosts解析 # cat >> /etc/hosts << EOF 192.168.150.21 mgr1 192.168.150.22 mgr2 192.168…

Windows环境下的调试器探究——硬件断点

与软件断点与内存断点不同&#xff0c;硬件断点不依赖被调试程序&#xff0c;而是依赖于CPU中的调试寄存器。 调试寄存器有7个&#xff0c;分别为Dr0~Dr7。 用户最多能够设置4个硬件断点&#xff0c;这是由于只有Dr0~Dr3用于存储线性地址。 其中&#xff0c;Dr4和Dr5是保留的…