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

C++的异常

引入:异常的意义是什么?

①:错误分离
将正常逻辑(try)与错误处理(catch)分离,避免代码被大量 if-else 污染。

②:强制处理
若不捕获异常,程序终止,避免错误被静默忽略(对比返回错误码可能被遗漏检查)。

③:跨函数传播
异常可自动跨多层函数调用栈向上传递,无需逐层检查返回值。

④:类型安全
能抛出任意类型(对象、字符串等),比错误码(固定类型)更灵活。

⑤:资源安全
结合 RAII(如智能指针),确保异常发生时资源自动释放。

一句话:异常是 C++ 中 结构化、类型安全、自动化 的错误处理机制,替代传统错误码的局限性

一:C和C++处理错误的方式的对比

1:C中的处理错误的方式

①:终止程序(如assert)

例子:

#include <assert.h>
#include <stdio.h>int divide(int a, int b) {assert(b != 0);  // 如果b为0,程序会终止return a / b;
}int main() {printf("Result: %d\n", divide(10, 2));  // 正常工作printf("Result: %d\n", divide(10, 0));  // 程序会在此终止return 0;
}

报错如下:

缺陷:

  • 当除数为0时,程序会突然终止,用户可能看到类似"Assertion failed: b != 0"的错误消息

  • 没有给用户友好的错误处理机会

  • 在生产环境中突然终止程序通常不是好的用户体验

②:返回错误码

例子:

#include <stdio.h>
#include <errno.h>
#include <string.h>int divide(int a, int b, int* result) {if (b == 0) {errno = EDOM;  // 定义域错误return -1;     // 返回错误码}*result = a / b;return 0;          // 返回0表示成功
}int main() {int result;if (divide(10, 0, &result) != 0) {printf("Error occurred: %s\n", strerror(errno));  // 需要查阅errno}else {printf("Result: %d\n", result);}return 0;
}

运行结果:

缺陷:

  • 程序员必须记得检查返回值

  • 需要查阅文档或头文件了解错误码含义

  • 错误处理代码可能使主逻辑变得混乱

  • 像errno这样的全局变量在多线程环境中可能有线程安全问题

总结:

1. 终止程序,如 assert ,缺陷:用户难以接受。如发生内存错误,除 0 错误时就会终止程序。
2. 返回错误码 ,缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno 中,表示错误
实际中 C 语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。

2:C++中处理错误的方式

C++则采取的是用异常来处理错误~

异常是一种处理错误的方式, 当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的
直接或间接的调用者处理这个错误
例子:
double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}int main()
{int a = 0;int b = 0;cin >> a >> b;try{Division(a, b);}catch (const char* s){cout << "捕获异常:" << s << endl;}}

当输入的b为0 的时候,打印如下:

往往会加上"捕获异常",显得更直观,甚至可以加上__LINE__在打印语句中,这样能够直观的知道在哪一行进行了打印异常的操作

__LINE__ 是一个预定义的宏,它的作用是在编译时被替换为当前代码所在的行号

		cout << __LINE__ << "捕获异常:" << s << endl;

打印如下:

389即打印的那一行的行号

其实从这个例子,也能稍微看出来异常使用时的一些语法:

但异常的语法不仅仅如此~

二:异常的语法规则

1:关键字的介绍

· throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
· catch: 在您想要处理问题的地方,通过异常处理程序捕获异常 . catch 关键字用于捕获异常,可以有多个catch 进行捕获。
· try: try 块中的代码标识将被激活的特定异常 , 它后面通常跟着一个或多个 catch 块。
如果有一个块抛出一个异常,捕获异常的方法会使用 try catch 关键字。 try 块中放置可能抛
出异常的代码, try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
try
{// 可能会抛异常的代码
}catch(  )
{// catch 块
}

2:异常的抛出和匹配原则

1. 异常是通过 抛出对象而引发 的,该 对象的类型 决定了应该激活哪个 catch 的处理代码。
对象:任意类型的实例。
2. 选中的catch处理代码是调用链中 与该对象类型匹配且离抛出异常位置最近 的那一个。
就近原则
3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch 以后销毁。(这里的处理类似于函数的传值返回)
4. catch(...) 可以捕获任意类型的异常,问题则是不知道异常错误是什么。
5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配, 可以抛出的派生类对象, 使用基类捕获!这个十分重要

对以上5点逐一举例:

原则1的例子:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}int main()
{int a = 0;int b = 0;cin >> a >> b;try{Division(a, b);}catch (const char* s){cout << __LINE__ << "捕获异常:" << s << endl;}catch (int s){cout << __LINE__ << "捕获异常:" << s << endl;}catch (double s){cout << __LINE__ << "捕获异常:" << s << endl;}}

运行结果:

解释:

即使我们有多个catch,但还是匹配到的389行的catch中的打印语句,所以对象的类型决定了应该激活哪个catch的处理代码。

原则2的例子:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}void func(int a, int b)
{try{Division(a, b);}catch (const char* s){cout << __LINE__ << "捕获异常:" << s << endl;cout << "由func内部的catch捕获" << endl;}
}int main()
{int a = 0;int b = 0;cin >> a >> b;try{func(a, b);}catch (const char* s){cout << __LINE__ << "捕获异常:" << s << endl;cout << "由main内部的catch捕获" << endl;}}

运行结果:

解释:

  1. 当 Division 抛出异常时,首先由 func 中的 catch 块处理

  2. 如果 func 中的 catch 块没有处理异常(比如捕获不同类型的异常)或者选择重新抛出(使用 throw;),那么异常会传递到 main 中的 catch 块

这正体现了"调用链中与该对象类型匹配且离抛出异常位置最近的那一个"的异常处理机制。

原则3的例子:

举例子前需要再理解一下原则3:

3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,
所以会生成一个拷贝对象,这个拷贝的临时对象会在被 catch 以后销毁。(这里的处理类似
于函数的传值返回)
说白了就是 当你在一个域中抛出了异常对象后,有可能是在域外进行捕获的;就类似与一个函数进行值返回的时候,你在main中接收这个值,该值会触发构造函数,所以同理的,在域外进行异常捕获也会让 抛出的异常对象 生成一个拷贝对象,才能让域外的catch成功捕获
所以下面代码就是一个自己实现的类(MyException),当抛出异常的对象是这个类的对象的时候,最起码会触发一次构造函数,这次构造函数就是throw语句去构造对象的,因为先不考虑你抛出的对象在哪个域被捕获,起码你得先有对象,所以第一个构造是throw造成的;现在当你的catch是在域外的时候,就应该是除开最开始的那一次构造,应该还有一次拷贝构造,那我们运行结果会如我们所愿吗?
#include <iostream>
using namespace std;class MyException 
{
public:MyException(const string& msg): message(msg){cout << "构造" << endl;}// 拷贝构造函数MyException(const MyException& other): message(other.message){cout << "拷贝构造" << endl;}private:string message;
};void func1()
{cout << "func1: 准备抛出异常..." << endl;throw MyException("这是一个异常"); // 这里会构造一个临时对象,然后拷贝构造一个新对象
}void func2()
{try{func1();}catch (const MyException& e){ // 这里用引用捕获,避免再次拷贝cout << "func2 捕获异常" << endl;}
}int main() {func2();return 0;
}

运行结果:

Q:为什么跨域异常捕获没有触发拷贝构造?

A:现代C++编译器(特别是C++17及以后版本)会对异常处理进行以下优化:

  • 拷贝省略(Copy Elision):编译器会直接在被调用的catch块处构造异常对象,跳过中间的拷贝步骤

  • 优化:编译器完全跳过拷贝/或移动,直接在目标位置构造对象

所以,知道原则3就行了,有时候例子也不一定能演示除出来

原则4例子:

#include <iostream>
#include <string>
using namespace std;void testFunction(int type)
{switch (type){case 1:throw 42;                // 抛出int类型case 2:throw 3.14;              // 抛出double类型case 3:throw string("hello");   // 抛出string类型case 4:throw runtime_error("error"); // 抛出标准异常}
}int main()
{for (int i = 1; i <= 4; i++){try{testFunction(i);}catch (...){  // 捕获所有类型的异常cout << "捕获到未知类型异常" << endl;}}
}

运行结果:

解释:

循环进行四个case,所产生的异常,都被main函数中的catch(...)捕获到了,但是也仅仅是捕获到了,不知道捕获的类型,所以无法打出具体的信息;

Q:那catch(...)的意义是什么,你只是捕获到了而已,我也不知道捕获到了什么啊?

A:C++中当有异常没被捕获,程序就会直接终止,所以一般catch(...) 是最后一层保险,任意类型的异常都可以匹配!比如,我们把上述例子的catch(...)换成catch(int x),这样就会有三个case抛出的异常无法被捕获,就会出现一下报错:

原则5的例子:

体现基类类型可以捕获派生类对象:

#include <iostream>
using namespace std;// 基类
class Base {};// 派生类
class Derived : public Base {};int main() 
{try {throw Derived(); // 抛出派生类对象}catch (Base&) {      // 用基类捕获cout << "成功用基类捕获了派生类对象" << endl;}
}

运行结果:

解释:抛出的是派生类对象,但是可以用基类进行捕获!

最后在五个原则之外呢,还有一个异常栈展开匹配原则:

1. 首先 检查 throw 本身是否在 try 块内部,如果是再查找匹配的 catch 语句 。如果有匹配的,则调到catch 的地方进行处理。
2. 没有匹配的 catch 则退出当前函数栈,继续在调用函数的栈中进行查找匹配的 catch
3. 如果到达 main 函数的栈,依旧没有匹配的,则终止程序 。上述这个沿着调用链查找匹配的
catch 子句的过程称为 栈展开 。所以实际中我们最后都要加一个 catch(...) 捕获任意类型的异
常,否则当有异常没捕获,程序就会直接终止。
4. 找到匹配的 catch 子句并处理以后,会继续沿着 catch 子句后面继续执行。
这不用单独讲,是一个常识性的问题,但是可以提现为什么要有原则4中 catch(...)的存在!

三:异常的使用场景

第三大点 是最重要的一点 要会多态才能理解!

多态博客:多态的原理 -CSDN博客

1:自定义异常体系

①:字符串前缀

上面说了这么多,好像还是无法提现到异常捕获在哪里会被使用,但其实原则5就是应用的场景!
也就是可以用过基类来捕获派生类的异常!
场景:
一个公司里面,往往会有多个小组,比如开发小组,测试小组....这里就假设有两个小组,现在对于除0错误,开发小组抛出一个string类对象(throw "Division by zero condition!"),而测试小组也抛出一样的对象(throw "Division by zero condition!"),此时当程序员去捕获异常的地方观测的时候,会分不清楚,到底是谁抛出的,那能不能 通过字符串前缀即可区分来源呢?
// 开发组代码
void devDivision(int a, int b) {if (b == 0) throw "开发组错误: Division by zero";// ...
}// 测试组代码
void testDivision(int a, int b) {if (b == 0) throw "测试组错误: Division by zero";// ...
}int main()
{// 捕获异常try{devDivision(1, 0);}catch (const char* errMsg) {cout << "捕获到异常: " << errMsg << endl;}return 0;
}

解释:

解决了分不清是哪个组抛出的异常的问题,但C++官方想到的场景远比我们复杂得多,所以我们自己想的办法有缺点:

缺点:
①:无法结构化处理错误
所有错误都是 const char*,无法附加额外信息(如错误码、堆栈跟踪等)。例如,开发组可能还想返回 错误发生时的变量值,而测试组想返回 测试用例ID,字符串无法优雅扩展。

②:容易拼写错误
如果开发组写 "开发组错误",测试组写 "测试错误",风格不统一,维护困难。每个抛出异常的地方,都要手动的加前缀,过于麻烦!无法强制统一接口。新成员可能直接抛 "Error: xxx",导致团队规范被破坏。

②:基类和派生类发

所以 公司里真正运用的是原则5 这一点!!!代码如下:


#include <iostream>
#include <string>
using namespace std;// 基类
class BaseException {
public:BaseException(const string& msg) : message(msg) {}// 虚函数,允许派生类重写virtual string what() const {return message;}// 虚析构函数(重要!)virtual ~BaseException() = default;protected:string message; // 存储错误信息
};// 开发组异常(派生类)
class DevException : public BaseException
{
public:DevException(const string& msg): BaseException("[Dev] " + msg) // 自动加前缀{}string what() const override{return message + " (错误码: 1001)";}
};// 测试组异常(派生类)
class TestException : public BaseException
{
public:TestException(const string& msg): BaseException("[Test] " + msg) // 自动加前缀{}
};// 示例函数
void devFunction(int x)
{if (x < 0)throw DevException("输入不能为负数");
}void testFunction(int x)
{if (x == 0)throw TestException("输入不能为零");
}int main()
{try{devFunction(-1); // 触发开发组异常// testFunction(0); // 触发测试组异常}catch (const BaseException& e){ // 用基类捕获所有派生类异常cout << "捕获到异常: " << e.what() << endl;}return 0;
}

运行结果:

解释:相对与 手动加前缀的方法 原则5优秀有几个点:

①:不用手动加前缀了,构造函数加一次就可以

②:我们可以有N多个组,新增的组继承基类就行,这样我们捕获的时候,catch中只用无脑写基类的类型就能识别出是哪个组抛出的异常

③:直接通过e.waht()来打印错误的信息,而waht()是一个虚函数,这样我们在不同的组里面,可以分别重写虚函数,打印出自己想打印的信息;比如开发组要要额外打印一个错误码,自己重写虚函数即可,而测试组不想打印额外的信息,直接不用重写虚函数,会自动调用基类的版本。

  • 开发组重写 what() 是因为需要 在基类信息基础上追加内容(错误码)。

  • 而测试组不想在基类的基础上追加内容,所以直接可以不重写虚函数

但是呢,C++早就知道我们自己实现基类,过于的麻烦,它连基类都实现好了,直接用库中的基类就行!

2:标准库异常体系

基类复用库中,简化后的代码:

#include <iostream>
#include <string>
#include <exception>
using namespace std;class DevException : public exception
{
public:DevException(const string& msg): message("[Dev] " + msg + " (错误码: 1001)") {}const char* what() const  override{return message.c_str();}private:string message;
};class TestException : public exception
{
public:TestException(const string& msg): message("[Test] " + msg) {}const char* what() const  override{return message.c_str();}private:string message;
};void devFunction(int x) {if (x < 0) throw DevException("输入不能为负数");
}void testFunction(int x) {if (x == 0) throw TestException("输入不能为零");
}int main() {try {devFunction(-1);// testFunction(0);}catch (const exception& e) {cout << "捕获到异常: " << e.what() << endl;}return 0;
}

运行结果:

解释:复用了库中的基类之后,会简单很多,但是呢也有需要注意的点

①:重写虚函数what的书写方式变了:返回值类型:const char*,也就是返回 C 风格字符串(历史原因,兼容 C 和早期 C++)

②:建议派生类重写what函数的时候,加上noexcept,否则可能产生警告(取决于编译器)

至此,异常的应用场景就讲完了❀~

3:对标准库的补充

标准库异常体系

C++标准库当中的异常也是一个基础体系,其中exception就是各个异常类的基类,我们可以在程序中使用这些标准的异常,它们之间的继承关系如下:

下表是对上面继承体系中出现的每个异常的说明:

说明一下:

  • exception类的what成员函数和析构函数都定义成了虚函数,方便子类对其进行重写,从而达到多态的效果。
  • 实际中我们也可以去继承exception类来实现自己的异常类,但实际中很多公司都会自己定义一套异常继承体系。
  • 其实 看起来这么多 我们也要不上大多数 这也是为什么会说异常体系定义得不够好,导致大家各自定义自己的异常体系,非常的混乱

 

四:异常安全

将抛异常导致的安全问题叫做异常安全问题,对于异常安全问题下面给出几点建议:

①:构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。

②:析构函数主要完成对象资源的清理,最好不要在析构函数中抛出异常,否则可能导致资源泄露(内存泄露、句柄未关闭等)。

③:C++中异常经常会导致资源泄露的问题,比如在new和delete中抛出异常,导致内存泄露,在lock和unlock之间抛出异常导致死锁,C++经常使用RAII(智能指针)的方式来解决以上问题。

其实③这种问题的本质原因,是因为我们在引入中提到的跨函数传播->异常可自动跨多层函数调用栈向上传递,无需逐层检查返回值。一个好处也会带来了一个坏处~

总结:

①②两点可以自己规避,但③要智能指针来解决,博主在C++专栏下一个博客会讲解

五:异常的重新抛出

异常的重新抛出就是为了解决和四大点中的③小点资源泄露的,但是无法完全解决,所以才需要智能指针,下面一步一步的举例,体现异常的重新抛出的作用以及无奈之处,引出智能指针的存在

有时候单个的catch可能不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理

1:异常重新抛出的场景

场景:

单个的catch会出错:

void func1()
{throw string("这是一个异常");
}
void func2()
{int* array = new int[10];func1();//do something...delete[] array;
}
int main()
{try{func2();}catch (const string& s){cout << s << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

解释:

其中func2中通过new操作符申请了一块内存空间,并且在func2最后通过delete对该空间进行了释放,但由于func2中途调用的func1内部抛出了一个异常,这时会直接跳转到main函数中的catch块执行对应的异常处理程序,并且在处理完后继续沿着catch块往后执行。

这时就导致func2中申请的内存块没有得到释放,造成了内存泄露。

所以,这种时候,就需要进行异常的重新抛出来避免了内存泄泄露,来解决问题:

void func2()
{int* array = new int[10];try{func1();//do something...}catch (...){delete[] array;throw; //将捕获到的异常再次重新抛出}delete[] array;
}

 解释:

①:func2中的new和delete之间可能还会抛出其他类型的异常,因此在fun2中最好以catch(...)的方式进行捕获,将申请到的内存delete后再通过throw重新抛出。

②:重新抛出异常对象时,throw后面可以不用指明要抛出的异常对象(正好也不知道以catch(...)的方式捕获到的具体是什么异常对象)。

2:重新抛出也无法解决的场景

但是呢会有一些场景,即使是用异常的重新抛出也难以解决,比如:

#include <iostream>
using namespace std;void riskyOperation() {int* ptr1 = new int(100);  // 内存1int* ptr2 = new int(200);  // 内存2int* ptr3 = new int(300);  // 内存3// 模拟后续操作抛出异常throw runtime_error("操作失败");// 正常释放(永远不会执行)delete ptr1;delete ptr2;delete ptr3;
}int main() {try {riskyOperation();} catch (const exception& e) {cerr << "错误: " << e.what() << endl;// 问题:ptr1/ptr2/ptr3 内存泄漏!}
}

此时我们想用异常的重新抛出来解决,会发现,太麻烦了~~~

void riskyOperation() {int* ptr1 = nullptr;int* ptr2 = nullptr;int* ptr3 = nullptr;try {ptr1 = new int(100);ptr2 = new int(200);ptr3 = new int(300);throw runtime_error("操作失败");delete ptr1;delete ptr2;delete ptr3;} catch (...) {// 必须按申请顺序反向释放!delete ptr3;delete ptr2;delete ptr1;throw;  // 重新抛出}
}

缺陷

  • 需要 严格反向释放ptr3 → ptr2 → ptr1),否则可能漏掉。

  • 每增加一个 new,就要多写一个 delete,维护成本高。

3:异常规范

异常规范是C++官方在智能指针产生前,看见重新抛出也无法解决的场景,决定进行异常的规范,来避免这种场景,但仍然无法完全避免,所以有了智能指针

为了让函数使用者知道某个函数可能抛出哪些类型的异常,C++标准规定:

在函数的后面接throw(type1, type2, ...),列出这个函数可能抛掷的所有异常类型。
在函数的后面接throw()或noexcept(C++11),表示该函数不抛异常。
若无异常接口声明,则此函数可以抛掷任何类型的异常。(异常接口声明不是强制的)

//表示func函数可能会抛出A/B/C/D类型的异常
void func() throw(A, B, C, D);
//表示这个函数只会抛出bad_alloc的异常
void* operator new(std::size_t size) throw(std::bad_alloc);
//表示这个函数不会抛出异常
void* operator new(std::size_t size, void* ptr) throw();

虽然C++官方对进行了异常规范,但是不治本 ,比如:

①:你认为他不会抛异常,但他出现了异常

②:一个函数的确不会抛异常 但是它内部调用的函数会抛异常

③:并且异常规范这个东西 也不是强制的 ,只是建议 ,因为c语言的旧代码没加异常规范,要满足向上兼容

④: 你可能会抛异常 abcd  但只写了abc

但是呢都是治标不治本啊,所以有了智能指针  达到了治标且治本的效果 
 

六:异常的优缺点

1:异常的优点:


①:异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用等信息,这样可以帮助更好的定位程序的bug。

②:返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误码,最终最外层才能拿到错误。

③:很多的第三方库都会使用异常,比如boost、gtest、gmock等等常用的库,如果我们不用异常就不能很好的发挥这些库的作用。

④:很多测试框架也都使用异常,因此使用异常能更好的使用单元测试等进行白盒的测试。

⑤:部分函数使用异常更好处理,比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

2:异常的缺点

①:异常会导致程序的执行流乱跳,并且非常的混乱,这会导致我们跟踪调试以及分析程序时比较困难。

②:异常会有一些性能的开销,当然在现代硬件速度很快的情况下,这个影响基本忽略不计。

③:C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄露、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题,学习成本比较高。

④:C++标准库的异常体系定义得不够好,导致大家各自定义自己的异常体系,非常的混乱。

⑤:异常尽量规范使用,否则后果不堪设想,随意抛异常,也会让外层捕获的用户苦不堪言。

⑥:异常接口声明不是强制的,对于没有声明异常类型的函数,无法预知该函数是否会抛出异常。

但总体而言,异常的利大于弊,所以工程中我们还是鼓励使用异常的,并且大多数的语言基本都使用异常处理错误,这也可以看出这是大势所趋。

 

相关文章:

C++的异常

引入&#xff1a;异常的意义是什么&#xff1f; ①&#xff1a;错误分离 将正常逻辑&#xff08;try&#xff09;与错误处理&#xff08;catch&#xff09;分离&#xff0c;避免代码被大量 if-else 污染。 ②&#xff1a;强制处理 若不捕获异常&#xff0c;程序终止&#xff0c…...

精益制造数字化转型智能工厂三年规划建设方案

该文档是精益制造数字化转型智能工厂三年规划建设方案,以打造高品质、低成本、柔性化的绿色智能工厂为愿景,围绕制造技术、自动化、数智化、管理赋能四大路径,通过夯实 EHS、品质一致性、生产安定化、现场整洁四大基石,推进标杆车间打造、联合管理、TOB 流程改善等专项。规…...

Linux 文件(3)

文章目录 1. Linux下一切皆文件2. 文件缓冲区2.1 缓冲区是什么2.2 缓冲区的刷新策略2.3 为什么要有缓冲区2.4 一个理解缓冲区刷新的例子 3. 标准错误 1. Linux下一切皆文件 在刚开始学习Linux的时候&#xff0c;我们就说Linux下一切皆文件——键盘是文件&#xff0c;显示器是文…...

Java异步编程利器:CompletableFuture 深度解析与实战

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、CompletableFuture 概述 CompletableFuture是Java 8引入的异步编程工具类&#xff0c;实现了Future和CompletionStage接口&#xff0c;支持链式调用、组…...

如何支持Enhanced RTMP H.265(HEVC)

在实时音视频传输中&#xff0c;H.264长期占据主流&#xff0c;但随着视频质量要求的不断提高和带宽压力的加大&#xff0c;H.265&#xff08;HEVC&#xff09;作为下一代视频编码标准逐渐崭露头角。 在这种背景下&#xff0c;我们顺应行业发展趋势&#xff0c;成功集成了对Enh…...

Idea 查找引用jar包依赖来源的Maven pom坐标

目录 问题引入 实现解决 问题引入&#xff1a; 在查看拉取的项目&#xff0c;维护自己项目、或者迁移原有项目时&#xff0c;会遇到不知道代码中引用到的依赖从哪里引用到的。 所以利用Idea&#xff0c;从import语句到Maven项目结构树中查找&#xff0c;最终找到pom文件里的…...

Linux操作系统之进程(二):进程状态

目录 前言 一、补充知识点 1、并行与并发 2、时间片 3、 等待的本质 4、挂起 二. 进程的基本状态 三、代码演示 1、R与S 2、T 3、Z 四、孤儿进程 总结&#xff1a; 前言 在操作系统中&#xff0c;进程是程序执行的基本单位。每个进程都有自己的状态&#xff0c;这些…...

web.py使用时报错AttributeError: No template named image_window

在使用python的web.py框架做前后端时遇到问题。 问题代码主要如下&#xff0c;当加上main(iamge_name)这行代码后就会报错。报错信息包含两个&#xff1a;第一是找不到image_window模板&#xff1b;第二是gbk无法解码... class ImageWindow:def GET(self, image_name):main(i…...

2025年度消费新潜力白皮书470+份汇总解读|附PDF下载

原文链接&#xff1a;https://tecdat.cn/?p42178 过去一年&#xff0c;消费市场在政策驱动与技术迭代中呈现结构性变革。社零总额达487,895亿元&#xff0c;实物商品网零额占比27%&#xff0c;线上渠道成为增长引擎。本报告从食品饮料、美妆护肤、家电数码、服饰户外四大核心领…...

全平台开源电子书阅读器推荐,支持多端同步+AI朗读!支持epub/mobi/azw3/pdf常见电子书格式!

Readest是一款好用的免费阅读工具&#xff0c;界面干净不花哨&#xff0c;特别适合喜欢专心读书的朋友。这个软件是经典阅读软件Foliate的全新升级版本&#xff0c;用最新技术开发&#xff0c;能在手机、电脑&#xff08;包括苹果和Windows系统&#xff09;以及网页上顺畅使用。…...

创建Workforce

创建你的Workforce 3.3.1 简单实践 1. 创建 Workforce 实例 想要使用 Workforce&#xff0c;首先需要创建一个 Workforce 实例。下面是最简单的示例&#xff1a; from camel.agents import ChatAgent from camel.models import ModelFactory from camel.types import Model…...

关于光谱相机的灵敏度

一、‌灵敏度的核心定义‌ ‌光谱灵敏度&#xff08;单色灵敏度&#xff09;‌ 描述光谱相机对单色辐射光的响应能力&#xff0c;即探测器对特定波长入射光的输出信号强度与入射光功率的比值。 例如&#xff0c;若在680nm波长下的光谱灵敏度较高&#xff0c;则表示该相机对此…...

【Redis】二、Redis常用数据类型命令学习

目录 一、String 1. SET、GET&#xff1a;设置与读取键值对&#xff1a; 2. DEL&#xff1a;删除键 3. INCR、DECR&#xff1a;自增 / 自减&#xff08;常用于计数器&#xff09; 4. APPEND&#xff1a;内容追加 5. EXPIRE&#xff1a;设置过期时间 / 查看剩余时间&#x…...

HarmonyOS基础组件:Button三种类型的使用

简介 HarmonyOS在明年将正式不再兼容Android原生功能&#xff0c;这意味着对于客户端的小伙伴不得不开始学习HarmonyOS开发语言。本篇文章主要介绍鸿蒙中的Button使用。 HarmonyOS中的Button相较于Android原生来说&#xff0c;功能比较丰富&#xff0c;扩展性高&#xff0c;减…...

RT_Thread——快速入门

文章目录 一、RT-Thread 目录结构二、核心文件三、移植时涉及的文件3.1 CPU 部分3.2 BSP 部分 四、内存管理五、启动流程及main函数5.1 启动流程5.2 关键函数速览5.3 main 函数示例 六、数据类型和编程规范6.1 数据类型6.2 函数名6.3 结构体定义6.4 注释规范 七、使用模拟器运行…...

Java 参数值传递机制

一个很经典的问题: java的方法入参 是值传递还是地址传递&#xff1f; 答案是&#xff1a;值传递。 今天排查一个生产问题&#xff0c;数据库链接资源没有关闭。 大致代码逻辑如下&#xff1a; try{Preparestatement ps null;String sql "select * from tableA wher…...

Redis 的 key 的过期策略是怎么实现的

在 Redis 中&#xff0c;有一个 expire 命令&#xff0c;用来设置某个 key 的过期时间&#xff0c;当超过这个时间后&#xff0c;这个 key 就被删除了&#xff0c;我们也就获取不到了&#xff0c;但是 Redis 是如何做到对于每一个设置了过期时间的 key 都能按时删除的呢&#x…...

ROG NUC 2025 :狂暴而冷静的小猛兽

在今年1 月的 CES 展会上&#xff0c;华硕首次披露了ROG NUC 2025&#xff0c;就以突破性紧凑设计桌面级超强性能配置&#xff0c;引发全球科技媒体和游戏爱好者的热议。蛰伏数月&#xff0c;蓄力进化&#xff01; 华硕自承接英特尔NUC产品线以来&#xff0c;就一直致力于重塑迷…...

origin绘图之【如何将多条重叠、高度重叠的点线图、折线图分开】

在使用 Origin 进行数据可视化时&#xff0c;尤其是在绘制多组数据的折线图或点线图时&#xff0c;我们经常会遇到这样的问题&#xff1a;多条曲线重叠严重&#xff0c;难以区分&#xff0c;导致图形信息密集、可读性差&#xff0c;影响图表的传达效果。 那么&#xff0c;我们该…...

2025第一届轩辕杯--Crypto--WriteUp

2025第一届轩辕杯–Crypto–WriteUp Crypto easyrsa task e 65537 n 1000000000000000000000000000156000000000000000000000000005643 c 418535905348643941073541505434424306523376401168593325605206exp from Crypto.Util.number import inverse, long_to_bytese …...

人工智能范式:技术革命下的认知重构

当生成式AI能够自主创作内容、设计解决方案甚至编写程序时&#xff0c;我们正在见证的不仅是工具革新&#xff0c;更是一场认知范式的根本转变。人工智能范式正在重塑人类理解世界、解决问题和创造价值的基本方式——这种转变将重新定义未来十年的职业逻辑与知识体系。 一、范…...

python训练营打卡第30天

模块和库的导入 知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 一、导入官方库 1.标准导入&#xff1a;导入整个库 import mathprint(&quo…...

第29天-python实现mysql数据增删改查

想用Python和Tkinter实现一个MySQL数据库的增删改查应用。首先,我需要确定用户的需求是什么。他们可能想要一个图形界面,方便操作数据库,而不需要直接写SQL语句。用户可能对Python和Tkinter有一定了解,但对如何整合数据库操作可能不太熟悉。 首先,我应该考虑如何设计界面。…...

2025.05.21华为暑期实习机考真题解析第三题

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. GPU资源租赁优化 问题描述 A先生是一家云计算服务商的资源调度负责人,负责管理公司的GPU资源租赁业务。公司拥有多个高性能GPU核心,并按时间段出租给不同客户使用。每个客户有…...

Datawhale 5月llm-universe 第4次笔记

第四章 构建RAG应用 envs 在 Conda 中&#xff0c;envs 目录是用来存放虚拟环境的地方。 也就是说&#xff0c;你在运行&#xff1a; onda create -n llm-universe python3.10 时&#xff0c;Conda 就会在这个路径下创建一个新的文件夹&#xff1a; makefile D:\Users\…...

滑窗问题实验LC2653(一次遍历维持窗口元素保持排序)

在只有一次遍历&#xff08;即滑窗每向右移动一步只处理新增元素和删除旧元素&#xff09;的前提下&#xff0c;要维持当前窗口内元素的全局“可排序”结构 问题背景&#xff1a;LeetCode 2653 - 滑动子数组的美丽值 题目要求在大小为 k 的滑动窗口中&#xff0c;找到第 x 小…...

PHP学习笔记(八)

返回值 值通过可选参数的返回语句返回 return的使用 函数不能返回多个值&#xff0c;但可以通过返回一个数组来得到类似的效果 函数返回一个引用&#xff0c;必须在函数声明和指派返回值给一个变量时都使用引用运算符&&#xff1a; 可变函数 PHP支持可变函数的概念。意味…...

【react18】在styled-components中引入图片报错

在styled-components项目中&#xff0c;遇到背景图片显示不出来的问题。图片的确是引入正确&#xff0c;但是webpack解析路径是有问题的 效果展示 以下这两种写法都不行&#xff0c;无法生效 export const HeaderNavLeft styled.h1width: 176px;height: 69px;background: ur…...

693SJBH基于.NET的题库管理系统

计算机与信息学院 本科毕业论文&#xff08;设计&#xff09;开题报告 论文中文题目 基于asp.net的题库管理系统设计与实现 论文英文题目 Asp.net based database management system design and Implementation 学生姓名 专业班级 XXXXXX专业08 班 ⒈选题的背景和意…...

centos系统redis-dump安装

1. ​Ruby 环境​ Redis-dump 是一个 Ruby 工具&#xff0c;需先安装 Ruby 和 RubyGems。 安装依赖​&#xff1a; sudo yum install -y curl gpg2 gcc-c patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf aut…...

如何利用 Conda 安装 Pytorch 教程 ?

如何利用 Conda 安装 Pytorch 教程 &#xff1f; 总共分为六步走&#xff1a; &#xff08;1&#xff09;第一步&#xff1a;验证conda 环境是否安装好&#xff1f; 1) conda -V2) conda --version&#xff08;2&#xff09;第二步&#xff1a;查看现有环境 conda env list…...

FPGA降低功耗研究

FPGA降低功耗研究 首先要明白一点&#xff1a;我们的核心目标是在维持性能的前提下&#xff0c;通过工艺、架构、设计方法学和系统级策略的协同优化&#xff0c;降低动态功耗、静态功耗和短路功耗。 本篇文章则是聚焦于 FPGA 设计阶段 的功耗优化&#xff0c;主要从 RTL 代码设…...

软考 系统架构设计师系列知识点之杂项集萃(67)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;66&#xff09; 第108题 RISC&#xff08;精简指令系统计算机&#xff09;的特点不包括&#xff08;&#xff09;。 A. 指令长度固定&#xff0c;指令种类尽量少 B. 寻址方式尽量丰富&#xff…...

第十节第三部分:常见API:传统时间:Date日期类、SimpleDateFormat

Date日期类常用方法 时间格式常用符号 Date日期类总结 为什么用SimpleDateFormat SimpleDateFormat常见方法 SimpleDateFormat解析字符串时间成为日期对象 SimpleDateFormat总结 代码&#xff1a; 代码一&#xff1a;Date日期类 package com.itheima.Time;import java.util.D…...

Python学习Day1:安装

Python的安装 1.安装python 打开python的官网&#xff0c;我们下载3.6.8版本Python Release Python 3.6.8 | Python.org 根据自己的电脑来下载对应的python版本 如图所示&#xff1a; 我已经下载完成&#xff0c;就是这样&#xff0c; 安装页面的第一排是自动安装&#xf…...

Java安全-Servlet内存马

内存马简介 内存马是指将恶意代码注入到内存中&#xff0c;达到无文件落地的效果&#xff0c;使得被攻击方难以察觉。由于是无文件的形式&#xff0c;可以绕过部分基于文件检测的杀软。而 Servlet 内存马是基于 Java Servlet 技术&#xff0c;动态将恶意代码注入到 Tomcat 内存…...

Mariadb cpu 93% 问题

最近项目遇到cpu平均使用率93% 一、登录数据库服务查看具体情况 可以看到服务器负载很高&#xff0c;&#xff0c;mariadb CPU使用已达到接近380.4% &#xff08;因为是8核&#xff0c;所以会有超过100%的情况&#xff09;。如下图&#xff1a; 二、排查是否有耗时较长sql 在…...

LeetCode222_完全二叉树的结点个数

LeetCode222_完全二叉树的结点个数 标签&#xff1a;#位运算 #树 #二分查找 #二叉树Ⅰ. 题目Ⅱ. 示例 0. 个人方法 标签&#xff1a;#位运算 #树 #二分查找 #二叉树 Ⅰ. 题目 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&…...

linux 查看java的安装路径

一、验证Java安装状态 java -version正常安装会显示版本信息&#xff1a; openjdk version "1.8.0_65" OpenJDK Runtime Environment (build 1.8.0_65-b17) OpenJDK 64-Bit Server VM (build 25.65-b01, mixed mode)二、检查环境变量配置 若已配置JAVA_HOME&#…...

day32 python解释性库PDPbox

目录 一、初识PDPbox官方文档 二、准备鸢尾花数据集和训练模型 三、使用PDPbox进行解释性分析 1. 导入类并实例化对象 2. 调用plot方法绘制图形 3. 探索返回值的意义 四、总结与感悟 作为一名正在学习机器学习的学生&#xff0c;今天要学习一个非常有趣的库——PDPbox&am…...

LVLM-AFAH论文精读

Basic Information 标题&#xff1a;Your Large Vision-Language Model Only Needs A Few Attention Heads For Visual Grounding作者&#xff1a;Seil Kang&#xff0c; Jinyeong Kim&#xff0c; Junhyeok Kim&#xff0c; Seong Jae Hwang机构&#xff1a;Yonsei Universit…...

蓝桥杯3503 更小的数

问题描述 小蓝有一个长度均为 n 且仅由数字字符 0∼9 组成的字符串&#xff0c;下标从 0 到 n−1&#xff0c;你可以将其视作是一个具有 n 位的十进制数字 num&#xff0c;小蓝可以从 num 中选出一段连续的子串并将子串进行反转&#xff0c;最多反转一次。 小蓝想要将选出的子…...

5.21本日总结

一、英语 复习list4list26 二、数学 学完14讲&#xff0c;1000题13讲写完 三、408 学习计网5.3剩余内容 四、总结 高数本月结束知识点学习&#xff0c;15讲知识点与14讲的题目同步进行。408剩余两本书要加快学习进度。 五、明日计划 英语&#xff1a;复习lsit5list25 …...

【25软考网工】第七章(3) UOS Linux防火墙配置和Web应用服务配置

博客主页&#xff1a;christine-rr-CSDN博客 ​​​专栏主页&#xff1a;软考中级网络工程师笔记 ​​​​ 大家好&#xff0c;我是christine-rr !目前《软考中级网络工程师》专栏已经更新三十多篇文章了&#xff0c;每篇笔记都包含详细的知识点&#xff0c;希望能帮助到你&am…...

Python数据分析基础

Python数据分析入门 介绍 在这个教程中&#xff0c;我们将学习如何使用Python来进行基本的数据分析。 安装必要的库 为了开始&#xff0c;你需要安装以下Python库&#xff1a; NumPyPandasMatplotlib 示例代码 import numpy as np import pandas as pd import matplotli…...

spring cloud config更新配置

在开发微服务时&#xff0c;往往需要有开发环境、测试环境和生产环境&#xff0c;手动修改配置环境是一件很麻烦的事情&#xff0c;因此&#xff0c;这里使用spring cloud config管理配置环境。要使用spring cloud config&#xff0c;需要先在GitHub搭建一个仓库。 一、仓库搭…...

小米汽车二期工厂下月将竣工,产能提升助力市场拓展

在新能源汽车市场竞争日益激烈的当下&#xff0c;小米汽车传来重要进展消息。据多方信息显示&#xff0c;小米汽车二期工厂下月即将竣工&#xff0c;这一关键节点的到来&#xff0c;有望为小米汽车的产能提升与市场布局带来重大突破。​ 小米汽车二期工厂位于北京亦庄&#xff…...

【单片机】如何产生负电压?

以下是对知乎文章《单片机中常用的负电压是这样产生的&#xff01;》的解析与总结&#xff0c;结合电路原理、应用场景及讨论要点展开&#xff1a; 一、负电压产生的核心原理 负电压本质是相对于参考地&#xff08;GND&#xff09;的电势差为负值&#xff0c;需通过电源或储能…...

Mcu_Bsdiff_Upgrade

系统架构 概述 MCU BSDiff 升级系统通过使用二进制差分技术&#xff0c;提供了一种在资源受限的微控制器上进行高效固件更新的机制。系统不传输和存储完整的固件映像&#xff0c;而是只处理固件版本之间的差异&#xff0c;从而显著缩小更新包并降低带宽要求。 该架构遵循一个…...

阿里云ecs 8核 16G 内存 装有redis6 分配了3G内存,和2个tomcat 每个tomcat 4G 服务器反应迟钝,如何确认不是redis的问题

我们经常用redis 但遇到tomcat timeout的时候&#xff0c;如何确认不是redis的问题。 不能因为这个再去搞个redis 压力测试。有没有更省劲的方法来确认不是redis的问题 以下是针对 阿里云 ECS&#xff08;8核16G&#xff0c;Redis 6分配3G内存&#xff0c;2个Tomcat各分配4G&a…...