【C++】:错误处理机制 -- 异常

news2024/9/21 4:25:40

目录

  • 前言
  • 一,C语言传统的处理错误的方式
  • 二,C++异常的概念
  • 三,异常的使用
    • 3.1 异常的抛出和匹配原则
    • 3.2 在函数调用链中异常栈展开匹配原则
    • 3.3 异常的重新抛出
    • 3.4 异常规范
  • 四,自定义异常体系
  • 五,异常的优缺点

点击跳转至文章: 【C++11】:lambda表达式&function包装器

前言

前面我们学习了C++11新增的几个常用新语法,本篇文章的重点是理解并学会使用C++中新设计的错误处理方式–异常,并且知道它的优缺点。

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

传统的错误处理机制:

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

实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误

代码示例如下:

int main()
{
	FILE* fout = fopen("Test.txt", "r");
	
	cout << fout << endl; // 打开失败返回空
	cout << errno << endl; // 返回错误码
	
	perror("fopen fail"); // 根据错误码打印错误信息

	return 0;
}

在这里插入图片描述

二,C++异常的概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误

  • throw当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。

  • catch:在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常,可以有多个catch进行捕获

  • trytry 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块

如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码

三,异常的使用

3.1 异常的抛出和匹配原则

(1) 抛出的类型和捕获的类型要严格一致

如果你在throw时抛出一个字符串,但是在catch捕获异常时的参数却写的是整数,那么这个抛出的异常就不会去这个catch,会继续匹配下面的catch

try{
	int a,b;
	cin>>a>>b;
	if(b == 0)
		throw "除0错误"
	cout<<(a/b)<<endl;
}
catch(int flag)//类型与throw的不匹配,会跳到下面的catch
{
	//......
}
catch(string str)
{
	//......
}

(2) 异常的抛出和捕获遵循就近原则

选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个

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;
	try
	{
		cout << Division(len, time) << endl;
	}
	catch (const char* errmsg)
	{
		//这个catch与throw相距最近,会优先到这儿
		cout << errmsg << "->111" << endl;
	}
}

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

在这里插入图片描述

(3) catch(…)可以捕获任意类型的异常(未知异常)

当抛出类型与捕获类型不匹配时如果有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 string& s)
	{
		cout << s << endl;
	}
	catch (...) 
	{
		cout << "unkown exception" << endl;
	}

	return 0;
}
 

在这里插入图片描述

(4) 抛出异常对象后,会生成一个异常对象的拷贝

因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		string s("Division by zero condition!");
		throw s;
	}
	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 string& s)
	{
		cout << s << endl;
	}
	catch (...) 
	{
		cout << "unkown exception" << endl;
	}

	return 0;
}

(5) 基类可以接受抛出的子类对象

实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个

3.2 在函数调用链中异常栈展开匹配原则

(1) 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理

(2) 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中(走完当前函数,回退到调用它的函数中)进行查找匹配的catch

(3) 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。

(4) 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行

在这里插入图片描述

3.3 异常的重新抛出

为什么在下面的func函数中还要进行异常的重新抛出呢

因为在这种情况下,如果Division函数发生异常并且抛出异常,就会被catch (…)捕获,释放掉new出的空间,如果Division函数正常执行,就会执行下面的语句,也能释放掉new出的空间。这样就避免了内存泄漏

但是如果在func函数中没有异常重新抛出,那么这样的程序是十分危险的,因为如果此时Division函数发生异常并且抛出异常,就会直接跳过下面的delete语句,被main函数中的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];
	try {
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array << endl;
		delete[] array;
		throw; // 异常重新抛出,捕获到什么抛出什么
	}

	// ...
	cout << "delete []" << array << endl;
	delete[] array;
}

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

	return 0;
}

3.4 异常规范

(1) 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型。

(2) 函数的后面接throw(),表示函数不抛异常

(3) 若无异常接口声明,则此函数可以抛掷任何类型的异常。

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);

// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);

// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();

// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

如果会抛异常但是加上 noexcept,编译时并不会报错,会报警告。运行时如果触发了异常,会直接终止程序,捕获不到异常

四,自定义异常体系

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

在这里插入图片描述

五,异常的优缺点

(1) 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug

(2) 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。

(3) C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。

总结:异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是用异常处理错误,这也可以看出这是大势所趋

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

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

相关文章

经典⾯试题,循环中使⽤闭包解决 var 定义函数的问题

⾸先因为 setTimeout 是个异步函数&#xff0c;所有会先把循环全部执⾏完毕&#xff0c;这时候 i 就是 5了&#xff0c;所以会输出6个 5。 解决办法两种&#xff0c;第一种使用闭包 &#xff1a; 第⼆种就是使用 setTimeout 的第三个参数&#xff1a; 第三种就是使用 let 定义 …

Power功效分析之方差原理及案例教程

Power功效分析常用于实验研究时样本量的计算&#xff08;或功效值计算&#xff09;&#xff0c;实验研究中进行方差分析的情况较多&#xff0c;在SPSSAU中单独将方差放成一个计算Power的方法&#xff0c;其具体包括单因素方差/双因素方差和多因素方差&#xff0c;具体如下表格所…

Callable 与 Runnable:多线程编程中的两大接口对比

Callable 与 Runnable&#xff1a;多线程编程中的两大接口对比 1、主要区别1.1 返回值1.2 使用方式 2、适用场景3、示例 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java多线程编程中&#xff0c;Callable和Runnable是两个核心接口&am…

如何打造 BeatBuddy:一款分析你的 Spotify 数据的 Web 应用

欢迎来到雲闪世界&#xff01;我将解释我是如何构建 BeatBuddy 的&#xff0c;这是一款分析您在 Spotify 上收听内容的网络应用。受 Spotify Wrapped 的启发&#xff0c;它旨在解读您当前的心情并提供您可以根据该分析进行调整的建议。 如果你不想阅读所有内容&#xff0c;只想…

未授权访问漏洞系列

环境 1.此漏洞需要靶场vulhub&#xff0c;可自行前往gethub下载 2.需要虚拟机或云服务器等linux系统&#xff0c;并在此系统安装docker和docker-compose提供环境支持 3.运行docker-compose指令为docker-compose up -d即可运行当前目录下的文件 Redis未授权访问漏洞 一、进…

流程挖掘,为光伏企业重塑确定的竞争力

2023年12月15日&#xff0c;2023中资光伏出海大会在江苏南京隆重举行。国内领先的流程挖掘服务商望繁信科技应邀出席大会&#xff0c;与来自海内外的重要领导、重磅嘉宾、行业大咖齐聚一堂&#xff0c;聚焦“数字化、全球化、本地化”&#xff0c;共同探讨中资光伏企业的出海机…

windows11 DNS手动配置过DNS,在恢复成自动获取后,无法自动获取到DNS,网卡里面DNS还是显示之前手动配置的DNS

windows11 DNS一开始手动配置过DNS,然后在恢复成自动获取后&#xff0c;网卡无法自动获取到DNS&#xff0c;并且网卡里面DNS显示还是之前手动配置的DNS。 系统版本&#xff1a;windows11 企业版、版&#xff1a;10.0.22621 版本 22621 解决办法&#xff1a; 注册表“HKEY_LO…

RHEL9网络设定及网络脚本

1. 添加一张网卡 2. 重写一个网卡配置文件 [rootlocalhost ~]# cd /etc/NetworkManager/system-connections/ [rootlocalhost system-connections]# ls ens160.nmconnection [rootlocalhost system-connections]# vim ens224.connection [rootlocalhost system-connections…

大模型应用(六)如何写好一个prompt,理论+实践

前言 设定是四个基本要素之一&#xff0c;没有一个好的prompt&#xff0c;就绝对不可能有一个好的agent。 如何写prompt的大部分是我粘的&#xff0c;实在是懒得写了。不感兴趣可以直接跳实战。 什么是prompt 大语言模型&#xff08;LLM&#xff09;的能力并不是被设计出来…

【数据结构与算法】迷宫求解------回溯法

回溯法 一.迷宫求解算法二.二维数组表示地图1.地图2.初始化地图3.地图的打印 三.进入迷宫四.栈的实现五.迷宫内探1.首先判断我们的入口2.入栈做标记3.开始探险4.出口判断5.能否下一步6.做标记7.不能下一步 六.运行结果 一.迷宫求解算法 当我们想要找到迷宫的出口,那我们在计算…

为什么网站要使用HTTPS访问

网站使用HTTPS访问的原因有很多&#xff0c;主要可以归纳为以下几个关键点&#xff1a; 1、数据安全性&#xff1a;HTTPS使用SSL/TLS协议对通信过程进行加密&#xff0c;确保信息在传输过程中不被窃取、篡改或冒充。对于涉及敏感信息&#xff08;如个人身份、信用卡号等&#x…

AnyGPT: Unified Multimodal LLM with Discrete Sequence Modeling

发表时间&#xff1a;arXiv 2024年2月26日 论文链接&#xff1a;https://arxiv.org/pdf/2402.12226 作者单位&#xff1a; Fudan University Motivation&#xff1a; LLM 在理解和生成人类语言方面表现出非凡的能力。但是&#xff0c;LLM 的能力仅限于针对文本的处理。而现实…

JVM系列 | 对象的消亡2——HotSpot的设计细节

HotSpot 的细节实现 文章目录 HotSpot 的细节实现OopMap 与 根节点枚举根节点类型及说明HotSpot中的实现 OopMap 与 安全点安全点介绍如何保证程序在安全点上&#xff1f; 安全区域记忆集与卡表记忆集卡表 写屏障并发的可达性分析&#xff08;与用户线程&#xff09;并发可达性…

Spring boot框架指南

1. Spring Boot 概述 1.1 定义与起源 Spring Boot是一种基于Spring框架的开源框架&#xff0c;旨在简化Spring应用程序的创建和开发过程。它通过提供一系列默认配置和自动配置功能&#xff0c;减少了开发者在配置上的工作量&#xff0c;使得快速搭建生产级别的Spring应用程序…

OV SSL证书优势及获取渠道

OV证书&#xff0c;即组织验证型SSL证书&#xff0c;通过严格的组织审查流程&#xff0c;为网站提供数据传输加密、身份验证和信息完整性保护。 OV证书优势 1 高信任度 OV证书通过证书颁发机构&#xff08;CA&#xff09;对企业实名认证&#xff0c;包括企业名称、注册地址、…

万能门店小程序开发平台功能源码系统 带完整的安装代码包以及安装搭建教程

互联网技术的迅猛发展和用户对于便捷性需求的不断提高&#xff0c;小程序以其轻量、快捷、无需安装的特点&#xff0c;成为了众多商家和开发者关注的焦点。为满足广大商家对于门店线上化、智能化管理的需求&#xff0c;小编给大家分享一款“万能门店小程序开发平台功能源码系统…

kernel32.dll丢失?那么kernel32.dll如何修复?教你几种修复丢失kernel32.dll错误的方法

在使用电脑时你是否遇到过kernel32.dll丢失的情况&#xff0c;那么遇到这种情况应该如何解决呢&#xff1f;遇到kernel32.dll丢失就会导致电脑无法正常运行&#xff0c;应用程序也会无法正常使用&#xff0c;今天就教大家kernel32.dll丢失的解决办法。 几种解决kernel32.dll丢失…

破解USB设备通讯协议实现自定义软件控制的步骤与方法

在设备和计算机之间通过USB进行通讯的情况下&#xff0c;厂家提供的软件可以控制设备&#xff0c;但没有提供任何其他资料和支持&#xff0c;这种情况下&#xff0c;若希望自行开发软件来实现同样的功能&#xff0c;可以通过以下步骤破解通讯协议并开发自定义程序。 1. 捕获US…

干货!如何选择Ai大模型(LLMs)?

过去一年里&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在人工智能界风起云涌&#xff0c;纷纷以突破性的进步拓展生成式人工智能的可能性。新模型层出不穷&#xff0c;令人目不暇接。 这些模型依靠日益增长的参数数量和庞大的数据集进行训练&#xff0c;显著提升了…

CentOS版本的Linux系统误删了自带的python和yum,恢复过程

文章借鉴于&#xff1a;Centos误删自带python2.7恢复方法_centos默认的被卸载了-CSDN博客 在进行别的操作的时候&#xff0c;一不小心将我的系统自带的Python2.7.5和yum删除掉了。 后来我尝试重新安装yum&#xff0c;但是安装yum必须要有python。 我又去重新安装了python&am…