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;如果…

D365:LookUp

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

面试经典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轻量级可视…

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&…

【生成式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服务器…

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

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

Java-nio

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

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

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

java中容器继承体系

首先上图 源码解析 打开Collection接口源码&#xff0c;能够看到Collection接口是继承了Iterable接口。 public interface Collection<E> extends Iterable<E> { /** * ...... */ } 以下是Iterable接口源码及注释 /** * Implementing this inte…

makefileGDB使用

一、makefile 1、make && makefile makefile带来的好处就是——自动化编译&#xff0c;一旦写好&#xff0c;只需要一个make命令&#xff0c;整个工程完全自动编译&#xff0c;极大的提高了软件开发的效率 下面我们通过如下示例来进一步体会它们的作用&#xff1a; ①…

使用 Python 实现一个飞书/微信记账机器人,酷B了!

Python飞书文档机器人 今天的主题是&#xff1a;使用Python联动飞书文档机器人&#xff0c;实现一个专属的记账助手&#xff0c;这篇文章如果对你帮助极大&#xff0c;欢迎你分享给你的朋友、她、他&#xff0c;一起成长。 也欢迎大家留言&#xff0c;说说自己想看什么主题的…

代码随想录第天 78.子集 90.子集II

LeetCode 78 子集 题目描述 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&…

LeetCode 2581.统计可能的树根数目:换根DP(树形DP)

【LetMeFly】2581.统计可能的树根数目&#xff1a;换根DP(树形DP) 力扣题目链接&#xff1a;https://leetcode.cn/problems/count-number-of-possible-root-nodes/ Alice 有一棵 n 个节点的树&#xff0c;节点编号为 0 到 n - 1 。树用一个长度为 n - 1 的二维整数数组 edges…

【通信基础知识】完整通信系统的流程图及各模块功能详解

2024.2.29 抱歉最近在写毕设大论文&#xff0c;因此没有太多时间更新。然而&#xff0c;在写论文的过程中&#xff0c;发现自己对通信系统的了解还不够全明白&#xff0c;因此差了一些硕博论文总结了一个完整的通信系统流程图。若有不对的地方请多多指正//部分内容有参考ChatGP…

YOLOv7基础 | 第2种方式:简化网络结构之yolov7.yaml(由104层简化为30层)

前言:Hello大家好,我是小哥谈。通过下载YOLOv7源码可知,原始的yolov7.yaml文件是拆开写的,比较混乱,也不好理解,并且为后续改进增添了很多困难。基于此种情况,笔者就给大家介绍一种将yolov7.yaml文件简化的方法,将104层简化为30层,并且参数量和计算量和原来是一致的,…

内存占用构造方法

#使用虚拟内存构造内存消耗 mkdir /tmp/memory mount -t tmpfs -o size5G tmpfs /tmp/memory dd if/dev/zero of/tmp/memory/block #释放消耗的虚拟内存 rm -rf /tmp/memory/block umount /tmp/memory rmdir /tmp/memory #内存占用可直接在/dev/shm目录下写文件

NLP(一)——概述

参考书: 《speech and language processing》《统计自然语言处理》 宗成庆 语言是思维的载体&#xff0c;自然语言处理相比其他信号较为特别 word2vec用到c语言 Question 预训练语言模型和其他模型的区别? 预训练模型是指在大规模数据上进行预训练的模型&#xff0c;通常…

测试环境搭建整套大数据系统(七:集群搭建kafka(2.13)+flink(1.13.6)+dinky(0.6)+iceberg)

一&#xff1a;搭建kafka。 1. 三台机器执行以下命令。 cd /opt wget wget https://dlcdn.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz tar zxvf kafka_2.13-3.6.1.tgz cd kafka_2.13-3.6.1/config vim server.properties修改以下俩内容 1.三台机器分别给予各自的broker_id…