C++重温笔记(八): C++异常

1. 写在前面

c++在线编译工具,可快速进行实验: https://www.bejson.com/runcode/cpp920/

这段时间打算重新把c++捡起来, 实习给我的一个体会就是算法工程师是去解决实际问题的,所以呢,不能被算法或者工程局限住,应时刻提高解决问题的能力,在这个过程中,我发现cpp很重要, 正好这段时间也在接触些c++开发相关的任务,所有想借这个机会把c++重新学习一遍。 在推荐领域, 目前我接触到的算法模型方面主要是基于Python, 而线上的服务全是c++(算法侧, 业务那边基本上用go),我们所谓的模型,也一般是训练好部署上线然后提供接口而已。所以现在也终于知道,为啥只单纯熟悉Python不太行了, cpp,才是yyds。

和python一样, 这个系列是重温,依然不会整理太基础性的东西,更像是查缺补漏, 不过,c++对我来说, 已经5年没有用过了, 这个缺很大, 也差不多相当重学了, 所以接下来的时间, 重温一遍啦 😉

资料参考主要是C语言中文网和光城哥写的C++教程,然后再加自己的理解和编程实验作为辅助,加深印象。 关于更多的细节,还是建议看这两个教程。

今天这篇文章比较简单, 主要是整理C++的异常处理,所谓异常,就是程序运行时可能会发生一些错误,比如除数为0, 数组下标越界等 ,如果不事先处理,可能导致程序崩溃,无法运行, 在写程序中,其实这些异常情况也需要尽可能的想到,然后用C++的异常处理机制捕获处理这些错误。 C++的异常处理机制主要包括try, catch, throw三个关键字, 下面就来看看啦。

主要内容

  • C++异常处理初识
  • C++异常类型以及多级catch匹配
  • C++throw关键字
  • C++的exception类: C++标准异常的基类

Ok, let’s go!

2. C++异常处理初识

程序错误大致上分为三种类型,语法错误,逻辑错误和运行时错误:

  • 语法错误: 编译和链接阶段可以发现,只有100%符合语法规则的代码才能生成可执行程序。语法错误是最容易发现,最容易排除的错误
  • 逻辑错误: 编写代码思路有问题,无法达到预期目标, 可通过调试解决
  • 运行错误: 运行期间的错误,比如除数为0,内存分配失败,数组越界,文件不存在等,C++异常机制就是解决这哥们而引入的

运行时,如果放任不管,系统就会执行默认操作,终止运行。 C++的异常机制,能捕获运行时错误,给程序一次"起死回生"的机会

从一个例子开始:

int main(){string str = "http://c.biancheng.net";char ch1 = str[100];  //下标越界,ch1为垃圾值cout<<ch1<<endl;char ch2 = str.at(100);  //下标越界,抛出异常cout<<ch2<<endl;return 0;
}

运行代码,在控制台输出 ch1 的值后程序崩溃(我实验发现,也不崩溃呀,可能编译器不同?)。 但至少ch1那里确实存在了越界。

at() 是 string 类的一个成员函数,它会根据下标来返回字符串的一个字符。与[ ]不同,at() 会检查下标是否越界,如果越界就抛出一个异常;而[ ]不做检查,不管下标是多少都会照常访问。

ch1那里不会检查下标越界,虽然逻辑错误,但程序会正常运行。 而ch2那里, 由于at函数会检查越界问题,所以会抛出异常来, 如果我们不处理这个异常,程序就终止了。 这样的好处就是我们能立刻认识到我们的一些错误。

那么,如何捕获异常,避免程序崩溃呢? 语法如下:

try{// 可能抛出异常的语句char ch2 = str.at(100)
}catch(exceptionType variable){// 处理异常的语句cout << "下标越界啦" << endl;return;
}

try 中包含可能会抛出异常的语句,一旦有异常抛出就会被后面的 catch 捕获。从 try 的意思可以看出,它只是“检测”语句块有没有异常,如果没有发生异常,它就“检测”不到。catch 是“抓住”的意思,用来捕获并处理 try 检测到的异常;如果 try 语句块没有检测到异常(没有异常抛出),那么就不会执行 catch 中的语句。

修改代码:

int main(){string str = "http://c.biancheng.net";try{char ch1 = str[100];cout<<ch1<<endl;}catch(exception e){cout<<"[1]out of bound!"<<endl;}try{char ch2 = str.at(100);cout<<ch2<<endl;}catch(exception &e){  //exception类位于<exception>头文件中cout<<"[2]out of bound!"<<endl;}
}

此时只会输出[2]这个数组越界, 第一个try依然没有捕获到异常,这是因为, []不会检查下标越界,不会抛出异常,即使有错误,try也检测不到。所以发生异常的时候,必须明确将其抛出,try才能检测到,如果不抛出,即使异常try检测不到。所谓抛出异常,就是告诉程序发生了什么错误。

第二个try检测到了异常,交给catch处理,但注意,一旦异常抛出,try检测到,就不会在执行异常点后面的语句了,直接跳到catch里面执行。

所以,先明确异常的处理流程: 抛出(Throw) --> 检测(Try) --> 捕获(Catch)

看一个终极例子:

void func_inner(){throw "Unknown Exception";  //抛出异常cout<<"[1]This statement will not be executed."<<endl;
}
void func_outer(){func_inner();cout<<"[2]This statement will not be executed."<<endl;
}
int main(){try{func_outer();cout<<"[3]This statement will not be executed."<<endl;}catch(const char* &e){cout<<e<<endl;  // Unknown Exception}return 0;
}

异常可以发生在当前的 try 块中,也可以发生在 try 块所调用的某个函数中,或者是所调用的函数又调用了另外的一个函数,这个另外的函数中发生了异常。这些异常,都可以被 try 检测到。

发生异常后,程序的执行流会沿着函数的调用链往前回退,直到遇见 try 才停止。在这个回退过程中,调用链中剩下的代码(所有函数中未被执行的代码)都会被跳过,没有执行的机会了。

3. C++异常类型及多级catch匹配

3.1 异常类型

try-catch语法里面,catch捕获异常的时候:

try{// 可能抛出异常的语句
}catch(exceptionType variable){// 处理异常的语句
}

这里有个exceptionType是异常类型,指明了当前的catch可以处理什么类型的异常; variable是一个变量,用来接收异常信息。 当程序抛出异常的时候,会创建一份数据,这份数据包含了错误信息,我们可以根据这些信息判断到底出了什么问题,怎么处理。

异常既然是一份数据,就应该有数据类型。 C++规定,异常类型可以是int,char,float, bool等基本类型,也可以是指针,数组,字符串,结构体,类等聚合类型。C++ 语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的异常。也就是说,抛出异常时,会创建一个 exception 类或其子类的对象

exceptionType variable和函数的形参非常类似,异常发生后,会将异常数据传递给variable这个变量, 和函数传参很类似。 只有跟exceptionType类型匹配的异常数据才会被传递给variable,否则catch不会接收这份异常数据,也不会执行catch块中的语句。

可以将catch看做一个没有返回值的函数,当异常发生后catch会被调用,并且会接收实参, 但是catch和真正的函数调用又有区别:

  • 真正的函数调用,形参和实参的类型必须要匹配,或者可以自动转换,否则编译阶段就报错
  • catch,异常是在运行阶段产生,可以是任何类型,所以没法提前预测, 不能在编译阶段判断类型是否正确,只有等程序运行后,真抛出异常了,再将异常类型和catch能处理的类型进行匹配,如果匹配成功,就调用当前catch,否则,忽略当前catch。
  • catch和真正的函数调用相比,多了一个运行阶段实参和形参匹配的过程。

如果不希望catch处理异常数据,也可以把variable省略:

try{// 可能抛出异常的语句
}catch(exceptionType){// 处理异常的语句
}

3.2 多级catch

一个try后面可以跟多个catch:

try{//可能抛出异常的语句
}catch (exception_type_1 e){//处理异常的语句
}catch (exception_type_2 e){//处理异常的语句
}
//其他的catch
catch (exception_type_n e){//处理异常的语句
}

当异常发生时,程序按照从上到下的顺序, 将异常类型和catch所能接收的类型逐个匹配。一旦找到类型匹配的catch就停止检索,并将异常交给当前的catch处理。如果最终也没有找到匹配的catch,就终止程序运行。

class Base{ };
class Derived: public Base{ };
int main(){try{throw Derived();  //抛出自己的异常类型,实际上是创建一个Derived类型的匿名对象cout<<"This statement will not be executed."<<endl;}catch(int){cout<<"Exception type: int"<<endl;}catch(char *){cout<<"Exception type: cahr *"<<endl;}catch(Base){  //匹配成功(向上转型)cout<<"Exception type: Base"<<endl;}catch(Derived){cout<<"Exception type: Derived"<<endl;}return 0;
}

在catch中,只给出了异常类型,没有给出接收异常信息的变量。这个最终输出, Exception type:Base。 异常类型是Derived的, 但catch在匹配类型时发生了向上转型,让catch(Base)捕获了。

3.3 catch匹配过程中的类型转换

C/C++存在很多种类型转换,普通函数的话,如果实参和形参的类型不是严格匹配,会将实参的类型进行适当转换,以适应形参类型,主要包括:

  • 算数转换: int->float, char->int, double->int
  • 向上转型: 派生类向基类转换
  • 数组或函数指针转换: 如果函数形参不是引用类型,那么数组名会转为数组指针,函数名也会转为函数指针
  • 用户自定义转换等

catch匹配异常过程中,也会进行类型转换,但仅能向上转型,const转换,数组或函数指针转换,其他都不能用于catch。 like this:

int main(){int nums[] = {1, 2, 3};try{throw nums;cout<<"This statement will not be executed."<<endl;}catch(const int *){cout<<"Exception type: const int *"<<endl;}return 0;
}

nums本来是int[3], 但catch中没有严格匹配类型,所以先转成int*, 再转成const int *

4. C++的throw关键字

C++异常处理的流程: 抛出(Throw) -> 检测(Try) -> 捕获(Catch) , 异常必须显式的抛出,才能被检测和捕获。

C++中, 使用throw关键字显式抛出异常,用法:

throw exceptionData;

下面通过一个动态数组的典型异常应用场景来深入了解下,详细细节参考第一个链接文档:

// 自定义的异常类型
class OutOfRange{
public:OutOfRange(): m_flag(1){};OutOfRange(int len, int index): m_len(len), m_index(index), m_flag(2){}void what() const;     // 获取具体的错误信息
private:int m_flag;    // 不同flag表示不同的错误int m_index;   // 当前数组的长度int m_len;     // 当前使用的数组下标
};
void OutOfRange::what() const{if (m_flag == 1){cout << "Error: empty array, no elements to pop. " << endl;}else if(m_flag == 2){cout << "Error: out of range( array length " << m_len << ", access index " << m_index << " )" << endl;}else{cout << "Unkonwn exception. " << endl;}
}// 实现动态数组
class Array{
public:Array();~Array(){free(m_p);}int operator[](int i) const;  // 获取数组元素int push(int ele);       // 在末尾插入数组元素int pop();              // 末尾删除数组元素int length() const{return m_len;}   // 获取数组长度
private:int m_len;  // 获取数组长度int m_capacity;   // 当前内存能容纳多少个元素int *m_p;     // 内存指针static const int m_stepSize = 50;   // 每次扩容步长
};Array::Array(){m_p = (int*)malloc(sizeof(int) * m_stepSize);m_capacity = m_stepSize;m_len = 0;
}int Array::operator[](int index) const{if (index < 0 || index >= m_len){throw OutOfRange(m_len, index);   // index不合法,抛出异常2 }return *(m_p + index);
}int Array::push(int ele){if (m_len >= m_capacity){  // 如果容量不足就扩容m_capacity += m_stepSize;m_p = (int*)realloc(m_p, sizeof(int) * m_capacity);  // 扩容}*(m_p+m_len) = ele;m_len++;return m_len - 1;
}int Array::pop(){if (m_len == 0){throw OutOfRange();   // 抛出异常}m_len--;return *(m_p + m_len);
}// 打印数组元素
void printArray(Array &arr){int len = arr.length();if (len == 0){cout << "Empty array! No elements to print. " << endl;return;}for (int i=0; i<len; i++){if (i == len-1){cout << arr[i] << endl;}else{cout << arr[i] << ", ";}}
}int main()
{Array nums;// 向数组中加十个元素for (int i=0; i<10; i++){nums.push(i);}printArray(nums);  // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9// 尝试访问第20个元素try{cout <<nums[20] << endl;}catch(OutOfRange &e){e.what();  // Error: out of range( array length 10, access index 20 )}// 尝试弹出20个元素try{for (int i=0; i<20; i++){nums.pop();}}catch(OutOfRange &e){e.what();  // Error: empty array, no elements to pop. }printArray(nums); // Empty array! No elements to print. return 0;
}

另外, throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范(Exception specification)

double func (char param) throw (int);

这条语句声明了一个名为 func 的函数,它的返回值类型为 double,有一个 char 类型的参数,并且只能抛出 int 类型的异常。如果抛出其他类型的异常,try 将无法捕获,只能终止程序。

但由于C++11不再建议使用了,所以这里就不整理了,关于异常规范的具体细节,参考第一篇链接。

5. C++的exception类

C++语言本身或标准库抛出的异常都是exception子类,称为标准异常,可以通过下面语句捕获:

try{// 可能抛出异常的句子
}catch(exception &e){   // 引用是为了提高效率,如果不使用引用,就要经历一次对象拷贝// 处理异常的语句
}

exception类位于头文件

class exception{
public:exception () throw();  //构造函数exception (const exception&) throw();  //拷贝构造函数exception& operator= (const exception&) throw();  //运算符重载virtual ~exception() throw();  //虚析构函数virtual const char* what() const throw();  //虚函数
}

what() 函数返回一个能识别异常的字符串,正如它的名字“what”一样,可以粗略地告诉你这是什么异常。

关于具体的exception继承体系,参考这里叭, 等具体用到再进行补充这块。

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

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

相关文章

复试 || 就业day09(2024.01.04)算法篇

文章目录 前言验证外星语词典在长度 2N 的数组中找出重复 N 次的元素找到小镇的法官查找共用字符数组的相对排序分发饼干分发糖果区间选点(AcWing)最大不相交区间数量(AcWing)无重叠区间关于重写小于号 前言 &#x1f4ab;你好&#xff0c;我是辰chen&#xff0c;本文旨在准备考…

国内SAP代理公司排行榜,哲讯为您选择最佳合作伙伴

SAP系统在企业数字化转型中的重要性日益凸显&#xff0c;越来越多的企业意识到了SAP的价值和潜力。然而&#xff0c;在选择合适的SAP代理公司方面&#xff0c;许多企业常常感到困惑。因此&#xff0c;本文将为您介绍国内SAP代理公司排行榜&#xff0c;为您提供参考和指导&#…

以 Serverfull 方式运行无服务器服务

当前 IT 架构中最流行的用例是从 Serverfull 转向 Serverless 设计。在某些情况下&#xff0c;我们可能需要以 Serverfull 方式设计服务或迁移到 Serverfull 作为运营成本的一部分。 在本文中&#xff0c;我们将展示如何将 Kumologica flow 作为 Docker 容器运行。通常&#x…

相机FOV是什么英文单词的缩写,是什么意思。

问题描述&#xff1a;相机FOV是什么英文单词的缩写&#xff0c;是什么意思。 问题解答&#xff1a; FOV 是 "Field of View" 的缩写&#xff0c;翻译成中文是视场角或视野。在相机领域&#xff0c;相机的 FOV 表示相机能够捕捉到的场景范围的大小&#xff0c;通常用…

HarmonyOS页面和自定义组件生命周期

页面和自定义组件生命周期 在开始之前&#xff0c;我们先明确自定义组件和页面的关系&#xff1a; 自定义组件&#xff1a;Component装饰的UI单元&#xff0c;可以组合多个系统组件实现UI的复用。页面&#xff1a;即应用的UI页面。可以由一个或者多个自定义组件组成&#xff…

通信管理之设备管理

点击标题栏 【系统】--【通信管理】 串口通信指串口按位&#xff08;bit)发送和接受字节。尽管比特字节的串行通信慢&#xff0c;但是串口可以在使用一根线发送数据的同时用另一根线接受数据&#xff0c;串口通信协议是指规定了数据包的内容&#xff0c;内容包含了起始位、主体…

52、全连接 - 特征与样本空间的对应关系

上一节说到经过全连接层之后,神经网络学习到的特征,会从隐层特征空间逐步映射到样本空间,这主要是由于全连接层可以融合全局的特征。 在经过全连接层之后,在 ResNet50 这个神经网络中会输出1000个特征的得分值,这1000个特征的得分值,便可以对应到图像的分类。 怎么对应…

居然在Web上就可以体验下苹果电脑的操作系统啦?

发现一款宝藏项目 MacOS &#xff0c;在Web上打造一款原汁原味的 MacOS系统&#xff0c;不同于以外的仿操作系统的web应用&#xff0c;该应用底层基于 HTML5的 FileSystem 和 IndexedDB 构建了文件系统&#xff0c;理论上可以基于这套系统实现任何的上层应用。作者还制定了可以…

洛谷P1024[NOIP2001 提高组] 一元三次方程求解(cpp)(二分查找)

目录 1.题目 2.思路 3.AC 1.题目 # [NOIP2001 提高组] 一元三次方程求解 ## 题目描述 有形如&#xff1a; 这样的一个一元三次方程。给出该方程中各项的系数&#xff08;a,b,c,d 均为实数&#xff09;&#xff0c;并约定该方程存在三个不同实根&#xff08;根的范围在 -…

【JavaEE进阶】 关于Spring mvc 响应

文章目录 &#x1f38d;序言&#x1f333; 返回静态⻚⾯&#x1f332;RestController 与 Controller 的关联和区别&#x1f334;返回数据 ResponseBody&#x1f38b;返回HTML代码⽚段&#x1f343;返回JSON&#x1f340;设置状态码&#x1f384;设置Header&#x1f6a9;设置Con…

Python循环语句

for 循环 for循环主要用来实现固定次数的循环&#xff0c;用于将一段代码重复的执行固定次数。 比如&#xff1a;循环打印数字&#xff0c;打印1-100之间的每个整数 for i in range(100):print(i1)while 循环 while循环用于实现不知道要执行多少次的循环&#xff0c;一般需…

【hyperledger-fabric】部署和安装

简介 对hyperledger-fabric进行安装&#xff0c;话不多说&#xff0c;直接开干。但是需要申明一点&#xff0c;也就是本文章全程是开着加速器进行的资源操作&#xff0c;所以对于没有开加速器的情况可能会由于网络原因导致下载资源失败。 资料提供 1.官方部署文档在此&#…

PyTorch|transforms

在将图片输入到神经网络进行训练时&#xff0c;一般都需要对输入的图像进行预处理。对图片进行操作有很多种方法&#xff0c;这里我们使用torchvision库的transforms模块。 tansforms有很多种方法(一些可以用在张量和PIL图像&#xff0c;一些仅能用于张量&#xff0c;而另一些…

解密!电梯机房温差之谜

小伍&#xff1a;大家好&#xff0c;本次小伍带大家来到【电梯机房】&#xff0c;我们来先测一下温度 电梯机房【外屋】&#xff1a;23.2 度 小伍&#xff1a;好&#xff0c;我们再看里面的设备温度 电梯机房【外里】&#xff1a;74 度 523能源&#xff1a;哇塞&#xff0c;…

写了个在线 SQL 转换工具,支持 Oracle、Mysql、SQLServer 语句互转。

原本用户公司要迁移 oracle 到 mysql 上&#xff0c;数据库方言上有一定的区别&#xff0c;老的 SQL 又臭又长转起来也不太方便&#xff0c;尤其是日期类的完全无法适用&#xff0c;所以才写了这个工具&#xff1a;不同类型sql互转在线工具-开发者工具 可以用于不同数据库之间的…

XML解析神器:Apache Commons Digester

第1章&#xff1a;引言 大家好&#xff0c;我是小黑。今天咱们聊聊一个在现代编程中经常遇到的话题&#xff1a;XML解析。你可能知道&#xff0c;XML&#xff08;可扩展标记语言&#xff09;因其灵活性和可读性&#xff0c;在配置文件、数据交换等方面广泛使用。但是&#xff…

第一节 初始化项目

系列文章目录 第一节 初始化项目 文章目录 操作步骤 总结 操作步骤 打开cmd 输入 vue ui 在打开的网页中点击“创建”&#xff0c;复制文件夹路径并粘贴点击“在此创建新项目” 输入项目名称 点击下一步选择手动配置 选择babel、router、vuex、css pre-processors、 linter建…

(Linux)虚拟机配置固定IP

Linux操作系统的IP地址是通过DHCP服务获取的&#xff0c;也就是动态获取IP地址&#xff0c;每次重启设备后都会获取一次&#xff0c;会导致IP地址频繁变更&#xff0c;为了不频繁更新映射关系&#xff0c;我们需要IP地址固定下来。 1.在VM中配置IP地址网关和网段 打开虚拟网络…

学生成绩管理系统半成品

C语言的老师在给我们讲指针的时候&#xff0c;讲的并不深入&#xff0c;她用了一个学生成绩管理系统来引入指针这个东西并给我们讲解&#xff0c;但我觉得她的管理系统功能有一些不足&#xff0c;并且不是很美观&#xff0c;所以说心血来潮&#xff0c;自己也动手写了一个学生成…

toRefs的用法

文章目录 toRefs是什么toRefs的作用以及为什么要用它&#xff1f; toRefs是什么 toRefs 是 Vue 3 Composition API 中的一个函数&#xff0c;它用于将响应式对象转换为普通对象&#xff0c;其中对象的每个属性都是 ref 对象。这是因为在 Vue 3 中&#xff0c;reactive 创建的对…