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:关键字的介绍
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;}}
运行结果:
解释:
-
当
Division
抛出异常时,首先由func
中的catch
块处理 -
如果
func
中的catch
块没有处理异常(比如捕获不同类型的异常)或者选择重新抛出(使用throw;
),那么异常会传递到main
中的catch
块
这正体现了"调用链中与该对象类型匹配且离抛出异常位置最近的那一个"的异常处理机制。
原则3的例子:
举例子前需要再理解一下原则3:
3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被 catch 以后销毁。(这里的处理类似于函数的传值返回)说白了就是 当你在一个域中抛出了异常对象后,有可能是在域外进行捕获的;就类似与一个函数进行值返回的时候,你在main中接收这个值,该值会触发构造函数,所以同理的,在域外进行异常捕获也会让 抛出的异常对象 生成一个拷贝对象,才能让域外的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:自定义异常体系
①:字符串前缀
// 开发组代码
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++的异常
引入:异常的意义是什么? ①:错误分离 将正常逻辑(try)与错误处理(catch)分离,避免代码被大量 if-else 污染。 ②:强制处理 若不捕获异常,程序终止,…...
精益制造数字化转型智能工厂三年规划建设方案
该文档是精益制造数字化转型智能工厂三年规划建设方案,以打造高品质、低成本、柔性化的绿色智能工厂为愿景,围绕制造技术、自动化、数智化、管理赋能四大路径,通过夯实 EHS、品质一致性、生产安定化、现场整洁四大基石,推进标杆车间打造、联合管理、TOB 流程改善等专项。规…...
Linux 文件(3)
文章目录 1. Linux下一切皆文件2. 文件缓冲区2.1 缓冲区是什么2.2 缓冲区的刷新策略2.3 为什么要有缓冲区2.4 一个理解缓冲区刷新的例子 3. 标准错误 1. Linux下一切皆文件 在刚开始学习Linux的时候,我们就说Linux下一切皆文件——键盘是文件,显示器是文…...
Java异步编程利器:CompletableFuture 深度解析与实战
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、CompletableFuture 概述 CompletableFuture是Java 8引入的异步编程工具类,实现了Future和CompletionStage接口,支持链式调用、组…...
如何支持Enhanced RTMP H.265(HEVC)
在实时音视频传输中,H.264长期占据主流,但随着视频质量要求的不断提高和带宽压力的加大,H.265(HEVC)作为下一代视频编码标准逐渐崭露头角。 在这种背景下,我们顺应行业发展趋势,成功集成了对Enh…...
Idea 查找引用jar包依赖来源的Maven pom坐标
目录 问题引入 实现解决 问题引入: 在查看拉取的项目,维护自己项目、或者迁移原有项目时,会遇到不知道代码中引用到的依赖从哪里引用到的。 所以利用Idea,从import语句到Maven项目结构树中查找,最终找到pom文件里的…...
Linux操作系统之进程(二):进程状态
目录 前言 一、补充知识点 1、并行与并发 2、时间片 3、 等待的本质 4、挂起 二. 进程的基本状态 三、代码演示 1、R与S 2、T 3、Z 四、孤儿进程 总结: 前言 在操作系统中,进程是程序执行的基本单位。每个进程都有自己的状态,这些…...
web.py使用时报错AttributeError: No template named image_window
在使用python的web.py框架做前后端时遇到问题。 问题代码主要如下,当加上main(iamge_name)这行代码后就会报错。报错信息包含两个:第一是找不到image_window模板;第二是gbk无法解码... class ImageWindow:def GET(self, image_name):main(i…...
2025年度消费新潜力白皮书470+份汇总解读|附PDF下载
原文链接:https://tecdat.cn/?p42178 过去一年,消费市场在政策驱动与技术迭代中呈现结构性变革。社零总额达487,895亿元,实物商品网零额占比27%,线上渠道成为增长引擎。本报告从食品饮料、美妆护肤、家电数码、服饰户外四大核心领…...
全平台开源电子书阅读器推荐,支持多端同步+AI朗读!支持epub/mobi/azw3/pdf常见电子书格式!
Readest是一款好用的免费阅读工具,界面干净不花哨,特别适合喜欢专心读书的朋友。这个软件是经典阅读软件Foliate的全新升级版本,用最新技术开发,能在手机、电脑(包括苹果和Windows系统)以及网页上顺畅使用。…...
创建Workforce
创建你的Workforce 3.3.1 简单实践 1. 创建 Workforce 实例 想要使用 Workforce,首先需要创建一个 Workforce 实例。下面是最简单的示例: from camel.agents import ChatAgent from camel.models import ModelFactory from camel.types import Model…...
关于光谱相机的灵敏度
一、灵敏度的核心定义 光谱灵敏度(单色灵敏度) 描述光谱相机对单色辐射光的响应能力,即探测器对特定波长入射光的输出信号强度与入射光功率的比值。 例如,若在680nm波长下的光谱灵敏度较高,则表示该相机对此…...
【Redis】二、Redis常用数据类型命令学习
目录 一、String 1. SET、GET:设置与读取键值对: 2. DEL:删除键 3. INCR、DECR:自增 / 自减(常用于计数器) 4. APPEND:内容追加 5. EXPIRE:设置过期时间 / 查看剩余时间&#x…...
HarmonyOS基础组件:Button三种类型的使用
简介 HarmonyOS在明年将正式不再兼容Android原生功能,这意味着对于客户端的小伙伴不得不开始学习HarmonyOS开发语言。本篇文章主要介绍鸿蒙中的Button使用。 HarmonyOS中的Button相较于Android原生来说,功能比较丰富,扩展性高,减…...
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的方法入参 是值传递还是地址传递? 答案是:值传递。 今天排查一个生产问题,数据库链接资源没有关闭。 大致代码逻辑如下: try{Preparestatement ps null;String sql "select * from tableA wher…...
Redis 的 key 的过期策略是怎么实现的
在 Redis 中,有一个 expire 命令,用来设置某个 key 的过期时间,当超过这个时间后,这个 key 就被删除了,我们也就获取不到了,但是 Redis 是如何做到对于每一个设置了过期时间的 key 都能按时删除的呢&#x…...
ROG NUC 2025 :狂暴而冷静的小猛兽
在今年1 月的 CES 展会上,华硕首次披露了ROG NUC 2025,就以突破性紧凑设计桌面级超强性能配置,引发全球科技媒体和游戏爱好者的热议。蛰伏数月,蓄力进化! 华硕自承接英特尔NUC产品线以来,就一直致力于重塑迷…...
origin绘图之【如何将多条重叠、高度重叠的点线图、折线图分开】
在使用 Origin 进行数据可视化时,尤其是在绘制多组数据的折线图或点线图时,我们经常会遇到这样的问题:多条曲线重叠严重,难以区分,导致图形信息密集、可读性差,影响图表的传达效果。 那么,我们该…...
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能够自主创作内容、设计解决方案甚至编写程序时,我们正在见证的不仅是工具革新,更是一场认知范式的根本转变。人工智能范式正在重塑人类理解世界、解决问题和创造价值的基本方式——这种转变将重新定义未来十年的职业逻辑与知识体系。 一、范…...
python训练营打卡第30天
模块和库的导入 知识点回顾: 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑:找到根目录(python解释器的目录和终端的目录不一致) 一、导入官方库 1.标准导入:导入整个库 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 中,envs 目录是用来存放虚拟环境的地方。 也就是说,你在运行: onda create -n llm-universe python3.10 时,Conda 就会在这个路径下创建一个新的文件夹: makefile D:\Users\…...
滑窗问题实验LC2653(一次遍历维持窗口元素保持排序)
在只有一次遍历(即滑窗每向右移动一步只处理新增元素和删除旧元素)的前提下,要维持当前窗口内元素的全局“可排序”结构 问题背景:LeetCode 2653 - 滑动子数组的美丽值 题目要求在大小为 k 的滑动窗口中,找到第 x 小…...
PHP学习笔记(八)
返回值 值通过可选参数的返回语句返回 return的使用 函数不能返回多个值,但可以通过返回一个数组来得到类似的效果 函数返回一个引用,必须在函数声明和指派返回值给一个变量时都使用引用运算符&: 可变函数 PHP支持可变函数的概念。意味…...
【react18】在styled-components中引入图片报错
在styled-components项目中,遇到背景图片显示不出来的问题。图片的确是引入正确,但是webpack解析路径是有问题的 效果展示 以下这两种写法都不行,无法生效 export const HeaderNavLeft styled.h1width: 176px;height: 69px;background: ur…...
693SJBH基于.NET的题库管理系统
计算机与信息学院 本科毕业论文(设计)开题报告 论文中文题目 基于asp.net的题库管理系统设计与实现 论文英文题目 Asp.net based database management system design and Implementation 学生姓名 专业班级 XXXXXX专业08 班 ⒈选题的背景和意…...
centos系统redis-dump安装
1. Ruby 环境 Redis-dump 是一个 Ruby 工具,需先安装 Ruby 和 RubyGems。 安装依赖: 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 教程 ? 总共分为六步走: (1)第一步:验证conda 环境是否安装好? 1) conda -V2) conda --version(2)第二步:查看现有环境 conda env list…...
FPGA降低功耗研究
FPGA降低功耗研究 首先要明白一点:我们的核心目标是在维持性能的前提下,通过工艺、架构、设计方法学和系统级策略的协同优化,降低动态功耗、静态功耗和短路功耗。 本篇文章则是聚焦于 FPGA 设计阶段 的功耗优化,主要从 RTL 代码设…...
软考 系统架构设计师系列知识点之杂项集萃(67)
接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(66) 第108题 RISC(精简指令系统计算机)的特点不包括()。 A. 指令长度固定,指令种类尽量少 B. 寻址方式尽量丰富ÿ…...
第十节第三部分:常见API:传统时间:Date日期类、SimpleDateFormat
Date日期类常用方法 时间格式常用符号 Date日期类总结 为什么用SimpleDateFormat SimpleDateFormat常见方法 SimpleDateFormat解析字符串时间成为日期对象 SimpleDateFormat总结 代码: 代码一:Date日期类 package com.itheima.Time;import java.util.D…...
Python学习Day1:安装
Python的安装 1.安装python 打开python的官网,我们下载3.6.8版本Python Release Python 3.6.8 | Python.org 根据自己的电脑来下载对应的python版本 如图所示: 我已经下载完成,就是这样, 安装页面的第一排是自动安装…...
Java安全-Servlet内存马
内存马简介 内存马是指将恶意代码注入到内存中,达到无文件落地的效果,使得被攻击方难以察觉。由于是无文件的形式,可以绕过部分基于文件检测的杀软。而 Servlet 内存马是基于 Java Servlet 技术,动态将恶意代码注入到 Tomcat 内存…...
Mariadb cpu 93% 问题
最近项目遇到cpu平均使用率93% 一、登录数据库服务查看具体情况 可以看到服务器负载很高,,mariadb CPU使用已达到接近380.4% (因为是8核,所以会有超过100%的情况)。如下图: 二、排查是否有耗时较长sql 在…...
LeetCode222_完全二叉树的结点个数
LeetCode222_完全二叉树的结点个数 标签:#位运算 #树 #二分查找 #二叉树Ⅰ. 题目Ⅱ. 示例 0. 个人方法 标签:#位运算 #树 #二分查找 #二叉树 Ⅰ. 题目 给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。 完全二叉树 的定义如下&…...
linux 查看java的安装路径
一、验证Java安装状态 java -version正常安装会显示版本信息: 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. 探索返回值的意义 四、总结与感悟 作为一名正在学习机器学习的学生,今天要学习一个非常有趣的库——PDPbox&am…...
LVLM-AFAH论文精读
Basic Information 标题:Your Large Vision-Language Model Only Needs A Few Attention Heads For Visual Grounding作者:Seil Kang, Jinyeong Kim, Junhyeok Kim, Seong Jae Hwang机构:Yonsei Universit…...
蓝桥杯3503 更小的数
问题描述 小蓝有一个长度均为 n 且仅由数字字符 0∼9 组成的字符串,下标从 0 到 n−1,你可以将其视作是一个具有 n 位的十进制数字 num,小蓝可以从 num 中选出一段连续的子串并将子串进行反转,最多反转一次。 小蓝想要将选出的子…...
5.21本日总结
一、英语 复习list4list26 二、数学 学完14讲,1000题13讲写完 三、408 学习计网5.3剩余内容 四、总结 高数本月结束知识点学习,15讲知识点与14讲的题目同步进行。408剩余两本书要加快学习进度。 五、明日计划 英语:复习lsit5list25 …...
【25软考网工】第七章(3) UOS Linux防火墙配置和Web应用服务配置
博客主页:christine-rr-CSDN博客 专栏主页:软考中级网络工程师笔记 大家好,我是christine-rr !目前《软考中级网络工程师》专栏已经更新三十多篇文章了,每篇笔记都包含详细的知识点,希望能帮助到你&am…...
Python数据分析基础
Python数据分析入门 介绍 在这个教程中,我们将学习如何使用Python来进行基本的数据分析。 安装必要的库 为了开始,你需要安装以下Python库: NumPyPandasMatplotlib 示例代码 import numpy as np import pandas as pd import matplotli…...
spring cloud config更新配置
在开发微服务时,往往需要有开发环境、测试环境和生产环境,手动修改配置环境是一件很麻烦的事情,因此,这里使用spring cloud config管理配置环境。要使用spring cloud config,需要先在GitHub搭建一个仓库。 一、仓库搭…...
小米汽车二期工厂下月将竣工,产能提升助力市场拓展
在新能源汽车市场竞争日益激烈的当下,小米汽车传来重要进展消息。据多方信息显示,小米汽车二期工厂下月即将竣工,这一关键节点的到来,有望为小米汽车的产能提升与市场布局带来重大突破。 小米汽车二期工厂位于北京亦庄ÿ…...
【单片机】如何产生负电压?
以下是对知乎文章《单片机中常用的负电压是这样产生的!》的解析与总结,结合电路原理、应用场景及讨论要点展开: 一、负电压产生的核心原理 负电压本质是相对于参考地(GND)的电势差为负值,需通过电源或储能…...
Mcu_Bsdiff_Upgrade
系统架构 概述 MCU BSDiff 升级系统通过使用二进制差分技术,提供了一种在资源受限的微控制器上进行高效固件更新的机制。系统不传输和存储完整的固件映像,而是只处理固件版本之间的差异,从而显著缩小更新包并降低带宽要求。 该架构遵循一个…...
阿里云ecs 8核 16G 内存 装有redis6 分配了3G内存,和2个tomcat 每个tomcat 4G 服务器反应迟钝,如何确认不是redis的问题
我们经常用redis 但遇到tomcat timeout的时候,如何确认不是redis的问题。 不能因为这个再去搞个redis 压力测试。有没有更省劲的方法来确认不是redis的问题 以下是针对 阿里云 ECS(8核16G,Redis 6分配3G内存,2个Tomcat各分配4G&a…...