【C++】异常之道,行者无疆:解锁 C++ 的异常捕获哲学

【C++】异常之道,行者无疆:解锁 C++ 的异常捕获哲学

文章目录

C语言处理错误

  • 终止程序:利用 assert() 断言去终止程序,当 ()的表达结果为 false 时会终止程序。
  • 返回错误码:手动查找对应的错误,系统的接口函数将作错误码放到 errno 中表示错误。

C语言中的 strerror 将参数对应 errno 的错误信息的字符串返回。errno 是一个全局变量,当使用标准库的函数发生错误时,就会将对应的的错误码放到 errno 中,每个错误码对应着不同的错误信息,strerror 就可以将错误码对应的字符串返回。
以下为错误码 0~10 对应的信息:

#include<iostream>#include<errno.h>usingnamespace std;intmain(){for(size_t i =0; i <=10;++i) cout << i <<":"<<strerror(i)<< endl;return0;}
在这里插入图片描述

C++异常

C++的异常处理是一种应对程序运行时错误的机制,允许在程序中独立开发的部分在运行时就出现的错误进行通信并作出相应的处理,使得将问题的检测与解决问题的过程分开,程序的一部分检测问题的出现,然乎将解决任务传递给程序的一部分。

总的来说,异常能够提供一种结构化的方法来捕获和处理错误,从而提高代码的健壮性和可维护性。

异常的抛出与捕获

基本语法

C++的常处理依赖于三个关键字:

  • try:用于定义可能抛出异常的代码块。
  • throw:用于抛出异常对象。
  • catch:用于捕获异常,并定义处理异常的逻辑。

使用实例

voidtestException(int x){if(x ==0)throw"Division by zero error!";//抛出 const char* 类型的异常 cout <<"x:"<< x << endl;}intmain(){try{testException(0);}catch(constchar* e){ cout <<"Caught an exception: "<< e << endl;}return0;}

catch 的匹配原则

1.异常的类型

C++支持抛出任意类型的对象作为异常,例如:

  • 基本类型:如 intcharfloat
  • 标准库类型:如 string
  • 自定义类型:通过类和结构体定义的类型。

示例:

throw42;// 抛出整数throw std::string("Error");// 抛出字符串throw std::runtime_error("Runtime error");// 抛出标准库异常类
在这里插入图片描述

2.标准异常类

C++标准库还提供了一组异常类,位于 exception 头文件中。常见的标准异常类包括:

  • exception:所有标准异常的基类。
  • runtime_error:运行时错误。
  • logic_error:逻辑错误,如非法参数、越界访问等。
  • 其他派生类:如 out_of_rangeinvalid_argument

使用标准异常类:

intmain(){try{throwruntime_error("Runtime exception occurred!");}catch(const exception& e){ cout <<"Caught standard exception: "<< e.what()<< endl;}return0;}

3.匹配原则

  • 异常抛出的对象的类型决定了应该匹配哪个 catch 的处理代码,该过程不会有隐式类型转换
voidtestException(int x){if(x ==0)throw"Division by zero error!";//抛出 const char* 类型的异常 cout <<"x:"<< x << endl;}intmain(){try{testException(0);}catch(const string& e)//不会有隐式类型转换,不会匹配string的catch{ cout <<"Caught an exception(string): "<< e << endl;}catch(constchar* e){ cout <<"Caught an exception: "<< e << endl;}return0;}
在这里插入图片描述
  • 如果有多个类型匹配的 catch 的版本,则会去调用离抛出异常位置最近catch
voidtestException(int x){if(x ==0)throw"Division by zero error!";//抛出 const char* 类型的异常 cout <<"x:"<< x << endl;}voidtest1(){try{testException(0);}catch(constchar* e){ cout <<"(most near) Caught an exception: "<< e << endl;}}intmain(){try{test1();}catch(constchar* e){ cout <<"Caught an exception: "<< e << endl;}return0;}
在这里插入图片描述
  • throw 抛出异常对象后,会生成异常对象的拷贝,因为抛出的异常对象可能是临时对象。这个拷贝的临时异常对象会在被 catch 后销毁(类似于函数的传值返回)
  • catch(...) 可以捕获任意类型的异常对象,但不知道具体的异常错误。
intmain(){try{throwruntime_error("Runtime exception occurred!");}catch(...){ cout <<"Unkonwn exception!"<< endl;}return0;}
在这里插入图片描述
  • 实际上,抛出的异常对象类型实际上也不需要完全匹配,比如:可以抛出派生类对象用基类捕获。

函数调用链中的匹配原则

  1. 当异常在 try 代码块中 throw 抛出时,它会沿函数调用链向上传播,直到找到匹配的 catch 代码块而且此过程中,throw 后面的代码不再执行。
  2. 匹配到对应异常对象的类型的 catch 代码块后,沿着函数调用链销毁沿途的对象。
  3. 如果最后在 main 函数中没有匹配的 catch,程序会调用 terminate 函数,通常导致程序终止。

一般为了避免这种情况,需要用 catch(...) 防止异常没有捕获导致程序崩溃。

在这里插入图片描述


具体例子

voidtestException(int x){if(x ==0)throw"Division by zero error!";//抛出 const char* 类型的异常 cout <<"x:"<< x << endl;}voidtest1(){try{testException(0);}catch(const string& e){ cout <<"(string) Caught an exception: "<< e << endl;}}intmain(){try{test1();}catch(constchar* e){ cout <<"Caught an exception: "<< e << endl;}catch(...)//如果抛出的异常未经处理,程序会被terminate终止{ cout <<"Unkonwn exception!"<< endl;}return0;}
在这里插入图片描述

异常的重新抛出

catch 代码块中,可以通过 throw 关键字将异常重新抛出,供更高层次的代码处理。

voidtestException(int x){if(x ==0)throw"Division by zero error!";//抛出 const char* 类型的异常 cout <<"x:"<< x << endl;}voidtest1(){try{testException(0);}catch(constchar* e){ cout <<"(Inner catch) Caught an exception: "<< e << endl;//将捕获到的异常重新抛出throw;}}intmain(){try{test1();}catch(constchar* e){ cout <<"(Outer catch) Caught an exception: "<< e << endl;}catch(...)//如果抛出的异常未经处理,程序会被terminate终止{ cout <<"Unkonwn exception!"<< endl;}return0;}
在这里插入图片描述

异常安全

  • 构造函数最好不要抛异常,因为可能导致对象不完整初始化甚至完全没有初始化。
  • 析构函数也最好不要抛异常,否则可能造成资源泄漏。
voidtestException(int x){int* array =newint[10];if(x ==0)throw"Division by zero error!";//抛出 const char* 类型的异常 cout <<"x:"<< x << endl;delete[] array;}intmain(){try{testException(0);}catch(constchar* e){ cout <<"Caught an exception: "<< e << endl;}return0;}

这里由于 throw 抛异常提前退出了 testException 函数导致执行 delete[] array 导致了资源泄漏,这是非常危险的,一定要避免。

异常规范

  • 在函数的后面接 throw(type) ,表示这个函数可能抛出的所有的异常类型。
  • 函数后面接 throw() 表示函数不抛异常,在C++11中新增关键字 noexcept ,表示该函数不抛异常。
  • noexcept 会影响异常的捕获,确认函数不会加才使用。
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常voidfun()throw(A,B,C,D);// 这里表示这个函数只会抛出bad_alloc的异常void*operatornew(std::size_t size)throw(std::bad_alloc);// 这里表示这个函数不会抛出异常void*operatordelete(std::size_t size,void* ptr)throw();// C++11 中新增的noexcept,表示不会抛异常thread()noexcept;thread(thread&& x)noexcept;

C++标准库异常

C++ 标准库中提供了一组异常类,用于支持异常处理机制。这些异常类都继承自 exception,提供了统一的接口以便捕获和处理各种类型的异常。

核心异常类
exception 是描述标准库异常的基类,其接口为 virtual const char* what() const noexcept ,返回异常的描述信息。

  • bad_alloc :表示内存分配失败的异常,通常由 new 操作符抛出。
  • bad_cast :表示动态类型转换(dynamic_cast)失败的异常。
  • bad_typeid :在对空指针调用 typeid 时抛出。
  • bad_exception :如果异常对象在 throw 时不匹配声明的异常类型,可能会抛出此异常。

逻辑错误异常
logic_error 是一个逻辑错误的基类,表示程序中的逻辑问题,通常在运行时能够检测到。

  • domain_error :表示函数参数超出定义域的异常,比如尝试对负数求平方根。
  • invalid_argument :表示无效参数引发的异常,比如传递非法格式的字符串。
  • length_error :表示试图创建超出容器最大长度的对象,比如向 vector 添加过多元素。
  • out_of_range :表示访问容器中不存在的元素时抛出的异常,比如使用越界的索引访问 vector

运行时错误异常
runtime_error 是运行时错误的基类,表示在程序运行过程中发生的错误。

  • range_error :表示计算结果超出表示范围的异常。
  • overflow_error :表示算术运算发生上溢的异常。
  • underflow_error :表示算术运算发生下溢的异常。

其他异常

  • ios_base::failure :表示与输入/输出流相关的错误,比如文件读取失败。

拜拜,下期再见😏

摸鱼ing😴✨🎞

请添加图片描述

Read more

【超详细】Python FastAPI 入门:写给新手的“保姆级”教程

【超详细】Python FastAPI 入门:写给新手的“保姆级”教程

前言  作为一名大学生,最近在做 Python Web 开发时发现了一个“宝藏”框架——FastAPI。 以前学 Django 光配置就头大,学 Flask 又不知道怎么写规范。直到遇到了 FastAPI,我才体会到什么叫“写代码像呼吸一样自然”。 这篇文章不讲复杂的原理,只讲最基础、最实用的操作,带你从 0 到 1 跑通第一个 API 接口! 一、FastAPI 是什么 在 Python 的世界里,做网站后台(Web 开发)主要有三巨头: 1. Django:老大哥,功能全但笨重,像一辆重型卡车。 2. Flask:二哥,轻便灵活但插件多,像一辆自行组装的赛车。 3.

By Ne0inhk

Python 代码打包为 EXE 完全指南

Python 代码打包为 exe 完全指南(2025–2026 年最新实用版) 目前最主流、最稳定的几种打包方式对比(按推荐顺序): 排名工具优点缺点/坑点适合场景推荐指数 (2026)1PyInstaller兼容性最好、社区最大、文档最全生成的 exe 偏大、启动稍慢几乎所有场景(首选)★★★★★2Nuitka启动速度最快、文件体积较小、接近原生性能编译时间长、对依赖处理更严格对启动速度敏感的项目★★★★☆3cx_Freeze跨平台支持好、配置灵活社区活跃度低、文档较老需要高度自定义打包逻辑★★★☆☆4PyOxidizer极致体积优化、Rust 底层配置复杂、生态不成熟极致追求小体积的场景★★☆☆☆5Shiv / PEX生成 .pex 文件(类似 jar),不生成 exe需要 Python 环境才能运行服务器/内部工具分发(非桌面程序)★★☆☆☆ 绝大多数人(尤其是 Windows 桌面程序)2026 年仍然首选:

By Ne0inhk
还在傻傻用系统自带Python?请立刻停止这场环境配置的自杀行为

还在傻傻用系统自带Python?请立刻停止这场环境配置的自杀行为

摘要: 做开发的,谁没经历过 Python 环境的“第十八层地狱”? 不管是初出茅庐的菜鸟,还是写了十年代码的老鸟,基本都遇到过这种窒息时刻:你要跑一个两年前的老项目,它依赖 Python 3.6,而你的 … 做开发的,谁没经历过 Python 环境的“第十八层地狱”? 不管是初出茅庐的菜鸟,还是写了十年代码的老鸟,基本都遇到过这种窒息时刻:你要跑一个两年前的老项目,它依赖 Python 3.6,而你的 MacBook 自带 Python 3.9,服务器上又是 Python 3.8。你手一抖,敲下了 sudo apt-get install python3.6,或者试图强行覆盖系统路径下的 Python。 恭喜你,你的系统环境可能已经离“爆炸”

By Ne0inhk
新手向:C语言、Java、Python 的选择与未来指南

新手向:C语言、Java、Python 的选择与未来指南

语言即工具,选对方向比埋头苦学更重要 你好,编程世界的新朋友!当你第一次踏入代码的宇宙,面对形形色色的编程语言,是否感到眼花缭乱?今天我们就来聊聊最主流的三种编程语言——C语言、Java 和 Python——它们各自是谁,适合做什么,以及未来十年谁能带你走得更远。 一、编程世界的三把钥匙:角色定位 如果把编程比作建造房屋,那么: * C语言是钢筋骨架:诞生于1972年,它直接与计算机硬件“对话”,负责构建最基础的支撑结构。 * Java是精装套房:1995年问世,以“一次编写,到处运行”闻名,擅长打造稳定、可复用的功能模块。 * Python是智能管家:1991年出生却在近十年大放异彩,像一位高效助手,用最少的指令完成复杂任务13。 二、核心差异对比:从底层到应用 1. 语言类型与设计哲学 * C语言:属于面向过程的编译型语言。代码在执行前需全部翻译成机器指令,运行效率极高,但需要开发者手动管理内存(类似自己打扫房间)15。 * Java:

By Ne0inhk