【C++进阶9】异常

在这里插入图片描述

一、C语言传统的处理错误的方式

  1. 终止程序,如assert
    如发生内存错误,除0错误时就会终止程序
  2. 返回错误码
    需要程序员自己去查找对应的错误
    z如系统的很多库的接口函数都是通
    过把错误码放到errno中,表示错误

二、C++异常概念

异常:函数无法处理的错误就可以抛出异常
让函数的直接或间接的调用者处理这个错误

  • throw: 出现问题时,程序抛出一个异常
    通过使用 throw 关键字来完成
  • catch: 在您想要处理问题的地方
    通过异常处理程序捕获异常 .catch 关键字用于捕获异常
    可以有多个catch进行捕获
  • try: try 块中的代码标识将被激活的特定异常
    它后面通常跟着一个或多个 catch 块

使用方法

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}
void Func()
{
	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* str) // catch 得跟"Division by zero condition!"类型匹配
	{
		cout << str << endl; // Division by zero condition!
	}

	return 0;
}

2.1 异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码
  2. 选中的处理代码是调用链中与该对象类型匹配离抛出异常位置最近的那一个
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,
    所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁
    (这里的处理类似于函数的传值返回)
  4. catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么
  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用

正常的异常抛出包含
错误码和错误描述

class Exception
{
public:
	Exception(int errid, const string& msg)
		: _errid(errid)
		, _errmsg(msg)
	{}

	const string& GetMsg() const // 引用返回,成员变量出了作用域还在
	{
		return _errmsg;
	}

	int GetErrid() const
	{
		return _errid;
	}

private:
	int _errid; // 错误码
	string _errmsg; // 错误描述
};

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		// Exception err(1, "除0错误");
		// throw err;
		throw Exception(1, "除0错误"); // 用匿名对象,连续的构造+拷贝编译器会优化
	}
	else
	{
		return ((double)a / (double)b);
	}
}

void Func()
{
	int len, time;
	cin >> len >> time;
	try {
		cout << Division(len, time) << endl;
	}
	catch (char str)
	{
		cout << str << endl;
	}

	cout << "void Func()" << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const Exception& e) 
	{
		cout << e.GetMsg() << endl;
	}
	catch (...) // 捕获任意类型的异常,一般放到最后,防止有些异常没捕获到,导致程序终止
	{
		cout << "未知异常" << endl; 
	}

	return 0;
}

一个公司里面每个人对抛异常的需求不一样
有些人可能只需要一个错误码
有些人则需要更详细的信息

解决方法: 可以搞一个继承体系
比如你是缓存部分我是网络部分
可以写一个子类继承父类Exception
捕获的时候捕两个部分
一个Exception
一个是未知异常
C++很灵活的地方抛子类捕父类

三、自定义异常体系

实际使用中很多公司
都会自定义自己的异常体系
进行规范的异常管理
因为一个项目
如果大家随意抛异常
那么外层的调用者基本没法玩
所以实际中会定义一套继承的规范体系
这样大家抛出的都是继承的派生类对象
捕获一个基类就可以了
在这里插入图片描述
服务器开发中通常使用的异常继承体系

class Exception
{
public:
   Exception(int errid, const string& msg)
   	: _errid(errid)
   	, _errmsg(msg)
   {}

   virtual string what() const // 引用返回,成员变量出了作用域还在
   {
   	return _errmsg;
   }

   int GetErrid() const
   {
   	return _errid;
   }

protected: // 子类可见用保护
   int _errid; // 错误码
   string _errmsg; // 错误描述
};

class SqlException : Exception // sql专属继承
{
public:
   SqlException(int errid, const string& msg, const string& sql) // 派生类必须调用父类的构造函数,派生类的特点必须得去复用父类
   	: Exception(errid, msg)
   	, _sql(sql)
   {}

   virtual string what() const // exception写成虚函数进行重写,构造需要的错误信息进行返回
   {
   	string msg = "SqlException:";
   	msg += _errmsg;
   	msg += "->";
   	msg += _sql;

   	return msg;
   }

protected:
   string _sql;
};

// 出现数据库错误或网络错误
class CacheException : public Exception
{
public:
   CacheException(const string& errmsg, int id)
   	:Exception(id, errmsg)
   {}
   
   virtual string what() const
   {
   	string msg = "CacheException:";
   	msg += _errmsg;

   	return msg;
   }
};

class HttpServerException : public Exception
{
public:
   HttpServerException(const string& errmsg, int id, const string& type) // 外加一个类型错误
   	:Exception(id, errmsg)
   	, _type(type)
   {}
   
   virtual string what() const
   {
   	string msg = "HttpServerException:";
   	msg += _errmsg;
   	msg += "->";
   	msg += _type; 

   	return msg;
   }

private:
   const string _type;
};

void SQLMgr() // 数据库
{
   srand(time(0));
   if (rand() % 7 == 0)
   {
   	throw SqlException(100, "权限不足", "select * from name = '张三'");
   }
   cout << "调用成功" << endl; // 3.数据库出错抛异常,数据库没出错,调用成功.数据库取到数据进行返回 
}

void CacheMgr() // 缓存
{
   srand(time(0));
   if (rand() % 5 == 0)
   {
   	throw CacheException("权限不足", 100);
   }
   else if (rand() % 6 == 0)
   {
   	throw CacheException("数据不存在", 101);
   }
   SQLMgr(); // 2.缓存出错抛异常,没出错调用数据库
}

void HttpServer() // 网络
{
   // 模拟服务出错
   srand(time(0));
   if (rand() % 3 == 0)
   {
   	throw HttpServerException("请求资源不存在", 100, "get");
   }
   else if (rand() % 4 == 0)
   {
   	throw HttpServerException("权限不足", 101, "post");
   }
   CacheMgr(); // 1.网络出错抛异常,没出错调用缓存
}

int main()
{
   while (1)
   {
   	this_thread::sleep_for(chrono::seconds(1));
   	try {
   		HttpServer();
   	}

   	 //catch (const HttpServerException& e) // 当子类和父类同时出现,按顺序走,优先匹配位置近的
   	 //{
     //	cout << "子类";
     //	cout << e.what() << endl;
     //}

   	catch (const Exception& e) // 这里捕获父类对象就可以
   	{
   		// 通过多态拿错误信息.多态指向子类,通过调用子类拿到错误信息 
   		cout << e.what() << endl; 
   		// e是父类对象,抛的可能是父类,调的就是父类的what.抛的如果是sql,指向的是子类,调what调用的则是子类的what
   	}
   	
   	catch (...)
   	{
   		cout << "Unkown Exception" << endl; // 捕捉未知异常
   	}
   }

   return 0;
}

3.1 异常的重新抛出

有可能单个catch不能完全处理一个异常
在进行一些校正处理以后,希望再交给更外层的调用
链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	return (double)a / (double)b;
}

void Func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* array = new int[10]; // 如果抛异常会发生内存泄漏
		
	int len, time;
	cin >> len >> time;

	try // 所以捕获异常
	{
		cout << Division(len, time) << endl;
	}
	/*catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}*/

	//catch (const char* errmsg)
	//{
	//	cout << "delete []" << array << endl; 
	//	delete[] array;

	//	throw errmsg; // 重新抛出异常,抛之前释放一下
	//}

	catch (...) // 各种抛错异常,需要不同的捕获方法,用...便可一劳永逸
	{
		cout << "delete []" << array << endl; 
		delete[] array;

		throw; // 异常的重新抛出,捕到什么抛什么

	}

	cout << "delete []" << array << endl; // 如果期望在main函数执行异常,且能够继续完成释放,这是就可以用异常的重新抛出
	delete[] array;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}

	return 0;
}

需要注意的是:
构造函数完成对象的构造和初始化
不要在构造函数中抛出异常
否则可能导致对象不完整
或没有完全初始化

析构函数主要完成资源的清理
不要在析构函数内抛出异常
否则可能导致资源泄漏

C++中异常经常导致资源泄漏的问题
比如:
在new和delete中抛出异常导致内存泄漏
在lock和unlock之间抛出异常导致死锁
C++经常使用RAII来解决以上问题
而RAII在下一篇博客智能指针
会进行专门讲解

在这里插入图片描述
本篇博客完,感谢阅读🌹
如有错误之处可评论指出
博主会耐心听取每条意见

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

anaconda卸载过程中出现fail to run pre-unistall报错

问题&#xff1a; 在使用Uninstall-Anaconda3.exe卸载程序时&#xff0c;出现报错&#xff1a; 解决方案&#xff1a; 把文件夹移动到C盘用户文件夹后再运行卸载程序。即可正常运行程序。

ping 出现的结果判断

ICMP协议发送包的时候 常见的ping反馈结果&#xff1a; 连接建立成功&#xff0c;Reply from 目标地址 目标主机不可达&#xff0c;Destination host unreachable 直接不能出交换机&#xff0c;到达不了交换机 请求时间超时&#xff0c;Request timed out 服务器到交换机…

一名HR,在招聘嵌入式开发岗位,为什么感觉一年比一年难?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 1.嵌入式学用不一致, 高…

MySQL基础查询与复杂查询

基础查询 1、查询用户信息&#xff0c;仅显示用户的姓名与手机号&#xff0c;用中文显示列名。中文显示姓名列与手机号列。 2、根据商品名称进行模糊查询&#xff0c;模糊查询需要可以走索引&#xff0c;需要给出explain语句。使用explain测试给出的查询语句&#xff0c;需要显…

如何把mkv转成mp4?介绍一下将mkv转成MP4的几种方法

如何把mkv转成mp4&#xff1f;如果你有一个MKV格式的视频文件&#xff0c;但是需要将其转换为MP4格式以便更广泛地在各种设备和平台上播放和共享&#xff0c;你可以通过进行简单的文件格式转换来实现。转换MKV到MP4格式可以提供更好的兼容性&#xff0c;并确保你的视频文件能够…

vue2(vue-cli3x[vue.config.js])使用cesium新版(1.117.0)配置过程

看来很多解决方法都没有办法&#xff0c;最后终于。呜呜呜呜 这里我用的是vue-cli去搭建的项目的vue2 项目&#xff0c;其实不建议用vue2搭配cesium。因为目前cesium停止了对vue2的版本更新&#xff0c;现在默认安装都是vue3版本&#xff0c;因此需要控制版本&#xff0c;否则…

Rockchip RK3588 - Rockchip Linux Recovery rkupdate升级

---------------------------------------------------------------------------------------------------------------------------- 开发板 &#xff1a;ArmSoM-Sige7开发板eMMC &#xff1a;64GBLPDDR4 &#xff1a;8GB 显示屏 &#xff1a;15.6英寸HDMI接口显示屏u-boot &a…

【产品经理】订单处理9-台账库存管理

在订单处理过程中&#xff0c;台账库存的具体设计怎么做&#xff1f; 在订单处理过程中&#xff0c;分配仓库成功后要扣除仓库库存并计算商品缺货情况&#xff0c;仓库库存就是台账库存。 1&#xff0c;台账库存是针对某个仓库的库存&#xff0c;且台账库存只计算此商品SKU的库…

小马搬运物品-第13届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第89讲。 小马搬运物品&…

C#/.NET量化开发实现财富自由【4】实现EMA、MACD技术指标的计算

听说大A又回到了2950点以下&#xff0c;对于量化交易来说&#xff0c;可能这些都不是事儿。例如&#xff0c;你可以预判到大A到顶了&#xff0c;你可能早就跑路了。判断逃顶还是抄底&#xff0c;最简单的方式就是判断是否顶背离还是底背离&#xff0c;例如通过MACD&#xff0c;…

华为5288 V5服务器安装BCLinux8U4手记

本文记录了华为5288 V5服务器安装BCLinux8U4操作系统的过程。 一、系统环境 1、服务器 华为FusionServer Pro 5288 V5服务器 2、操作系统 BCLinux-R8-U4-Server-x86_64-220725.iso 官网下载地址 sha256sum&#xff1a;1d31d3b8e02279e89965bd3bea61f14c65b9d32ad2ab6d4eb…

MySQL数据库基础练习系列——教务管理系统

项目名称与项目简介 教务管理系统是一个旨在帮助学校或教育机构管理教务活动的软件系统。它涵盖了学生信息管理、教师信息管理、课程管理、成绩管理以及相关的报表生成等功能。通过该系统&#xff0c;学校可以更加高效地处理教务数据&#xff0c;提升教学质量和管理水平。 1.…

uniapp获取证书秘钥、Android App备案获取公钥、签名MD5值

一、 uniapp获取证书秘钥 打开uniapp开发者中心下载证书打开cmd输入以下这段代码&#xff0c;下载提供查看到的密钥证书密码就可以了&#xff01;下载证书在 java 环境下运行才可以 // your_alias 换成 证书详情中的别名&#xff0c;your_keystore.keystore 改成自己的证书文件…

炸裂行情,只涨指数难涨票!你的股票涨了吗?

今天的A股&#xff0c;让人愣住了&#xff0c;你知道是为什么吗&#xff1f;盘面上出现2个耐人寻味的重要信号&#xff0c;一起来看看&#xff1a; 1、今天A股冲高回落&#xff0c;很多人愣住了&#xff0c;别慌&#xff01;主力增量在不断增加&#xff0c;7月的一波主线反弹正…

小程序web-view无法打开该页面的解决方法

问题&#xff1a;开发者工具可以正常打开&#xff0c;正式上线版小程序使用 web-view 组件测试时提示&#xff1a;“无法打开该页面&#xff0c;不支持打开 https://xxxxxx&#xff0c;请在“小程序右上角更多->反馈与投诉”中和开发者反馈。” 解决方法&#xff1a;需要配…

计网实训——不相同网段的PC相互通信

目录 提前准备APP路由器指令 实验一1、实验需求&#xff08;1&#xff09;实现同网段的PC相互通信。&#xff08;2&#xff09;实现不相同网段的PC相互通信。&#xff08;3&#xff09;分析相同和不同网段PC通信时MAC地址的变化。 2、实验拓扑3、实验步骤及实验截图&#xff08…

金顺心贸易有限公司简介

金顺心贸易有限公司成立于2015年&#xff0c;注册地位于风景如画的广西壮族自治区防城港市东兴市。 金顺心贸易如他们的名字一样&#xff0c;有着实实在在的业绩和口碑的。他们专注于国际贸易&#xff0c;主营越南进口食品&#xff1a;果汁饮料、春卷皮、调味品、汤底、米粉、…

【LeetCode】九、双指针算法:环形链表检测 + 救生艇

文章目录 1、双指针算法1.1 对撞双指针1.2 快慢双指针 2、leetcode141&#xff1a;环形链表3、leetcode881&#xff1a;救生艇 1、双指针算法 用两个指针来共同解决一个问题&#xff1a; 1.1 对撞双指针 比如先有一个有序的数组array int[] array {1, 4, 5, 7, 9}先要找两个…

# Kafka_深入探秘者(10):kafka 监控

Kafka_深入探秘者&#xff08;10&#xff09;&#xff1a;kafka 监控 一、kafka JMX 1、JMX &#xff1a;全称 Java Managent Extension 在实现 Kafka 监控系统的过程中&#xff0c;首先我们要知道监控的数据从哪来&#xff0c;Kafka 自身提供的监控指标(包括 broker 和主题的…

Zabbix如何帮助企业将监控数据转化为竞争优势

By Fernanda Moraes 在我们生活的高度互联世界中&#xff0c;变化以越来越快和激烈的速度发生。这影响了消费者的认知与行为&#xff0c;迫使零售商寻找更有效的方式来吸引客户。Linx 是 StoneCo 集团旗下的一家公司&#xff0c;也是零售技术专家&#xff0c;Linx了解这一点&am…