【C++】异常

📝前言:
这篇文章我们来讲讲C++异常

🎬个人简介:努力学习ing
📋个人专栏:C++学习笔记
🎀CSDN主页 愚润求学
🌄其他专栏:C语言入门基础python入门基础python刷题专栏Linux


一,异常处理

异常处理是C++中处理运行时错误的重要机制,它允许程序在遇到错误时能够优雅地恢复或终止,而不是直接崩溃。

1.1 基本语法

C++异常处理使用三个关键字:try, catch, 和 throw

try {
    // 可能抛出异常的代码
    if (error_occurred) {
        throw exception_object;
    }
} catch (exception_type1& e) {
    // 处理exception_type1类型的异常
} catch (exception_type2& e) {
    // 处理exception_type2类型的异常
} catch (...) {
    // 处理所有其他类型的异常
}

1.1 抛出异常

使用throw关键字抛出异常:

double divide(double a, double b) {
    if (b == 0.0) {
        throw "Division by zero!";  // 抛出字符串字面量
    }
    return a / b;
}

1.2 捕获异常

使用catch块捕获并处理异常:

try {
    double result = divide(10.0, 0.0);
} catch (const char* msg) {
    cerr << "Error: " << msg << endl;
}
  • 程序先执行try里面的语句,如果try里面throw了异常,throw后⾯的语句将不再被执行。程序的执行从throw位置跳到与之匹配的catch模块
  • catch的规则:找调用链中与该对象类型匹配离抛出异常位置最近的那⼀个
  • throwcatch:1,沿着调用链的函数可能提早退出。2、沿着调用链创建的对象都将销毁
  • 抛出异常对象后,会⽣成⼀个异常对象的拷⻉,因为抛出的异常对象可能是⼀个局部对象,所以会⽣成⼀个拷⻉对象,这个拷⻉的对象会在catch⼦句后销毁。(这⾥的处理类似于函数的传值返
    回)

二,具体的找catch的栈展开过程

  • 如果throwtry里面,则找对应的catch
  • 如果没找到 / 类型不匹配,则提前退出当前函数,到外层函数里面(函数调用链)找
  • 如果找到main函数都没找到,则程序会调⽤标准库的 terminate 函数终止程序【main函数兜底】
  • 如果这个过程中有一步找到了,则跳去执行catch,执行完以后,不会回到原来的位置,而是继续执行catch后面的语句,相当于throw改变了执行流程

在这里插入图片描述

三,catch 的类型匹配

除了严格的完全对应的类型匹配,以下几种也可以匹配:

  • 允许从⾮常量向常量的类型转换,也就是权限缩⼩
  • 允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针
  • 允许从派⽣类向基类类型的转换(实际中继承体系基本都是⽤这个⽅式设计)
  • catch(...)...表示匹配任意类型(但是不知道异常错误是什么),main函数的兜底就是这个

四, 异常的重新抛出

在catch块中可以重新抛出当前异常:

try {
    // ...
} catch (const std::exception& e) {
    // 部分处理
    throw;  // 重新抛出相同的异常
}
  • 重新抛出的是原始异常对象(不是拷贝),并且保留原始的异常类型和内容。
  • 不会被同一个catch块再次捕获,而是会跳出当前try-catch块,继续向外层传播,由外层的try-catch块捕获。

五,异常安全问题

  • 异常抛出后,后⾯的代码就不再执行,如果前面申请了资源,但是释放代码在catch后面,而catch中又重新抛出了异常,就会没有释放,引发资源泄漏,产⽣安全性的问题。解决方法:1,捕获异常以后,要先释放资源,再重新抛出;2,用智能指针的RAII方式解决这种问题是更好的
  • 其次,析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛
    出异常,则也需要捕获处理,否则后⾯的5个资源就没释放,也资源泄漏了。

示例(解决方法1):

void process() {
    int* resource = new int(100);  // 申请资源(假设是动态内存)
    
    try {
        some_operation_that_may_throw();  // 可能抛出异常
    } 
    catch (...) {
        delete resource;  // 手动释放资源
        throw;           // 重新抛出异常
    }

    delete resource;  // 正常情况下的释放
}

但是这种解决方法不好,后面用智能指针 +RAII更好!

六,异常的规范

  • 对于用户和编译器而言,预先知道某个函数是否会抛出异常有助于简化调用函数的代码
  • noexcept关键字:函数参数列表后⾯加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常
  • 编译器并不会在编译时检查noexcept修饰的函数。即使里面还是有throw
  • 但是,如果⼀个声明了noexcept的函数抛出了异常(里面有throw,并且运行时抛出了),程序会调用 terminate 终止程序
  • noexcept还可以作为⼀个运算符去检测⼀个表达式是否会抛出异常,如果保证不会抛出异常就返回true,可能会就返回false

示例(有noexcept但是还是会throw):

double Divide(int a, int b) noexcept
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	return (double)a / (double)b;
}
int main()
{
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	int i = 0;
	cout << noexcept(Divide(1, 2)) << endl;
	cout << noexcept(Divide(1, 0)) << endl;
	cout << noexcept(++i) << endl;
	return 0;
}

编译时通过,运行时,输入:1 0,直接干没了
在这里插入图片描述
不带noexcept

double Divide(int a, int b)

输入2 2 运行结果:
在这里插入图片描述

七,标准异常

官方文档:https://legacy.cplusplus.com/reference/exception/exception/

  • C++标准库也定义了⼀套自己的⼀套异常继承体系库,基类是exception,所以我们⽇常写程序,需要在主函数捕获exception即可,要获取异常信息,调⽤what函数,what是⼀个虚函数,派生类可以重写。
  • 标准异常头文件;<stdexcept>

在这里插入图片描述
标准库常见异常类:

异常类用途
std::logic_error程序逻辑错误(如无效参数)
std::runtime_error运行时错误(如文件不存在)
std::bad_alloc内存分配失败(new 抛出)
std::out_of_range越界访问(如 vector::at)

示例:

int main() {
    try {
        // 尝试执行的代码块
        std::vector<int> v(1000000000); // 可能抛出 std::bad_alloc
        throw std::runtime_error("自定义运行时错误"); // 构造并抛出标准异常类:runtime_error异常,并且用"自定义运行时错误"构造它,把异常信息设置成这句话。后续.what()的时候,就可以获取异常信息
    }
    catch (const std::exception& e) {
        // 异常处理块
        std::cerr << "捕获异常: " << e.what() << std::endl;
    }
    return 0;
}

执行流程

  1. try 块中的代码执行:

    • 首先尝试创建一个超大vector v(1000000000)

      • 如果系统内存不足,会抛出 std::bad_alloc 异常

      • 如果创建成功,继续执行下一行

    • 然后执行 throw std::runtime_error("自定义运行时错误")

      • 这行代码会主动抛出一个运行时错误异常
  2. catch 块捕获异常:

    • 无论抛出的是 std::bad_alloc 还是 std::runtime_error

    • 因为它们都继承自 std::exception,所以都会被这个catch块捕获

    • 捕获后执行异常处理代码,打印错误信息

运行示例(内存足够时):
在这里插入图片描述
vector的大小再加大(内存不足时):
在这里插入图片描述


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚润泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值