【C++】异常处理机制(对运行时错误的处理)
🌈 个人主页:谁在夜里看海.
🔥 个人专栏:《C++系列》《Linux系列》
⛰️ 天高地阔,欲往观之。

目录
引言
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_SUCCESS和EXIT_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 -2int 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 0cout << 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.需要手动定义标准体系 | |
总结:异常总体还是利大于弊的,在工程中也鼓励使用异常。
以上就是对异常处理的介绍与个人理解,欢迎指正~
码文不易,还请多多关注支持,这是我持续创作的最大动力!
相关文章:
【C++】异常处理机制(对运行时错误的处理)
🌈 个人主页:谁在夜里看海. 🔥 个人专栏:《C系列》《Linux系列》 ⛰️ 天高地阔,欲往观之。 目录 引言 1.编译器可以处理的错误 2.编译器不能处理的错误 3.传统的错误处理机制 assert终止程序 返回错误码 一、…...
C++ boost steady_timer使用介绍
文章目录 1. 引入必要的头文件2. 基本用法2.1 同步定时器解释:2.2 异步定时器解释:3. 异步定时器与回调函数4. 设置定时器的超时时间4.1 使用秒、毫秒、微秒4.2 修改定时器的到期时间5. 多次使用定时器6. 循环执行任务7. 错误处理总结:C++ Boost 库提供了 boost::asio::stea…...
JVM 由多个模块组成,每个模块负责特定的功能
Java虚拟机(JVM, Java Virtual Machine)是一个抽象的计算机,它提供了一个运行环境,使得Java字节码可以在不同的平台上执行。JVM 由多个模块组成,每个模块负责特定的功能。以下是 JVM 的主要模块及其功能: …...
ORACLE批量插入更新如何拆分大事务?
拆分大事务 一、批量插入更新二、拆分事务之前文章MYSQL批量插入更新如何拆分大事务?说明了Mysql如何拆分,本篇文章探讨Oracle或OceanBase批量插入更新拆分大事务的问题 一、批量插入更新 oracle批量插入更新可使用merge语法eg: merge test ausing test_tmp bon (a.id = b.id…...
kafka+zookeeper的搭建
kafka从2.8版本开始,就可以不用配置zookeeper了,但是也可以继续配置。我目前使用的kafka版本是kafka_2.12-3.0.0.tgz,其中前面的2.12表示是使用该版本的scala语言进行编写的,而后面的3.00才是kafka当前的版本。 通过百度网盘分享…...
Spark中的宽窄依赖
一、什么是依赖关系 这里通过一张图来解释: result_rdd是由tuple_rdd使用reduceByKey算子得到的, 而tuple_rdd是由word_rdd使用map算子得到的,word_rdd又是由input_rdd使用flatMap算子得到的。它们之间的关系就称为依赖关系! 二…...
安装和运行开发微信小程序
下载HBuilder uniapp官网 uni-app官网 微信开发者工具 安装 微信小程序 微信小程序 官网 微信小程序 配置 运行 注意:运行前需要开启服务端口 如果运行看不到效果,设置下基础库选别的版本 配置...
地图框架之mapbox——(五)
今天主要学习mapbox中如何使用画笔! 一、导入画笔依赖 <script src"https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.2.2/mapbox-gl-draw.js"></script> <link rel"stylesheet" href"https://api.mapbox…...
Hive 的数据类型
基本类型 整型 TINYINT: 1字节整数,范围从 -128 到 127。SMALLINT: 2字节整数,范围从 -32,768 到 32,767。INT: 4字节整数,范围从 -2,147,483,648 到 2,147,483,647。BIGINT: 8字节整数,范围从 -9,223,372,036,854,775,808 到 9…...
2024下半年软考考后估分,快来预约!
2024下半年软考这周末就要开考了!考后大家最关心的,莫过于考试成绩。届时会为家更新回忆版真题及答案,现在就可以开始预约啦~ 因为是回忆版,老师做题也需要时间,答案会慢慢更新,大家耐心等待片刻ÿ…...
第8章 利用CSS制作导航菜单作业
1.利用CSS技术,结合链接和列表,设计并实现“山水之间”页面。 浏览效果如下: HTML代码如下: <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>山水之间</title><…...
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
摘要 近年来,信息化管理行业的不断兴起,使得人们的日常生活越来越离不开计算机和互联网技术。首先,根据收集到的用户需求分析,对设计系统有一个初步的认识与了解,确定船舶监造系统的总体功能模块。然后,详…...
linux强制修改mysql的root账号密码
在Linux环境下,如果您忘记了MySQL的root密码,可以通过以下步骤来强制修改root密码: 在执行这些步骤之前,请确保您有足够的权限来执行这些命令。 停止MySQL服务: systemctl stop mysql 启动MySQL的安全模式,…...
CentOS系统查看CPU、内存、操作系统等信息
Linux系统提供了一系列命令可以用来查看系统硬件信息,如CPU的物理个数、核数、逻辑CPU数量、内存信息和操作系统版本。 查看物理CPU、核数和逻辑CPU 在多核、多线程的系统中,了解物理CPU个数、每个物理CPU的核数和逻辑CPU个数至关重要。超线程技术进一步…...
针对解决前后端BUG的个人笔记
1-IDEA Q:Required Java version 17 is not supported by SDK 1.8. The maximum supported Java version is 8. A: 我们只知道IDEA页面创建Spring项目,其实是访问spring initializr去创建项目。故我们可以通过阿里云国服去间接创建Spring项目。将https…...
5G时代已来:我们该如何迎接超高速网络?
内容概要 随着5G技术的普及,我们的生活似乎变得更加“科幻”了。想象一下,未来的智能家居将不仅仅是能够听你说“开灯”;它们可能会主动询问你今天心情如何,甚至会推荐你一杯“维他命C芒果榨汁”,帮助你抵御夏天的炎热…...
企业级-实现Redis封装层
作者:fyupeng 技术专栏:☞ https://github.com/fyupeng 项目地址:☞ https://github.com/fyupeng/distributed-blog-system-api 留给读者 封装 Redis 客户端Dao层、分布式锁等。 一、介绍 二、代码 DataInitialLoadRunner.java /*** Clas…...
SpringBoot使用ApplicationContext.getBean启动报空指针处理记录
问题:项目启动报空指针 定位:新增filter中init方法使用getbean控制 解决:在新增filter上加注解 DependsOn({"applicationContextUtils"}) Component DependsOn({"applicationContextUtils"})//此处解决空指针问题 pu…...
MongoDB Shell 基本命令(三)聚合管道
管道含义 类似Linux中的管道,前一个命令的输出作为后一个命令的输入。 显示网络连接、路由表和网络接口统计信息 netstat -ano -netstat:network statistics 网络统计 -a:显示所有连接和监听端口,包括所有活动的TCP和UDP连接。 -n:以数字形式显示地址…...
Go语言的内置容器
文章目录 一、数组数组的定义数组声明数组特点数组元素修改 二、切片切片声明基于数组创建切片使用make()函数构造切片使用append()为切片动态添加元素\使用copy()复制新的切片数组与切片相互转换 三、Map映射Map定义使用make()函数创建map用切片作为map的值使用delete()函数删…...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...

