【C++】异常处理机制(对运行时错误的处理)

news2024/11/13 6:31:11

75e194dacf184b278fe6cf99c1d32546.jpeg

🌈 个人主页:谁在夜里看海.

🔥 个人专栏:《C++系列》《Linux系列》

⛰️ 天高地阔,欲往观之。

d047c7b1ef574257b8397fe5cc5c290b.gif

目录

引言

1.编译器可以处理的错误

2.编译器不能处理的错误

3.传统的错误处理机制

assert终止程序

返回错误码 

一、异常的概念

直观的例子

二、异常的使用

1.异常的抛出和捕获

抛出异常 

捕获异常

处理异常 

2.异常的重新抛出

三、异常的安全与规范

1.异常安全

2.异常规范

3.自定义异常体系

四、异常的优缺点


引言

我们在编写程序的时候不可避免地会造成一些错误,有些错误是编译器可以帮我们找出并纠正的,而有些错误则需要我们自己自行处理。

1.编译器可以处理的错误

一般是一些静态的语义和语法错误,下面列举一些常见的错误:

// 1.语法错误
int a = 0 // 缺少封号

// 2.类型错误
int b = "hello"; // 类型不匹配

// 3.未定义标识符
int result = unknownFunc(); // 未定义的函数

// 4.作用域错误
if(1){
    int x = 10;
}
cout << x; // 不在当前作用域

// ......

2.编译器不能处理的错误

编译器无法检测和处理的错误主要是程序运行时才会发生的动态错误,例如下面几种:

// 1.运行时错误(除零、非法访问内存、堆栈溢出)
int a = 10;
int b = 0;
int c = a / b;  // 运行时的除零错误

// 2.逻辑错误,逻辑错误是在代码设计或实现上出现的错误,但从语法和类型上都是合法的。
// 逻辑错误只能通过调试和测试来发现。例如,意图将数字从1到10累加,但写错了循环条件:
int sum = 0;
for (int i = 1; i <= 10; ++i) {
    sum -= i;  // 应该是 sum += i
}

// 3.资源管理错误,包括内存泄露等问题,编译器无法检测
int* ptr = new int[10]; // 没有 delete[] ptr;

// ......

总上所述,编译器负责静态检测,只能检查代码中明显的语义和语法错误对于运行时的错误处理则需要我们设置适当的错误处理机制

3.传统的错误处理机制

assert终止程序

assert是C和C++中的一个宏,用于在程序中检查条件是否满足,如果条件不满足,assert会报错并终止程序,可以在错误发生的地方及时发现问题:

int operation()
{
	int a = 0;
	int b = 0;
	cin >> a >> b; // 输入 3 0
	// assert判错
	assert(b); // 此时发现错误,程序终止

	return a / b;
}

int main()
{
    operation();
    return 0;
}

弊端:

在调试过程中,assert还是很有用的,但是在发布版本(Release模式)下会降低性能,因此会将其禁用。通过定义宏 NDEBUG 禁用 assert:

#define NDEBUG
#include <cassert>

而且在一些情况下,使用assert终止程序会发生很严重的错误!

实际上C语言基本是使用返回错误码的方式处理错误:

返回错误码 

在C语言中,函数通常返回一个整数表示执行的结果:

返回0表示执行成功;

返回非零值(整数或负数)表示不同的错误类型,根据错误码,调用者可以判断错误类型 

常见的错误码有以下类型:

  • 标准错误码:使用标准库中定义的宏,如 EXIT_SUCCESSEXIT_FAILURE:
#include <stdlib.h>
#include <stdio.h>

int divide(int a, int b) {
    if (b == 0) {
        return EXIT_FAILURE;  // 返回标准错误码表示失败
    }
    printf("Result: %d\n", a / b);
    return EXIT_SUCCESS;  // 返回标准错误码表示成功
}

int main() {
    if (divide(10, 0) == EXIT_FAILURE) {
        printf("Error: Division by zero.\n");
    }
    return 0;
}
  • 自定义错误码:为程序中的不同错误类型定义特定的错误码。
#include <stdio.h>

#define SUCCESS 0
#define ERR_DIVISION_BY_ZERO -1
#define ERR_INVALID_INPUT -2

int divide(int a, int b, int* result) {
    if (b == 0) {
        return ERR_DIVISION_BY_ZERO;  // 返回自定义错误码
    }
    *result = a / b;
    return SUCCESS;  // 返回成功码
}

int main() {
    int result;
    int status = divide(10, 0, &result);
    
    if (status == ERR_DIVISION_BY_ZERO) {
        printf("Error: Division by zero.\n");
    } else if (status == SUCCESS) {
        printf("Result: %d\n", result);
    }
    return 0;
}

弊端:

每次调用函数都要检查返回值,如果嵌套调用较多,会造成大量错误检查代码,降低代码可读性,我们需要一种更简洁更灵活的错误管理机制,它就是C++异常处理:

一、异常的概念

异常是指程序运行时发生的、使程序无法正确执行的错误或异常情况,而异常处理机制是一种处理异常的手段,它允许程序在遇到问题时转移到异常处理逻辑,而不是直接崩溃。

直观的例子


对异常不处理:

int main()
{
	int a = 0;
	int b = 0;
	cin >> a >> b; // 输入 3 0
	cout << a / b << endl; // 发生除零异常,程序中断
	return 0;
}


使用assert处理:

int main()
{
	int a = 0;
	int b = 0;
	cin >> a >> b;
	//	 assert处理
	assert(b);

	cout << a / b << endl;
	return 0;
}


使用异常处理机制:

int main()
{
	int a = 0;
	int b = 0;
	cin >> a >> b;

	//	 异常处理机制
	try{
		if (b == 0){
			throw "The dividend is zero";
		}
	}
	catch (const char* ret){
		cout << ret << endl;
		return 0; // 遇到除零 结束程序
	}

	cout << a / b << endl;
	return 0;
}

我们可以看到,相比于assert,异常处理可以更直观地显示错误信息,而不是让程序崩溃或无反应

下面介绍异常的使用方法:

二、异常的使用

1.异常的抛出和捕获

抛出异常 

当程序遇到无法正常处理的问题或错误情况时,使用 throw 语句抛出异常。抛出的异常对象可以是一个具体的值(如整数、字符串)或特定的异常类实例:

	int a = 0;
	int b = 0;
	cin >> a >> b;

	// 异常处理机制
	try{
		if (b == 0){
			throw "The dividend is zero";
		}
	}

在这里,如果b == 0(除零错误),则抛出一个 char* 类型的错误信息,提示“除零错误”。

捕获异常

当抛出异常后,程序会自动寻找最接近的 catch来捕获这个异常。在 catch 块中,可以编写处理代码以应对错误。多个 catch 块可以处理不同类型的异常。

int main()
{
	int a = 0;
	int b = 0;
	while (1)
	{
		cin >> a >> b;

		// 异常处理判错
		try {
			if (b < 0) {
				string info = { "The dividend is negative" }; // 除负数(假定为错误)
				throw info;
			}
			if (b == 0){
				throw "The dividend is zero"; // 除零错误
			}
		}
		catch (const string info) { // 捕获string类型的异常
			cout << info << endl;
		}
		catch (const char* info){ // 捕获char*类型的异常
			cout << info << endl;
		}
	}

	return 0;
}

try 块中调用 divide 函数,如果发生异常,程序会跳转到相应的 catch,输出捕获到的异常信息。

处理异常 

catch 块中处理异常的逻辑可以包括打印错误信息、清理资源、执行恢复操作等。这样可以防止程序崩溃,并在必要时继续执行其他代码。 

多类型异常捕获:

 有时候可能需要处理多种类型的异常,这时可以使用多个 catch 块,就像上面那样,也可以使用一个通用的 catch(...) 块来捕获所有异常。

注意:

 catch(...) 可以捕获任意类型的异常,但同时并不能清楚异常的类型,所以一般是放在程序的最后,用于捕获未知异常的,相当于一个保障,并且不能放在其他异常捕获代码的前面,否则会屏蔽其他异常捕获:

2.异常的重新抛出

有时单个的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];
	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;
}

三、异常的安全与规范

1.异常安全

由于异常处理会导致程序执行流程的随意跳转,过多或任意地使用异常可能会导致代码混乱,而且随意抛出异常会有资源泄露的风险,所以下面几种情况最好不要抛出异常:

1. 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化

2. 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内 存泄漏、句柄未关闭等)

3. 在new和delete中最好不要抛出异常,可能会导致内存泄漏

4. 在lock和unlock中最好不要抛出异常,可能会导致死锁

2.异常规范

对异常规格说明,可以让函数使用者知道函数可能抛出的异常有哪些。可以在函数的后面接throw(类型),列出这个函数可能抛出的所有异常类型。如果函数后面接throw(),说明函数不抛出异常,若无说明,则函数可以抛出任意异常:

// 这里表示这个函数会抛出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;

3.自定义异常体系

在实际中,公司会自定义自己的异常体系进行规范的异常管理,通常会定义一套继承的规范体系,这样大家抛出的都是继承的派生类对象,都只要捕获基类对象就可以了(C++允许通过基类对象捕获其派生类对象)

在 C++ 中通常继承 std::exception 类(标准异常类):

#include <exception>
#include <string>
using namespace std;

// 基类对象
class Custom : public exception {
protected:
    string message; // 错误信息
    int errorCode; // 错误码

public:
    Custom=(const =string& msg, int code = 0)
        : message(msg), errorCode(code) {}

    virtual const char* what() const noexcept override {
        return message.c_str();
    }

    int getErrorCode() const { return errorCode; }
};

// 派生类对象
class Database= : public Custom {
public:
    Database(const string& msg, int code = 1001)
        : Custom("Database Error: " + msg, code) {}
};

class Network : public Custom {
public:
    Network(const string& msg, int code = 1002)
        : Custom("Network Error: " + msg, code) {}
};

四、异常的优缺点

异常的优缺点如下:

                                优点                                    缺点
1.清晰准确的展示出错误的各种信息1.导致程序的执行流乱跳,跟踪调试时比较困难
2.直接跳转catch捕获,直接处理错误2.额外的性能开销(可忽略)
3.使用场景更为广泛3.内存泄漏的风险(可结合智能指针解决)
4.需要手动定义标准体系

总结:异常总体还是利大于弊的,在工程中也鼓励使用异常。 


以上就是对异常处理的介绍与个人理解,欢迎指正~ 

码文不易,还请多多关注支持,这是我持续创作的最大动力!

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

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

相关文章

Python基础学习-03逻辑分支语句、循环

目录 1、记住逻辑关系 2、逻辑分支语句 3、for-loop循环 4、while-loop 5、break 和 continue 6、本节总结 1、记住逻辑关系 • 逻辑关系 1&#xff09; True&#xff08;真&#xff09; 和 False&#xff08;假&#xff09; 2&#xff09;逻辑关系有 and&#xff08;与…

【Windows】Android Studio 上cmd 换为Powershell 终端

最近在Windows 环境下Android Studio 的Terminal 终端&#xff0c;低版本默认用的是cmd.exe&#xff0c;好多linux 命令不支持&#xff0c;有时候一不小心就记忆错了&#xff1b;干脆直接换成Windows PowerShell 得了。 下载Powershell&#xff1a;https://aka.ms/PSWindows 选…

javascript实现sha512和sha384算法(支持微信小程序),可分多次计算

概述&#xff1a; 本人前端需要实现sha512和sha384计算的功能&#xff0c;最好是能做到分多次计算。 本文所写的代码在现有sha512和sha384的C代码&#xff0c;反复测试对比计算过程参数&#xff0c;成功改造成sha512和sha384的javascript代码&#xff0c;并成功验证好分多次计算…

Pr 视频过渡:沉浸式视频

效果面板/视频过渡/沉浸式视频 Video Transitions/Immersive Video Adobe Premiere Pro 的视频过渡效果中&#xff0c;沉浸式视频 Immersive Video效果组主要用于 VR 视频剪辑之间的过渡。 自动 VR 属性 Auto VR Properties是所有 VR 视频过渡效果的通用选项。 默认勾选&#x…

Ascend C的编程模型

1 并发执行 Ascend C和cudnn相似&#xff0c;都是一种多核心编程的范式。想要了解Ascend C&#xff0c;必须得先掌握这种“多核”是怎么实现得。 多核执行&#xff0c;说白了就是使用CPU/GPU/Ascend的物理多核并发去执行一段流程&#xff0c;一般情况下&#xff0c;可以通过以…

商品,订单风控业务梳理二

订单风控流程 业务风控系统

苍穹外卖05-Redis相关知识点

目录 什么是Redis&#xff1f; redis中的一些常用指令 value的5种常用数据类型 各种数据类型的特点 Redis中数据操作的常用命令 字符串类型常用命令&#xff1a; 哈希类型常用命令 列表操作命令 集合操作命令 有序集合操作命令 通用命令 在java中操作Redis 环境…

一些面试题总结(一)

1、string为什么是不可变的&#xff0c;有什么好处 原因&#xff1a; 1、因为String类下的value数组是用final修饰的&#xff0c;final保证了value一旦被初始化&#xff0c;就不可改变其引用。 2、此外&#xff0c;value数组的访问权限为 private&#xff0c;同时没有提供方…

3.3 软件需求:面对对象分析模型

面对对象分析模型 1、对象2、面对对象的软件开发模型3、用例图建模基础3.1 用例图基本符号参与者用例系统执行关联 3.2 用例建模过程3.3 用例图初步3.4 用例图进阶关联Association泛化Inheritance包含Include扩展Extend示例 1、对象 在现实世界中有意义的&#xff0c;与所要解…

「C/C++」C++标准库 之 #include<exception> 异常处理库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

嵌入式linux中gpio子系统的开发与实现

大家好,今天主要给大家分享一下,如何使用gpio子系统,来控制对应的引脚电平状态与实现。 第一:linux中gpio子系统描述 gpio0:gpio@fdd60000{compatible = "rockchip,gpio-bank";reg = <0x0 0xfdd60000 0x0 0x100>; interrupts = <GIC_SPI 33 IRQ_TYP…

【主机游戏】艾尔登法环游戏攻略

艾尔登法环&#xff0c;作为一款备受好评但优化问题频发的游戏&#xff0c;就连马斯克都夸过 今天介绍一下这款游戏 https://pan.quark.cn/s/24760186ac0b 角色升级 在《艾尔登法环》中&#xff0c;角色升级需要找到梅琳娜。你可以在关卡前废墟的营地附近&#xff0c;风暴关…

大数据面试题--kafka夺命连环问

1、kafka消息发送的流程&#xff1f; 在消息发送过程中涉及到两个线程&#xff1a;一个是 main 线程和一个 sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给双端队列&#xff0c;sender 线程不断从双端队列 RecordAccumulator 中拉取…

出海企业如何借助云计算平台实现多区域部署?

云计算de小白 如需进一步了解&#xff0c;请单击链接了解有关 Akamai 云计算的更多信息 在本文中我们将告诉大家如何在Linode云计算平台上借助VLAN快速实现多地域部署。 首先我们需要明确一些基本概念和思想&#xff1a; 部署多区域 VLAN 为了在多区域部署中在不同的 VLAN …

W55RP20-EVB-Pico评估板介绍

目录 1 简介 2 硬件资源 2.1 硬件规格 2.2 引脚定义 2.3 工作条件 3 参考资料 3.1 RP2040 数据手册 3.2 原理图 ​编辑 原理图 & 物料清单 & Gerber 文件 3.3 尺寸图&#xff08;单位&#xff1a;mm&#xff09; ​编辑 3.4 认证 3.5 参考例程 4 硬件协…

【机器学习】均方误差根(RMSE:Root Mean Squared Error)

均方误差根&#xff08;Root Mean Squared Error&#xff0c;RMSE&#xff09;是机器学习和统计学中常用的误差度量指标&#xff0c;用于评估预测值与真实值之间的差异。它通常用于回归模型的评价&#xff0c;以衡量模型的预测精度。 RMSE的定义与公式 给定预测值 和实际值 …

《计算机原理与系统结构》学习系列——存储器(上)

系列文章目录 目录 存储器技术概要存储器层次cache&#xff0c;内存辅存存储器技术SRAM技术DRAM技术闪存磁盘存储器 局部性原理 高速缓存cache访存性能概念命中与缺失访存阻塞的周期数 cache基础&#xff1a;直接映射块号内存地址字段缺失缺失处理和写策略 全相联映射组相连映…

python爬虫自动库DrissionPage保存网页快照mhtml/pdf/全局截图/打印机另存pdf

目录 零一、保存网页快照的三种方法二、利用打印机保存pdf的方法 零 最近星球有人问如何使用页面打印功能&#xff0c;另存为pdf 一、保存网页快照的三种方法 解决方案已经放在星球内&#xff1a;https://articles.zsxq.com/id_55mr53xahr9a.html当然也可以看如下代码&…

【Linux】进程概念与PCB,父子进程与foke函数

目录 一、进程概念&#xff1a; 描述&#xff1a; 组织&#xff1a; 二、Linux中的进程管理&#xff1a; 指令&#xff1a;ps ajx 三、父子进程&#xff1a; PID和PPID的调用查看&#xff1a; 四、创建子进程------fork&#xff1a; 一、进程概念&#xff1a; 首先&…

处理PhotoShopCS5和CS6界面字体太小

处理PhotoShop CS6界面字体太小 背景&#xff1a;安装PhotoShop CS6后发现无法调大字体大小&#xff0c;特别是我的笔记本14寸的&#xff0c;显示的字体小到离谱。 百度好多什么降低该电脑分辨率&#xff0c;更改电脑的显示图标大小&#xff0c;或者PS里的首选项中的界面设置。…