C++中的异常处理方式

目录

一、异常

二、C语言中对错误的处理

三、C++中的异常处理

四、异常的抛出和捕获

五、异常的重新抛出

 六、C++标准库中的异常体系

 七、异常的规范


一、异常

        在C++中,异常是程序运行期间发生的意外或错误情况。这些情况可能会导致程序无法继续正常执行,但又不能在当前的代码位置立即处理。异常提供了一种机制,使得在错误发生时可以将控制权从当前代码的执行位置转移到异常处理代码的位置,从而进行适当的处理。

1.异常类型通常是类,可以是标准库中的异常类,也可以是自定义的异常类。异常类通常派生自 std::exception 类或其派生类。通过定义不同类型的异常类,可以让程序区分不同类型的错误情况,并采取相应的处理措施。

2.当程序在执行过程中遇到错误情况时,可以使用 throw 关键字手动抛出异常。throw 后面通常跟着一个异常对象,可以是任何类型的对象,包括基本类型、类、指针等。

3.异常捕获通过 try 和 catch 块来实现。try 块用于包裹可能抛出异常的代码块,而 catch 块用于捕获并处理在 try 块中抛出的异常。catch 块可以捕获特定类型的异常或者所有类型的异常。

4.异常处理是指在程序运行时遇到异常情况时所采取的行动。异常处理的目的是保证程序的稳定性和可靠性,使得程序能够在出错情况下进行适当的恢复或终止。

5.当一个函数中抛出了异常,但该函数没有处理这个异常时,异常将会被传递给调用该函数的上层函数,直到找到相应的异常处理代码为止。如果异常一直没有被处理,最终将导致程序的终止。

        通过异常机制,C++ 提供了一种结构化的方式来处理程序中的错误情况,从而提高了程序的健壮性和可维护性。

二、C语言中对错误的处理

C语言中的处理机制:

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

这两个方法存在不足之处

1. 终止程序,如assert

        当程序遇到错误时,直接终止程序可能会给用户带来不好的体验,特别是对于用户交互型的程序而言。这种突然的终止也不利于程序的稳定性和可维护性。

2. 返回错误码

        需要程序员在每次调用可能出错的函数后手动检查返回值,并查找对应的错误码,这增加了代码的复杂性和出错的可能性。此外,如果程序员忘记检查错误码,可能会导致错误被忽略或未处理,进而引发更严重的问题。同时,错误码通常是整数类型,可能不够精确地描述错误的性质和上下文,导致错误处理不够灵活。

三、C++中的异常处理

在C++中,通常使用三个关键字来处理:

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

我们来看一下下面的例子(除0异常)

#define  _CRT_SECURE_NO_WARNINGS
#include <iostream>
int main() {
    int dividend = 10;
    int divisor = 0;
    float result;

    try {
        if (divisor == 0) {
            throw "除数不能为零";
        }
        result = static_cast<float>(dividend) / divisor;
        std::cout << "以下代码不会被执行" << std::endl;
        std::cout << "结果: " << result << std::endl;
    }
    catch (const char* msg) {
        std::cerr << "异常: " << msg << std::endl;
    }
    return 0;
}

可以看到一旦throw后,会直接跳转到对应的catch,后面的代码不会被执行。

四、异常的抛出和捕获

1. 异常是通过 抛出对象而引发 的,该 对象的类型 决定了应该激活哪个 catch 的处理代码。
// 抛出对象决定匹配的 catch 块
void throwException(int value) {
    if (value < 0) {
        throw "遇到负值!";
    }
    else {
        throw 42;
    }
}

int main() {
    try {
        throwException(-1);
    }
    catch (const char* msg) {
        std::cout << "捕获到带有消息的异常: " << msg << std::endl;
    }
    catch (int num) {
        std::cout << "捕获到带有值的异常: " << num << std::endl;
    }

    return 0;
}

运行结果

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

// 匹配最近的 catch 块
void tryCatchChain(int option) {
    try {
        if (option == 1) {
            throw "第一个";
        }
        else {
            throw "第二个";
        }
    }
    catch (const char* msg) {
        std::cout << "在外部 catch 块中捕获到异常: " << msg << std::endl;
       // throw; // 重新抛出异常
    }
}

int main() {
    try {
        tryCatchChain(2);
    }
    catch (const char* msg) {
        std::cout << "在主 catch 块中捕获到异常: " << msg << std::endl;
    }

    return 0;
}

运行结果可以看到最近的是外部捕获:

3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch 以后销毁。(这里的处理类似于函数的传值返回)
#include <iostream>
// 异常对象的拷贝和销毁
class MyException {
public:
    MyException() {
        std::cout << "异常对象已创建" << std::endl;
    }

    ~MyException() {
        std::cout << "异常对象已销毁" << std::endl;
    }
};
void throwException() {
    throw MyException();
}
int main() {
    try {
        throwException();
    }
    catch (MyException& e) {
        std::cout << "捕获到异常" << std::endl;
    }

    return 0;
}

运行结果:

4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。
#include <iostream>

// catch(...) 的通配符
void throwException() {
    throw "出现了问题!";
}

int main() {
    try {
        throwException();
    }
    catch (...) {
        std::cout << "捕获到异常" << std::endl;
    }

    return 0;
}

运行结果:

5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配, 可以抛出的派生类对象, 使用基类捕获。
#include <iostream>

// 使用基类捕获派生类对象
class BaseException {
public:
    virtual void what() const {
        std::cout << "基类异常" << std::endl;
    }
};

class DerivedException : public BaseException {
public:
    void what() const override {
        std::cout << "派生类异常" << std::endl;
    }
};

void throwException() {
    throw DerivedException();
}

int main() {
    try {
        throwException();
    }
    catch (const BaseException& e) {
        std::cout << "捕获到 ";
        e.what();
    }

    return 0;
}

运行结果:

这个例子就发展到自定义异常体系

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

五、异常的重新抛出

有可能单个的 catch 不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用
链函数来处理, catch 则可以通过重新抛出将异常传递给更上层的函数进行处理。
#include<iostream>
using namespace std;
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;
}

Division 函数负责进行整数除法,但在除数为零时抛出异常 "Division by zero condition!"

Func 函数中,我们使用 new 分配了一个整数数组,然后在 try 块中调用 Division 函数,如果出现除以零的情况,会抛出异常。

在 catch 块中,我们捕获了所有类型的异常 catch (...)在这里,我们先输出一条消息表示我们正在释放动态分配的数组,然后使用 delete[] 释放数组的内存。

最后,在 catch 块中使用 throw 重新抛出异常,这样异常会继续向外传递给上层调用者处理。

在 main 函数中,我们捕获并处理 const char* 类型的异常消息,然后输出错误消息。

catch 块会捕获异常并进行处理,比如在例子中,释放动态分配的数组。如果没有重新抛出,程序会继续执行 catch 块后的代码,而不是中断执行。这可能导致程序继续执行其他代码,导致不可预期的行为或错误,比如释放两次空间。

异常的重新抛出这种方式确保了在出现异常时,动态分配的资源得到正确释放,同时异常也被传递给上层处理。

异常安全问题:

构造函数完成对象的构造和初始化 最好不要 在构造函数中抛出异常,否则 可能导致对象不 完整或没有完全初始化
析构函数主要完成资源的清理 最好不要 在析构函数内抛出异常,否则 可能导致资源泄漏 ( 内存泄漏、句柄未关闭等)
C++ 中异常经常会导致资源泄漏的问题,比如在 new delete 中抛出了异常,导致内存泄漏,在lock unlock 之间抛出了异常导致死锁, C++ 经常使用 RAII 来解决以上问题。
使用RAII来申请空间,就算直接跳到catch出了作用域也会自动调用析构来释放。

 六、C++标准库中的异常体系

        在 C++ 中,标准异常类都是以父子类层次结构组织的,它们的定义在 <stdexcept> 头文件中。这个层次结构的根是 std::exception 类,它是所有标准异常类的基类。其他的异常类都直接或间接地继承自 std::exception,形成了一个继承体系。这样的设计使得异常处理更加方便,可以根据需要捕获特定类型的异常,也可以捕获更通用的异常。

下面是一些常见的标准异常类及其关系:

  • std::exception:所有标准异常类的基类。
    • std::bad_alloc:内存分配失败时抛出的异常。
    • std::bad_cast:在动态类型转换中出现问题时抛出的异常。
    • std::bad_typeid:当 typeid 运算符应用于空指针时抛出的异常。
    • std::logic_error:所有逻辑错误异常的基类。
      • std::invalid_argument:当函数参数无效时抛出的异常。
      • std::domain_error:当函数参数超出有效域时抛出的异常。
      • std::length_error:当试图创建超出长度限制的对象时抛出的异常。
      • std::out_of_range:当使用超出有效范围的值时抛出的异常。
    • std::runtime_error:所有运行时错误异常的基类。
      • std::range_error:当执行超出范围的结果时抛出的异常。
      • std::overflow_error:当执行算术运算超出范围时抛出的异常。
      • std::underflow_error:当执行算术运算下溢时抛出的异常。

 七、异常的规范

        异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的 后面接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;

如果使用noexpect仍然抛出异常,编译会告警:

但是有的编译器就算加了也不一定会告警,所以使用的时候要注意。

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

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

相关文章

STM32接入CH340芯片的初始化进入升级模式(死机)问题处理

目录 1. 问题描述2. 问题分析2.1 CH340G/K 的初始化波形2.2 第1种USB升级电路2.3 第2种USB升级电路2.4 第3种USB升级电路2.5 第4种USB升级电路 3. 总结 1. 问题描述 我所用的CH340G&#xff08;CH340K也用过&#xff09;接在MCU的电路中&#xff0c;在插入CH340G/K 的接插件&a…

基于点灯Blinker的ESP8266远程网络遥控LED

本文介绍基于ESP8266模块实现的远程点灯操作&#xff0c;手机侧APP选用的是点灯-Blinker&#xff0c;完整资料及软件见文末链接 一、ESP8266模块简介 ESP8266是智能家居等物联网场景下常用的数传模块&#xff0c;具有强大的功能&#xff0c;通过串口转WIFI的方式可实现远距离…

文献速递:深度学习医学影像心脏疾病检测与诊断--CT中的深度学习用于自动钙评分:使用多个心脏CT和胸部CT协议的验证

Title 题目 Deep Learning for Automatic Calcium Scoring in CT: Validation Using Multiple Cardiac CT and Chest CT Protocols CT中的深度学习用于自动钙评分&#xff1a;使用多个心脏CT和胸部CT协议的验证 Background 背景 Although several deep learning (DL) calc…

微软开发新模型;YouTube 推出新AI功能;可折叠iPhone 或发布?

微软或开发新模型与 Google、OpenAI 竞争 The Information 报道&#xff0c;微软正在训练一种新的 AI 大模型「MAI-1」&#xff0c;规模上足以与 Google、Anthropic 乃至 OpenAI 的先进模型抗衡。 据报道&#xff0c;这个 MAI-1 模型由微软聘请的 Inflection 前 CEO Mustafa S…

unity基础(二)

debug方法 Debug.Log(" 一般日志 ");Debug.LogWarning(" 警告日志 ");Debug.LogError(" 错误日志 ");// Player Informationstring strPlayerName "Peter";int iPlayerHpValue 32500;short shPlayerLevel 10;long lAdvantureExp 1…

爱普生MCU系列语音芯片S1C31D41

随着科技的发展和产品的集成化&#xff0c;语音芯片已经逐渐替代了多种语音设备应用在各场合。语音芯片主要特性是功耗低&#xff0c;抗干扰能力强&#xff0c;外围器件少&#xff0c;控制简单&#xff0c;语音保存时间久(某些语音芯片可以保存内容100年)&#xff0c;掉电不丢失…

yolo-world:”目标检测届大模型“

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

【Git】Git学习-16:git merge,且解决合并冲突

学习视频链接&#xff1a; 【GeekHour】一小时Git教程_哔哩哔哩_bilibili​编辑https://www.bilibili.com/video/BV1HM411377j/?vd_source95dda35ac10d1ae6785cc7006f365780 1 创建分支dev&#xff0c;并用merge合并master分支&#xff0c;使dev分支合并上master分支中内容为…

[Algorithm][多源BFS][矩阵][飞地的数量][地图中的最高点][地图分析] + 多源BFS原理讲解 详细讲解

目录 0.原理讲解1.矩阵1.题目链接2.算法原理详解3.代码实现 2.飞地的数量1.题目链接2.算法原理详解3.代码实现 3.地图中的最高点1.题目链接2.算法原理详解3.代码实现 4.地图分析1.题目链接2.算法原理详解3.代码实现 0.原理讲解 注意&#xff1a;只要是用**BFS解决的最短路径问题…

韩顺平0基础学Java——第5天

p72——p86 今天同学跟我说别学java&#xff0c;真的吗&#xff1f;唉&#xff0c;先把这视频干完吧。 逻辑运算符练习 x6&#xff0c;y6 x6&#xff0c;y5 x11&#xff0c;y6 x11&#xff0c;y5 z48 错了&a…

【web网页制作】html+css旅游家乡河南开封主题网页制作(4页面)【附源码】

HTMLCSS家乡河南主题网页目录 &#x1f354;涉及知识&#x1f964;写在前面&#x1f367;一、网页主题&#x1f333;二、页面效果Page1 首页Page2 开封游玩Page 3 开封美食Page4 留言 &#x1f308; 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 &#x1f40b;四…

【Git】Git学习-14:VSCode中使用git

学习视频链接&#xff1a;【GeekHour】一小时Git教程_哔哩哔哩_bilibili​编辑https://www.bilibili.com/video/BV1HM411377j/?vd_source95dda35ac10d1ae6785cc7006f365780 在vscode中打开文件 code . 自行修改内容&#xff0c;在源代码管理器中测试下

flutter报错

组件相关 type ‘List’ is not a subtype of type ‘List’ children: CardList.map((item) > Container( 加上 *** < Widget>*** 正常 type ‘(dynamic, dynamic) > Container’ is not a subtype of type ‘(CardType) > Widget’ of ‘f’ children: CardL…

Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用

前言 在Spring Data JPA系列的第一篇文章 SpringBoot集成JPA及基本使用-CSDN博客 中讲解了实体类的Id生成策略可以通过GeneratedValue注解进行配置&#xff0c;该注解的strategy为GenerationType类型&#xff0c;GenerationType为枚举类&#xff0c;支持四种Id的生成策略&…

详细讲解lua中string.gsub的使用

string.gsub 是 Lua 标准库中的一个函数&#xff0c;用于全局替换字符串中的某些部分。string.gsub 是 Lua 中非常实用的一个函数&#xff0c;它可以用来进行字符串的处理和替换操作。 它的基本语法如下&#xff1a; string.gsub(s, pattern, replacement [, n])s 是要处理的…

鸿蒙开发核心技术都有哪些【都是从零开始】

鸿蒙开发核心技术都有哪些&#xff1f;&#xff1a;【持续1年的时间公关鸿蒙技术】 我们能做哪些呢&#xff1f; 还是从UI业务开始吧 面试题1&#xff1a; 基于STAGE模型项目重构等问题 代理设计模式&#xff0c;业务与架构隔离 中介者模式&#xff0c;和代理设计模式的区别…

项目管理-项目绩效域1/2

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 1.项目绩效域--整体框架 项目绩效域 重点&#xff1a; ①八大绩效域的含义。 ②八大绩效域的问题和解决方案。 ③八大绩效域与十大管…

Go标准库——Flag库和Log库

一.Flag Go语言内置的flag包实现了命令行参数的解析&#xff0c;flag包使得开发命令行工具更为简单。 1.1 os.Args 如果你只是简单的的想要获取命令行参数&#xff0c;可以像下面代码示例一样使用os.Args来获取命令行参数。 os.Arg实际是一个存储命令行参数的字符串切片([]stri…

Linux最新提权通杀五大绝招(上)

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 Linux 主机权限提升问题是普遍存在的。在Web 服务器、数据库、防火墙、IOT等基础设施中&#xff0c;大部分都运行着Linux 操作系统&#xff0c;鉴于Linux 设备在大量基…

【负载均衡在线OJ项目日记】项目简介

目录 前言 什么是负载均衡 所用的技术和开发环境 所用技术 开发环境 项目的宏观结构 leetcode 结构 结构 编写思路 前言 从C语言的文章到现在Linux网络部分&#xff0c;我已经涉猎了很多知识&#xff1b;终于在今天我要开始搞项目了&#xff0c;通过项目我也可以开始…