C++智能指针循环引用:原理、解决方案与工程实践
1. 项目概述从“内存泄漏”到“循环引用”的认知跃迁在C的世界里手动管理内存就像走钢丝new和delete的每一次配对都考验着程序员的严谨。智能指针的出现特别是std::shared_ptr被誉为现代C送给开发者的一份“自动化内存管理”大礼它通过引用计数机制让资源“谁在用、何时释放”变得清晰可控。很多新手包括几年前的我在初次接触shared_ptr时都会有种“内存管理从此高枕无忧”的错觉。然而现实很快会给你上一课当你精心设计的对象网络因为相互引用而形成一个闭环时你会发现程序运行结束后这些对象并没有如你所愿地被销毁内存悄无声息地泄漏了。这就是臭名昭著的“循环引用”问题。它不是一个复杂的语法错误编译器不会报错运行时也可能不会立即崩溃但它像程序中的一个“幽灵”缓慢地吞噬着系统资源。理解并解决循环引用是从“会使用智能指针”到“精通智能指针”的关键一步。这篇文章我将结合自己踩过的坑和项目中的实战经验为你彻底拆解循环引用的成因、危害并给出清晰、可落地的解决方案。无论你是正在准备面试的C开发者还是在实际项目中遇到了难以排查的内存泄漏相信这篇详解都能给你带来直接的帮助。2. 智能指针与循环引用原理深度拆解要解决问题必须先透彻理解问题是如何产生的。循环引用并非智能指针的“缺陷”而是其引用计数机制在特定对象关系下的必然表现。2.1std::shared_ptr引用计数机制回顾std::shared_ptr的核心是“共享所有权”。当一个shared_ptr被创建指向某个资源比如一个new出来的对象时该资源会关联一个控制块其中包含一个引用计数器。每当有一个新的shared_ptr通过拷贝或赋值指向同一资源时引用计数加1每当一个shared_ptr被销毁离开作用域或被重置或转而指向其他资源时引用计数减1。当引用计数减为0时控制块会负责销毁其管理的资源对象。这个过程听起来很完美但它隐含了一个关键前提对象之间的引用关系必须是一个有向无环图DAG。也就是说从任何一个节点出发沿着引用箭头走不应该最终走回自己。一旦形成环引用计数机制就失效了。2.2 循环引用是如何形成的让我们用一个经典的“双亲-孩子”双向引用模型来具象化这个过程。假设我们有一个Person类每个人可能有孩子也可能有伴侣。#include memory #include iostream class Person { public: std::string name; std::shared_ptrPerson child; // 指向孩子的智能指针 std::shared_ptrPerson partner; // 指向伴侣的智能指针 Person(const std::string n) : name(n) { std::cout name created.\n; } ~Person() { std::cout name destroyed.\n; } }; int main() { auto alice std::make_sharedPerson(Alice); auto bob std::make_sharedPerson(Bob); // 建立双向关系Alice的孩子是BobBob的母亲是Alice // 注意这里我们先只用一个指针演示后面会引入真正的循环 alice-child bob; // bob-parent alice; // 如果这里还有一个parent指针就会形成环 return 0; }运行上面的代码输出会是Alice created. Bob created. Bob destroyed. Alice destroyed.一切正常因为alice和bob在main函数结束时离开作用域alice的引用计数从1变0释放Alice释放Alice时其成员child即bob被销毁bob的引用计数从2bob自身和alice-child减为1然后bob自身离开作用域引用计数从1变0释放Bob。这是一个线性关系。现在我们引入真正的循环修改Person类让每个人都有parent和childclass Person { public: std::string name; std::shared_ptrPerson child; std::shared_ptrPerson parent; // 新增指向双亲的指针 Person(const std::string n) : name(n) { std::cout name created.\n; } ~Person() { std::cout name destroyed.\n; } }; int main() { auto alice std::make_sharedPerson(Alice); auto bob std::make_sharedPerson(Bob); alice-child bob; // Alice引用Bob bob-parent alice; // Bob引用Alice std::cout Alice use_count: alice.use_count() std::endl; // 输出 2 std::cout Bob use_count: bob.use_count() std::endl; // 输出 2 return 0; // main函数结束alice和bob的局部智能指针被销毁。 // 但此时alice对象的引用计数从2减为1还剩bob-parent引用着 // bob对象的引用计数也从2减为1还剩alice-child引用着 // 两者引用计数都不为0因此都不会被销毁内存泄漏发生。 }运行这段代码你会发现只输出了“created”没有“destroyed”这就是循环引用导致的内存泄漏。其生命周期可以分解为以下几步alice和bob被创建各自的引用计数为1。alice-child bobbob的引用计数1变为2。bob-parent alicealice的引用计数1变为2。main函数结束局部变量alice和bob被销毁。alice销毁alice局部变量的引用计数-1从2变为1。由于计数不为0Alice对象不会被释放。关键点这个“1”是谁是bob-parent这个成员变量。bob销毁bob局部变量的引用计数-1从2变为1。由于计数不为0Bob对象不会被释放。这个“1”是alice-child。此时Alice对象被Bob对象的parent成员指着Bob对象被Alice对象的child成员指着。两者互相持有引用计数永远为1没有任何外部力量能将其减为0。垃圾回收器如果存在或许能处理但纯引用计数的shared_ptr对此无能为力。注意在实际项目中循环引用可能隐藏在更复杂的对象网络中比如A引用BB引用CC又引用A形成一个更大的环。排查起来会更困难。2.3std::weak_ptr的救赎打破循环的钥匙标准库提供了std::weak_ptr专门用来解决循环引用问题。weak_ptr是一种“弱引用”它指向一个由shared_ptr管理的对象但不增加该对象的引用计数。这意味着weak_ptr的存在不会阻止其所指对象的销毁。你可以把weak_ptr想象成一张“观察券”你可以通过它知道对象是否还存在但不能直接使用它需要先转换为shared_ptr。它的工作流程是你将循环引用中的某一环通常是代表“从属”、“观察”、“非拥有”关系的一方改为weak_ptr。当所有shared_ptr强引用都被销毁后对象引用计数归零对象被销毁。此时相关的weak_ptr会自动感知到对象已失效expired()返回true或者在你尝试通过lock()方法将其提升为shared_ptr时得到一个空指针。3. 解决方案实战使用std::weak_ptr理论说完了我们来看如何用weak_ptr改造上面的Person例子。在家庭关系中孩子“拥有”父母从出生就确定了但父母并不“拥有”孩子孩子成年后独立。更常见的建模是孩子知道父母是谁弱引用但父母不一定强引用孩子。这里我们调整一下让child为weak_ptr。#include memory #include iostream class Person { public: std::string name; std::weak_ptrPerson child; // 改为弱引用 std::shared_ptrPerson parent; Person(const std::string n) : name(n) { std::cout name created.\n; } ~Person() { std::cout name destroyed.\n; } // 一个辅助函数安全地访问孩子如果孩子还存在 void checkChild() { if (auto spChild child.lock()) { // 尝试将weak_ptr提升为shared_ptr std::cout name s child is spChild-name (alive).\n; } else { std::cout name s child is no longer alive or never set.\n; } } }; int main() { auto alice std::make_sharedPerson(Alice); auto bob std::make_sharedPerson(Bob); alice-child bob; // 弱引用赋值bob的引用计数不变仍为1 bob-parent alice; // 强引用赋值alice的引用计数1变为2 std::cout After setup:\n; std::cout Alice use_count: alice.use_count() std::endl; // 输出 2 std::cout Bob use_count: bob.use_count() std::endl; // 输出 1 bob-checkChild(); // Bob尝试访问孩子未设置输出无 alice-checkChild(); // Alice尝试访问孩子Bob输出存在 std::cout \nLeaving main scope...\n; return 0; // main结束局部变量alice和bob销毁。 // bob销毁bob引用计数从1减为0 - Bob对象被销毁。 // alice销毁alice引用计数从2减为1 - 还剩谁bob-parent已经随Bob对象销毁而销毁了所以实际上也减为0 - Alice对象被销毁。 }输出结果Alice created. Bob created. After setup: Alice use_count: 2 Bob use_count: 1 Bobs child is no longer alive or never set. Alices child is Bob (alive). Leaving main scope... Bob destroyed. Alice destroyed.看析构函数被成功调用了内存泄漏被解决。关键在于alice-child是一个weak_ptr它的赋值alice-child bob并没有增加bob的引用计数。因此在main结束时局部变量bob销毁bob的引用计数从1变为0Bob对象立即被销毁。在Bob的析构函数中其成员parent一个shared_ptrPerson也被销毁这导致alice的引用计数减1。接着局部变量alice销毁alice的引用计数从2变为1不在步骤1中已经减了1所以此时alice的引用计数是从1变为0Alice对象也被销毁。循环被打破了。weak_ptr就像在环上切开了一个口子让引用计数的水流能够顺畅地归零。3.1weak_ptr的核心操作构造与赋值通常通过一个已有的shared_ptr来构造或赋值。std::shared_ptrMyClass sp std::make_sharedMyClass(); std::weak_ptrMyClass wp1(sp); // 构造 std::weak_ptrMyClass wp2 sp; // 赋值lock()方法这是最常用的方法。它尝试将weak_ptr提升为一个shared_ptr。如果原对象还存在即引用计数0则返回一个有效的shared_ptr并且增加引用计数如果原对象已被销毁则返回一个空的shared_ptr。这是线程安全的。if (auto shared wp.lock()) { // 对象还存在可以安全使用shared shared-doSomething(); } else { // 对象已失效 std::cout Object is gone.\n; }expired()方法检查weak_ptr所观察的对象是否已被销毁即其对应的shared_ptr引用计数是否为0。注意在多线程环境下expired()和lock()之间对象状态可能改变因此最佳实践是总是使用lock()。if (!wp.expired()) { // 对象可能还存在但这里不是绝对安全的 auto sp wp.lock(); // 仍然需要lock来获取使用权 }use_count()方法返回与之共享对象的shared_ptr的数量。主要用于调试不应作为业务逻辑判断的依据。实操心得在设计类关系时要仔细思考所有权。谁“拥有”谁拥有关系用shared_ptr单纯的“知道”、“引用”、“观察”关系用weak_ptr。例如在观察者模式中主题Subject持有观察者Observer的weak_ptr列表这样观察者可以随时被销毁而不会导致主题持有其悬空引用或阻碍其析构。4. 其他场景与进阶解决方案weak_ptr是解决循环引用的标准答案但并非唯一答案。根据不同的设计场景还有其他几种思路。4.1 方案二重新设计所有权关系使用原始指针或引用有时循环引用源于错误的所有权设计。如果对象A明确地拥有对象B的整个生命周期而B只需要在存在时能访问A那么根本不需要在B中用智能指针指向A。class Boss; // 前向声明 class Employee { public: std::string name; Boss* boss; // 使用原始指针表示“我知道我老板是谁但我不拥有他” Employee(const std::string n, Boss* b) : name(n), boss(b) {} // ... 使用boss指针前需要判断非空 }; class Boss { public: std::string name; std::vectorstd::unique_ptrEmployee team; // Boss拥有Employees Boss(const std::string n) : name(n) {} void addEmployee(const std::string empName) { team.push_back(std::make_uniqueEmployee(empName, this)); // 传递this指针 } };在这个例子里Boss通过unique_ptr完全拥有Employee。Employee只通过原始指针boss知道自己的老板。当Boss对象销毁时team中的unique_ptr会被销毁所有Employee对象也随之销毁不存在循环引用。Employee中的boss指针会变成悬空指针但只要确保在Employee对象被销毁后不再访问boss指针就是安全的通常Employee的生命周期不会超过Boss。注意事项使用原始指针需要非常小心生命周期管理。你必须百分百确定当Employee试图访问boss指针时Boss对象一定还活着。这在紧密耦合的父子或组合关系中可行但在复杂的、生命周期不确定的对象网络中风险极高不推荐作为通用解决方案。4.2 方案三使用std::enable_shared_from_this有一种特殊场景在类的成员函数内部你需要获得一个指向当前对象自身的shared_ptr。如果你直接return std::shared_ptrT(this)将会创建一个新的、独立的控制块导致同一个对象被多个shared_ptr以不同的引用计数管理最终会重复析构引发未定义行为。std::enable_shared_from_this提供了一个安全的机制。它要求对象本身必须已被一个shared_ptr管理然后在其成员函数中你可以通过shared_from_this()方法获得一个与现有管理共享控制块的shared_ptr。class Good : public std::enable_shared_from_thisGood { public: std::shared_ptrGood getptr() { return shared_from_this(); } }; int main() { std::shared_ptrGood gp1 std::make_sharedGood(); std::shared_ptrGood gp2 gp1-getptr(); // 正确gp1和gp2共享引用计数 std::cout gp1.use_count() std::endl; // 输出 2 }重要限制在构造函数中不能调用shared_from_this()因为此时对象尚未被shared_ptr完全接管。同时如果对象不是通过shared_ptr创建的例如在栈上调用shared_from_this()会抛出std::bad_weak_ptr异常。这个特性本身不直接解决循环引用但它是在使用shared_ptr和weak_ptr进行复杂交互时的基础工具。例如在需要将自身的weak_ptr传递给其他对象如注册回调时可以这样用class Observable : public std::enable_shared_from_thisObservable { std::vectorstd::weak_ptrObserver observers; public: void registerObserver(std::weak_ptrObserver obs) { observers.push_back(obs); } void notifyAll() { for (auto wobs : observers) { if (auto obs wobs.lock()) { obs-update(shared_from_this()); // 传递自身的shared_ptr } } } };4.3 方案四手动打破循环这是一种比较原始但有时有效的方案在知道对象网络即将不再需要时手动将形成循环的指针置空reset()。int main() { auto alice std::make_sharedPerson(Alice); auto bob std::make_sharedPerson(Bob); alice-child bob; bob-parent alice; // ... 使用alice和bob ... // 在作用域结束前手动打破循环 alice-child.reset(); // 或者 bob-parent.reset(); return 0; // 现在可以正常析构了 }这种方法将清理责任交给了使用者极易出错和遗漏违背了智能指针“自动管理”的初衷仅在非常特定的、可控的临时场景下考虑绝大多数情况下应优先使用weak_ptr。5. 设计模式与架构层面的预防策略解决循环引用除了在语法层面使用weak_ptr更重要的是在软件设计之初就避免产生循环的所有权关系。这属于架构层面的考量。5.1 审视对象关系组合、聚合与关联组合Composition“部分”的生命周期完全由“整体”控制。例如Car拥有Engine。使用unique_ptr或直接作为成员对象。这是最强的所有权关系不会产生循环。聚合Aggregation“部分”可以独立于“整体”存在。例如Department拥有Employee但Employee可以调换部门。通常使用shared_ptr或原始指针。需要仔细设计避免双向强引用。关联Association一种使用关系一个对象知道另一个对象。例如Driver知道他所开的Car。通常使用原始指针、引用或weak_ptr。这是最弱的关系。在设计时应优先考虑组合其次聚合谨慎使用关联。明确区分“拥有”和“知道”。对于“知道”的关系果断使用weak_ptr。5.2 引入中间层或使用依赖注入当两个模块或类相互依赖时可以考虑引入一个第三方中介Mediator Pattern或者使用依赖注入框架。让中介持有双方的shared_ptr而双方只持有中介的引用或weak_ptr从而将直接的双向依赖解耦为两个单向依赖。5.3 使用观察者模式与弱回调这是weak_ptr的经典应用场景。被观察者Subject持有观察者Observer的weak_ptr列表。当事件发生时被观察者遍历列表尝试将weak_ptr提升为shared_ptr来调用观察者的接口。如果提升失败观察者已不存在则安静地将其从列表中移除。这样观察者可以随时安全地销毁自己而不会造成内存泄漏或被回调时访问已销毁对象。class Observer : public std::enable_shared_from_thisObserver { public: virtual void update() 0; virtual ~Observer() default; }; class Subject { std::vectorstd::weak_ptrObserver observers_; public: void attach(std::weak_ptrObserver obs) { observers_.push_back(obs); } void notify() { auto it observers_.begin(); while (it ! observers_.end()) { if (auto sp it-lock()) { sp-update(); it; } else { // 观察者已失效移除弱引用 it observers_.erase(it); } } } };6. 调试与排查循环引用实战指南当你怀疑程序存在内存泄漏且可能是循环引用导致时可以按以下步骤排查。6.1 使用工具检测Valgrind (Linux/macOS)神器级别的内存调试工具。使用valgrind --leak-checkfull ./your_program运行程序它会详细报告内存泄漏的位置和大小。对于循环引用它会指出哪些块是“definitely lost”的。AddressSanitizer (ASan)GCC/Clang的编译选项性能损耗比Valgrind小。编译时添加-fsanitizeaddress运行时如果发生泄漏会给出清晰的堆栈信息。Visual Studio 调试器 (Windows)在调试模式下运行程序程序退出时输出窗口会报告是否检测到内存泄漏。可以使用_CrtDumpMemoryLeaks()函数进行更精确的定位。自定义调试shared_ptr对于复杂情况可以创建一个简单的包装类或使用调试版本的分配器在构造/析构时打印对象的地址和引用计数跟踪其生命周期。6.2 代码审查与思维实验绘制对象关系图对于复杂的模块在白板或纸上画出主要类之间的指针引用关系。寻找是否有形成闭环的路径。审查所有shared_ptr成员变量这是循环引用的高发区。对每一个shared_ptrT成员问自己这个类是否“拥有”T对象还是仅仅“知道”它如果是后者考虑改为weak_ptrT或原始指针。检查容器中的shared_ptrstd::vectorstd::shared_ptrX、std::mapKey, std::shared_ptrY等。容器内的shared_ptr同样参与引用计数。如果容器本身被一个对象持有而容器内的对象又引用了这个持有者就会形成环。关注全局或静态的shared_ptr它们生命周期极长很容易无意中持有对象导致其无法释放。6.3 一个典型的排查案例假设你有一个ChatRoom聊天室类和User用户类。ChatRoom有一个std::vectorstd::shared_ptrUser users_列表User有一个std::shared_ptrChatRoom currentRoom_指向所在的聊天室。问题当用户离开聊天室时你只是从ChatRoom::users_中移除了该用户的shared_ptr。但如果用户对象例如在一个全局用户管理列表中仍然存在并且它的currentRoom_还指向聊天室而聊天室的users_列表又可能通过其他方式间接引用用户这里的关系网很容易变得复杂。解决方案User对ChatRoom的关系是“当前所在”这是一种临时关联并非拥有。应将User::currentRoom_改为std::weak_ptrChatRoom。确保从ChatRoom::users_中移除用户时使用有效的方式如查找用户ID而非指针比较避免遗留悬空指针。或者ChatRoom也可以持有std::weak_ptrUser但这需要配套的用户管理机制来保证User对象的存在。7. 性能考量与最佳实践总结7.1weak_ptr的性能开销使用weak_ptr会带来微小的开销内存weak_ptr对象本身和shared_ptr大小通常相同两个指针但控制块需要额外空间来维护弱引用计数。时间lock()操作需要原子操作检查引用计数并可能增加强引用计数比直接使用shared_ptr稍慢。weak_ptr的构造、析构、赋值也需要操作弱引用计数。然而在绝大多数应用中这种开销是微不足道的。与内存泄漏和系统崩溃的风险相比这点性能代价是绝对值得支付的。不要因为担心性能而拒绝使用weak_ptr。7.2 智能指针使用黄金法则首选std::unique_ptr如果所有权是独占的、明确的毫不犹豫地使用unique_ptr。它没有开销语义最清晰。慎用std::shared_ptr仅在确实需要共享所有权时才使用。共享所有权意味着更复杂的生命周期和潜在的循环引用风险。使用std::weak_ptr打破循环一旦出现共享所有权的双向引用立即将其中一方改为weak_ptr。避免从原始指针创建多个shared_ptrstd::shared_ptrT p1(new T); std::shared_ptrT p2(p1.get());这是灾难性的会导致双重释放。始终使用std::make_shared或从一个已存在的shared_ptr拷贝。使用std::make_shared和std::make_unique它们更安全异常安全、更高效单次内存分配。不要使用shared_ptr管理非堆内存或没有所有权的资源例如不要用shared_ptr来管理栈上对象或第三方库返回的裸指针除非提供了自定义删除器。循环引用是现代C内存管理中的一个经典陷阱但也是一个很好理解的问题。其核心在于理解shared_ptr的引用计数原理并清晰地区分对象间的“拥有”和“知道”关系。记住weak_ptr是你武器库中解决此类问题的标准装备。在项目设计初期就思考清楚对象的所有权链路能省去后期大量的调试和重构时间。当你养成了在可能出现循环的地方主动使用weak_ptr的习惯后你会发现内存泄漏问题将大大减少代码也更加健壮和清晰。

相关新闻

博物馆转企改制员工积极性低|北京华恒智信薪酬改革成功案例

博物馆转企改制员工积极性低|北京华恒智信薪酬改革成功案例

【客户行业】事业单位【问题类型】薪酬管理【客户背景】某国家级博物馆是由当地政府与自然资源局共建共管的事业单位,是一家综合性博物馆。自建立时,该博物馆属于公益一类事业单位,近两年,随着上级政策的改变,该单位由…

2026/8/26 4:03:03 阅读更多 →
Hermes Agent 0.18.0 Runtime 新特性解析与生产环境升级指南

Hermes Agent 0.18.0 Runtime 新特性解析与生产环境升级指南

在 AI 智能体快速发展的今天,Hermes Agent 作为一款功能强大的开源 AI 助手框架,其 0.18.0 Runtime 版本的正式发布标志着该平台在稳定性、性能和功能完整性方面迈出了重要一步。对于已经在使用 Hermes Agent 的开发者和团队来说,了解新版本的…

2026/8/25 18:45:09 阅读更多 →
Kimi K3编程助手GPU资源需求分析与优化配置指南

Kimi K3编程助手GPU资源需求分析与优化配置指南

最近,如果你关注AI开发领域,一定注意到了Kimi K3的火爆。这个被冠以"编程助手"名号的新工具,在短短几周内迅速成为技术圈的热门话题。但随之而来的,是不少开发者发现自己的GPU资源突然变得紧张起来。这背后反映的其实是…

2026/8/22 8:37:30 阅读更多 →

最新新闻

AI驱动网络钓鱼攻击的防御策略:从技术原理到实战指南

AI驱动网络钓鱼攻击的防御策略:从技术原理到实战指南

1. 项目概述:当钓鱼攻击披上AI的“新衣” 最近几年,网络安全圈里一个老生常谈的话题——“网络钓鱼”,正在经历一场静默但深刻的“工业革命”。过去,我们识别钓鱼邮件,很大程度上依赖于一些“粗糙”的痕迹:…

2026/8/26 9:05:47 阅读更多 →
Android应用启动性能优化:Binder机制源码解析与实战调优

Android应用启动性能优化:Binder机制源码解析与实战调优

1. 项目概述:从一次应用启动卡顿说起最近在排查一个线上应用的启动性能问题时,遇到了一个棘手的情况:应用在冷启动阶段,从点击图标到第一个Activity的onCreate方法被调用,中间有长达数百毫秒的“空白期”。使用Systrac…

2026/8/26 9:05:47 阅读更多 →
冲击地压预测实战路径:从数学建模到井下预警卡片

冲击地压预测实战路径:从数学建模到井下预警卡片

1. 这不是一份“标准答案”,而是一套可落地的冲击地压预测实战路径 2024年五一数学建模竞赛C题——“煤矿深部开采冲击地压危险预测”,一公布就让不少参赛队头皮发紧。它不像A题偏重纯理论推演,也不像B题侧重宏观政策分析,而是直戳…

2026/8/26 9:05:47 阅读更多 →
甲骨文识别:古文字学驱动的OCR新范式

甲骨文识别:古文字学驱动的OCR新范式

1. 这不是传统OCR:甲骨文识别建模的本质矛盾与破局点 2024 Mathorcup B题一出来,不少队伍第一反应是“不就是OCR识别嘛,调个PaddleOCR或者EasyOCR跑通就行”。我去年带三支队伍试过这条路——全部卡在第三天凌晨两点,盯着屏幕上92…

2026/8/26 9:05:47 阅读更多 →
基于腾讯云Lighthouse与Hermes Agent构建企业级智能客服系统实战

基于腾讯云Lighthouse与Hermes Agent构建企业级智能客服系统实战

1. 项目缘起:从“救火”到“预警”的客服效率革命 我接手过不少中小企业的线上业务,发现一个普遍存在的痛点:客户咨询响应慢。尤其是在非工作时间,或者客服人手不足的时候,一个简单的产品咨询,客户可能要等…

2026/8/26 9:05:45 阅读更多 →
ADC过采样提升分辨率:噪声利用与STM32工程实践

ADC过采样提升分辨率:噪声利用与STM32工程实践

搞嵌入式的人第一次听到“噪声能提高ADC分辨率”这句话,十有八九要愣一下。我入行前几年也一直信奉“模拟信号链路越干净越好”,PCB布局恨不得把ADC引脚周围全铺地,电源用三级LDO再加磁珠,生怕有一点纹波进到采样结果里。结果后来…

2026/8/26 9:04:40 阅读更多 →

日新闻

Python random 模块常用函数详解:从入门到实战

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 0:00:40 阅读更多 →
《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》索引目录: 《Microsoft Sql server 2008 Internals》读书笔记--目录索引 在上篇文章中,主要介绍了创建数据库的基本语法和FileGroup的初步知识。需要注意的是: 关于FileGroup 如果你的系统是用Raid设备直接存…

2026/8/26 1:18:18 阅读更多 →
政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体已经从概念试点阶段,转入了政务服务的常态化落地应用;在实际使用过程中,它能自主理解办事需求、辅助完成填报申报、开展材料预审,并联动多个系统协同作业,真正嵌入到政务办理的全流程当中。但在落地推进过…

2026/8/26 1:18:18 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 3:38:12 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 3:38:18 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/25 3:38:23 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/26 3:50:20 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/25 10:31:12 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/26 1:24:05 阅读更多 →