当前位置: 首页 > news >正文

C++修炼:异常

         Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》

欢迎点赞,关注!

        异常可以说是一种非常厉害的错误处理方式,不仅C++在用,java,python等也都在用。那么这期我们就深入学习一下异常。

目录

1、异常的概念

        1.1、什么是异常

        1.2、异常的抛出与捕获

        1.3、查找匹配的处理代码

基类与派生类异常定义

异常触发函数

异常捕获场景

关键点说明

扩展测试场景

        1.4、异常重新抛出       

        1.5、异常安全问题

        1.6、异常规范

2、标准库的异常

标准异常类

使用示例

自定义异常

异常处理建议


 

1、异常的概念

        1.1、什么是异常

        在C语言中,我们主要是通过错误码的形式处理错误。也就是说对各种错误进行编号。在C++中我们不用这么麻烦的方法去处理错误了,我们出现错误时,也就是异常时直接抛出一个对象,这个对象可以包含各种错误信息。

        1.2、异常的抛出与捕获

        当出现错误是,我们通过throw这个关键字来抛出一个异常对象,同时呢我们可以用catch关键字进行捕获。try也是异常处理机制的关键字,他一般和catch搭配使用。

        话不多说我们直接上代码:

#include<iostream>
using namespace std;
int division(int x, int y)
{if (y == 0){string s("Division by zero!");throw(s);}else{return x / y;}return 0;
}
int func1(int x,int y)
{try{cout << division(x, y) << endl;}catch (const char* ch){cout << ch << endl;cout << "Successful catch!" << endl;}return 0;
}
int func2(int x, int y)
{try{func1(x,y);}catch (string& s){cout << s << endl;cout << "Successful catch!" << endl;}return 0;
}
int main()
{func2(10, 5);func2(10, 0);return 0;
}

        在上面这个逻辑中,当除数为0是会发生错误,此时应该会抛出异常。 

        抛出异常后,会暂定当前程序,开始寻常与之匹配的catch子句。对于try catch语句,如果没有捕获到异常,就会继续在外层调用函数链中查找,也就是返回上一层函数。一直查找,直到异常被捕获。那么这个查找的过程就叫做栈展开。

        如果某个异常一直没有被捕获,当他找不到任何能够和他匹配的捕捉时就会报错终止程序。

终止程序是通过标准库函数terminate来实现的。

        上述的栈展开的过程都是在编译时进行的。

        下面我们拿func2(10,0)来举例看看栈展开的过程是什么样的。

        当捕获之后,后续的代码正常执行。比如说我们上面的代码在func2中被捕获了,那么继续执行func2中catch之后的代码,也就是return 0。 

        一个try可以匹配多个catch:

	try{cout << division(x, y) << endl;}catch (const char* ch){cout << ch << endl;cout << "Successful catch!" << endl;}catch (string& s){cout << s << endl;cout << "Successful catch!" << endl;}

        当有多个catch与异常匹配的时候,距离抛出异常最近的catch优先捕获。 并且还有两点需要注意:1、沿着调用链中的其他函数可能提前退出。2、一旦程序开始处理异常,沿着调用链创建的对象都将销毁。

        抛出异常对象后,会生成一个异常对象的拷贝,因为异常对象可能是局部对象。这个拷贝对象在catch子句之后销毁。

        1.3、查找匹配的处理代码

        一般情况下catch是和抛出对象完全匹配的。但是也有例外,捕获允许权限缩小(常量向非常量转换)、数组向指向数组元素类型的指针,函数向指向函数的指针,但最重要的是允许派生类向基类转换。

        我们在捕获异常时,可以设置一个基类,再设置几个典型的异常去继承这个基类,那么对于捕获来说,我们直接捕获基类,这个基类的所有子类就都被捕获了。如果我们不需要捕获这个基类的异常,可以直接把这个基类定义成抽象类。

        以下是一个展示C++异常捕获子类的经典场景示例,通过基类异常和派生类异常的分层处理,体现异常捕获的多态性和优先级机制。

基类与派生类异常定义

#include <iostream>
#include <stdexcept>// 基类异常
class BaseException : public std::runtime_error {
public:BaseException(const std::string& msg) : std::runtime_error(msg) {}
};// 派生类异常
class DerivedException : public BaseException {
public:DerivedException(const std::string& msg) : BaseException(msg) {}
};

异常触发函数

void simulateError(int code) {if (code == 1) {throw DerivedException("Derived Exception Occurred");} else if (code == 2) {throw BaseException("Base Exception Occurred");} else {throw std::runtime_error("Generic Error");}
}

异常捕获场景

int main() {try {simulateError(1); // 触发派生类异常} catch (const DerivedException& e) {std::cerr << "Caught DerivedException: " << e.what() << std::endl;}catch (const BaseException& e) {std::cerr << "Caught BaseException: " << e.what() << std::endl;}catch (const std::exception& e) {std::cerr << "Caught Generic Exception: " << e.what() << std::endl;}return 0;
}

关键点说明

  • 捕获顺序:派生类异常(DerivedException)的catch块必须放在基类(BaseException)之前,否则派生类异常会被基类catch块截获。
  • 多态性:所有异常类型最终继承自std::exception,可通过基类引用捕获任意派生类异常。
  • 输出示例
    当调用simulateError(1)时,输出结果为:
    Caught DerivedException: Derived Exception Occurred
    

扩展测试场景

修改main()中的simulateError参数,测试不同异常类型:

simulateError(2); // 触发基类异常
simulateError(3); // 触发通用异常

        我们为了防止到main函数中无法捕获异常而终止程序,一般会在main函数中使用catch(...)捕获,可以捕捉到任意类型的异常。但是我们无法判断异常错误是什么。

        1.4、异常重新抛出       

        捕获异常后,可以直接在 catch 块中重新抛出该异常。

        我们在后面介绍标准库的异常之后再实现一下异常重新抛出的场景。

        1.5、异常安全问题

        在异常抛出之后,当前程序之后的代码就不会执行了。倘若在抛出异常之前向内存申请了一些资源,这部分资源不会被释放,此时会造成内存泄漏。我们可以通过智能指针来解决这个问题,我们下一期会介绍智能指针。其实智能指针就是让每个申请的内存在出作用域时自己释放资源(通过析构函数实现)。

#include <iostream>
using namespace std;void test()
{int* arr1 = new int[10];int* arr2 = new int[10];int* arr3 = new int[10];throw 1;delete[] arr1;//内存泄漏delete[] arr2;delete[] arr3;
}int main() {try{test();}catch (...){cout << "successful catch" << endl;}return 0;
}

         在上面这个场景中会造成内存泄漏。而且不仅如此,还有一个更严重的问题,就是new本身也会抛异常。但是如果我们对于每次new都进行异常捕获并且重新抛出的话,对于连续多个new的话写起来太麻烦了。这个也是可以使用智能指针来解决。

        另外就是析构函数中对抛异常也要进行谨慎处理,否则也可以会导致资源释放不完全而内存泄漏。

        1.6、异常规范

        在C++11之后如果某个函数不会抛异常,我们只需要在函数列表之后加noexcpt。但是抛异常的函数也可以不加noexcept,并不会报错。但是如果某个函数标明了noexcept,但是却抛出了异常,编译器会调用terminate终止程序,因为他会认为这个异常没有被捕获:

#include <iostream>
using namespace std;void test()noexcept//程序终止
{int* arr1 = new int[10];int* arr2 = new int[10];int* arr3 = new int[10];throw 1;delete[] arr1;//内存泄漏delete[] arr2;delete[] arr3;
}int main() {try{test();}catch (...){cout << "successful catch" << endl;}return 0;
}

        还可以用noexcept()来判断一个表达式是否会抛异常。可能抛异常返回false,否则返回true。

#include <iostream>
using namespace std;void test()//程序终止
{int* arr1 = new int[10];int* arr2 = new int[10];int* arr3 = new int[10];throw 1;delete[] arr1;//内存泄漏delete[] arr2;delete[] arr3;
}int main() {cout << noexcept(test()) << endl;//输出0return 0;
}

         需要注意的是,上面的代码中noexcept只是检测test会不会抛出异常,但是并没有真正执行test函数,所以也不会终止程序。

2、标准库的异常

        C++标准库中的异常主要分为两类:标准异常类和自定义异常类。标准异常类均继承自std::exception,提供统一的错误处理接口。

标准异常类

  1. 逻辑错误(std::logic_error
    表示程序逻辑错误,通常在编写代码时可避免。常见子类:

    • std::invalid_argument:参数无效
    • std::out_of_range:超出有效范围
    • std::length_error:超出容器最大长度
  2. 运行时错误(std::runtime_error
    表示运行时无法避免的错误。常见子类:

    • std::overflow_error:算术溢出
    • std::underflow_error:算术下溢
    • std::system_error:系统调用错误
  3. 其他异常

    • std::bad_alloc:内存分配失败(如new操作)
    • std::bad_cast:动态类型转换失败(dynamic_cast

使用示例

#include <iostream>
#include <stdexcept>
#include <vector>int main() {try {std::vector<int> v(5);v.at(10) = 1; // 抛出 std::out_of_range} catch (const std::out_of_range& e) {std::cerr << "Error: " << e.what() << std::endl;} catch (const std::exception& e) {std::cerr << "General error: " << e.what() << std::endl;}
}

自定义异常

继承std::exception或其子类可自定义异常:

class MyException : public std::runtime_error {
public:MyException(const std::string& msg) : std::runtime_error(msg) {}
};try {throw MyException("Custom error");
} catch (const MyException& e) {std::cerr << e.what() << std::endl;
}

        接下来我们补一下之前提到的异常重新抛出场景:

#include <iostream>
#include <stdexcept>void riskyOperation() {throw std::runtime_error("Original error occurred");
}void intermediateFunction() {try {riskyOperation();}catch (...) {std::cout << "Logging the error before rethrow" << std::endl;throw;  // 重新抛出}
}int main() {try {intermediateFunction();}catch (const std::exception& e) {std::cout << "Caught in main: " << e.what() << std::endl;}
}

        当然在实践中我们一般是捕获到特定类型的异常再重新抛出。以上只是一个最基本的异常重新抛出场景。

        好了,今天的内容就分享到这,我们下期再见! 

 

相关文章:

  • 延庆b2c网站制作价格十大引擎网址
  • 明年做哪些网站致富连云港seo优化公司
  • 找人做一下网站大概多少钱深圳百度代理
  • 用mac做网站福建seo
  • 什么网站可以做全景图seo权威入门教程
  • 这么做网站免费开发软件制作平台
  • stm32万年历仿真+keil5程序
  • DeepSeek 和 GPT 系列模型针对越狱攻击的安全评估
  • Lombok注解 - 提高Java开发效率
  • phpstudy apache伪静态.htaccess文件置空丢失问题解决
  • WPF CommunityToolkit.Mvvm
  • JavaEE初阶第四期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(二)
  • Unity_导航操作(鼠标控制人物移动)_运动动画
  • (C++)标准模板库(STL)相关介绍(C++教程)
  • 【轨物洞见】光伏清洁机器人本地组网探析——面向沙漠/海上电站的可靠通信架构设计
  • SAP-ABAP:MODIFY语句灵活更新数据库表详解
  • 使用 Bank Churn 数据集进行二元分类
  • 字节跳动开源了一款 Deep Research 项目
  • react生命周期及hooks等效实现
  • Windows 创建并激活 Python 虚拟环境venv
  • 华为云Flexus+DeepSeek征文 | 基于CCE容器的AI Agent高可用部署架构与弹性扩容实践
  • 解决Fedora21下无法使用NWJS网页透明效果的问题
  • OSS监控体系搭建:Prometheus+Grafana实时监控流量、错误码、存储量(开源方案替代云监控自定义视图)
  • 学习threejs,使用kokomi、gsap实现图片环效果
  • 独家战略!谷子科技“芯”技术联姻浙江卫视
  • 跟着Carl学算法--哈希表