C++ 内存安全与智能指针深度解析

C++ 内存安全与智能指针深度解析

面试官考察“野指针”,实际上是在考察你对 C++ “资源所有权” (Ownership)“生命周期管理” (Lifetime Management) 的理解。现代 C++ 的答案不是“如何手动避免”,而是“如何自动化管理”。

第一部分:核心知识点梳理

1. 问题的核心:所有权不明 (The Why)

“野指针”问题的根源在于,C++ 的裸指针 (Raw Pointer) 将“资源的使用权”和“资源的所有权”分离开来。你拿到一个指针,可以读写它指向的内存,但你不知道:

  • 谁应该负责释放它?
  • 它什么时候会被释放?
  • 它指向的内存现在是否还有效?

这种所有权和生命周期的不确定性,导致了三类典型问题:

  1. 野指针 (Dangling/Wild Pointer): 指针指向的内存已被释放,或指针未被初始化。对它的任何操作都是未定义行为,是程序崩溃的主要元凶。
  2. 内存泄漏 (Memory Leak): 忘记释放动态分配的内存,导致程序可用内存越来越少。
  3. 重复释放 (Double Free): 多次释放同一块内存,同样是严重的未定义行为。

2. C++ 的解决方案:绑定所有权与生命周期 (The What)

现代 C++ 的核心解决方案是 RAII (Resource Acquisition Is Initialization) 范式。

  • RAII 哲学: 将资源(如动态分配的内存、文件句柄、锁)的生命周期与一个栈上对象的生命周期绑定。当对象被创建时,它获取资源(构造函数);当对象离开作用域时,它的析构函数被自动调用,从而自动释放资源。

智能指针 (Smart Pointers) 就是 RAII 范式在内存管理上的标准实现。它们是行为像指针的类模板,但在其析构函数中自动处理内存释放。

2.1 std::unique_ptr:独占所有权

这是默认首选的智能指针。

  • 核心语义: 独占,或者说唯一的所有权。在任何时刻,只有一个 unique_ptr 可以指向一个给定的对象。
  • 所有权转移: 它不能被复制,但可以通过 std::move转移所有权。这是一种轻量级的操作,仅涉及指针值的拷贝,不涉及底层对象的拷贝。
  • 自动释放:unique_ptr 被销毁时(例如离开作用域),它会自动调用 delete 释放其管理的对象。
  • 创建方式: 优先使用 std::make_unique<T>(...),它更安全(避免了在复杂表达式中可能发生的内存泄漏)且效率更高。
void process_widget() {// 使用 make_unique 创建对象,所有权属于 a_widgetauto a_widget = std::make_unique<Widget>();// 使用 a_widgeta_widget->do_something();// 将所有权从 a_widget 转移到 b_widgetstd::unique_ptr<Widget> b_widget = std::move(a_widget);// a_widget 现在是空的 (nullptr)} // 函数结束,b_widget 离开作用域,其析构函数被调用,自动 delete Widget 对象
2.2 std::shared_ptr:共享所有权

当你需要多个指针共同管理同一个对象的生命周期时使用。

  • 核心语义: 共享所有权,通过引用计数 (Reference Counting) 来实现。
  • 引用计数: shared_ptr 内部维护一个指向“控制块”的指针,控制块中包含了引用计数器。每当有一个新的 shared_ptr 指向该对象(通过拷贝构造或拷贝赋值),引用计数加一。每当有一个 shared_ptr 被销毁,引用计数减一。
  • 自动释放: 当引用计数变为 0 时,意味着最后一个拥有该对象的 shared_ptr 被销毁,它会自动 delete 底层对象和控制块。
  • 创建方式: 同样,优先使用 std::make_shared<T>(...)。它能一次性分配对象和控制块的内存,比分开分配效率更高。
2.3 std::weak_ptr:临时所有权/观察者

weak_ptrshared_ptr 的“助手”,用于解决 shared_ptr 可能导致的循环引用问题。

  • 核心语义: 它是一个非拥有型的观察者。它指向由 shared_ptr管理的对象,但不会增加引用计数
  • 作用:
    1. 打破循环引用: 如果两个对象通过 shared_ptr 互相引用,它们的引用计数永远不会变为0,导致内存泄漏。将其中一个或两个引用改为 weak_ptr 即可打破循环。
    2. 安全地观察: 在使用前,你必须通过调用 lock() 方法将其提升为一个 shared_ptr。如果底层对象仍然存在,lock() 会返回一个有效的 shared_ptr;如果对象已被销毁,则返回一个空的 shared_ptr。这完美地解决了“检查一个裸指针是否仍然有效”的难题。

3. 如何选择:现代C++内存管理最佳实践 (The How)

  1. 默认使用 std::unique_ptr 它是最轻量、最高效的智能指针,清晰地表达了“唯一所有权”的意图。
  2. 只在需要共享所有权时才使用 std::shared_ptr 明确知道一个资源需要被多个独立的生命周期共同管理时,才升级到 shared_ptr
  3. 使用 std::weak_ptr 打破 shared_ptr 的循环引用。
  4. 几乎永远不要在应用代码中直接使用 newdelete 让智能指针为你代劳。
  5. 使用 std::make_uniquestd::make_shared 来创建智能指针管理的对象。
  6. 项目关联点: 在你的代码迁移项目中,你会遇到大量返回裸指针的工厂函数或API。例如 HRESULT CreateInstance(IUnknown** ppv)。一个经典的重构模式就是:
    • 封装遗留API: 创建一个新的C++函数,比如 std::unique_ptr<MyObject> create_my_object()
    • 内部调用旧API: 在这个函数内部,你声明一个裸指针 MyObject* raw_ptr = nullptr;,然后调用旧的API来填充它。
    • 包装并返回: 检查API调用是否成功,如果成功,就 return std::unique_ptr<MyObject>(raw_ptr);
    • 价值: 这样一来,所有调用者都拿到了一个现代、安全的智能指针。资源的生命周期被严格管理,无论发生异常还是提前返回,内存都将被自动释放。这是你可以在简历和面试中重点讲述的、非常有价值的实践经验。

第二部分:模拟面试问答

面试官: 我们来聊聊内存安全。当提到“野指针”,你首先想到的是什么?如何避免它?

你: 面试官你好。提到“野指针”,我首先想到的是其背后的根源:C++裸指针的所有权和生命周期管理是分离的、手动的。避免它的传统方法是“防御性编程”,比如初始化为 nullptr、释放后置空。但现代C++提供了更好的答案:通过RAII机制和智能指针,从设计上消除手动管理。我的首选方案是使用 std::unique_ptr 来独占资源,或者在需要共享时使用 std::shared_ptr,从而将内存的生命周期与对象的生命周期绑定,实现自动、安全地回收。

面试官: unique_ptrshared_ptr,它们的核心区别是什么?你在项目中会如何选择?

你: 它们的核心区别在于所有权模型

  • unique_ptr 实现的是独占所有权。它非常轻量,开销和裸指针几乎一样,并且清晰地表明“我是这个资源的唯一管理者”。
  • shared_ptr 实现的是共享所有权。它通过引用计数允许多个 shared_ptr 实例共同管理一个对象,但它有额外的开销(需要维护一个控制块,引用计数操作是原子的)。

我的选择原则是:默认永远使用 unique_ptr。只有当业务逻辑明确要求一个资源必须被多个独立的、生命周期不同的模块共享时,我才会考虑使用 shared_ptr。我把它看作是从 unique_ptr 的“升级”,而不是一个平级的选项。

面试官: 既然 shared_ptr 这么强大,为什么它还会导致内存泄漏?你听说过 weak_ptr 吗?

你: shared_ptr 本身无法解决循环引用的问题。如果两个对象A和B,A内部有一个指向B的 shared_ptr,B内部也有一个指向A的 shared_ptr,那么即使外界所有指向A和B的 shared_ptr 都被销毁了,A和B内部的引用计数也各自为1,永远不会归零,从而导致它们占用的内存无法被释放,造成内存泄漏。

weak_ptr 就是为了解决这个问题而生的。它是一个非拥有型的观察者,可以指向 shared_ptr 管理的对象,但不会增加引用计数。将循环引用中的任意一方(或双方)从 shared_ptr 改为 weak_ptr,就可以打破循环。在使用 weak_ptr 访问对象前,必须调用 lock() 方法尝试将它提升为一个 shared_ptr,这是一种安全检查机制,可以防止访问已经被释放的对象。

面试官: 很好。我们都知道 make_uniquemake_shared 是推荐的创建方式,为什么?直接 new 然后传给智能指针的构造函数有什么潜在问题吗?

你: 优先使用 make_ 系列函数主要有两个原因:异常安全性能

  • 异常安全: 考虑这样一个函数调用 process(std::shared_ptr<T>(new T()), some_func())。C++标准不保证函数参数的求值顺序。编译器有可能先执行 new T(),然后执行 some_func(),最后才构造 shared_ptr。如果此时 some_func() 抛出异常,那么已经分配的 T 的内存就会泄漏,因为管理它的 shared_ptr 还没有来得及被构造。而 make_shared 是一个单独的函数,它能保证在内部原子性地完成内存分配和智能指针的构造,从而避免了这个问题。
  • 性能 (仅限 make_shared): make_shared 可以在一次内存分配中,同时为对象 Tshared_ptr 所需的控制块分配空间。而 std::shared_ptr<T>(new T()) 至少需要两次内存分配(一次 new T(),一次在 shared_ptr 内部为控制块分配),因此 make_shared 效率更高。

第三部分:核心要点简答题

  1. RAII 范式的核心思想是什么?

    答:将资源的生命周期与一个栈上对象的生命周期绑定。对象构造时获取资源,对象析构时自动释放资源。

  2. unique_ptr 如何体现“独占所有权”?

    答:它禁止拷贝(编译错误),只允许通过 std::move 进行所有权的转移,保证了任何时候只有一个 unique_ptr 实例指向资源。

  3. 什么场景下你必须使用 weak_ptr?

    答:当使用 shared_ptr 出现了或可能出现循环引用导致内存泄漏时,必须使用 weak_ptr 来打破这个循环。

第四部分:简化版智能指针实现代码

1. 简化版 std::unique_ptr

核心:禁用拷贝构造 / 赋值,允许移动构造 / 赋值,析构时自动 delete 资源。

#include <utility>  // 用于 std::move// 简化版 unique_ptr:独占所有权,禁止拷贝,允许移动
template <typename T>
class MyUniquePtr {
public:// 1. 构造函数:接管裸指针的所有权explicit MyUniquePtr(T* ptr = nullptr) : m_ptr(ptr) {}// 2. 析构函数:释放资源(核心RAII逻辑)~MyUniquePtr() {delete m_ptr;  // 自动释放,避免内存泄漏m_ptr = nullptr;}// 3. 禁用拷贝构造和拷贝赋值(独占所有权的关键)// 方式:只声明不定义,或用 =delete(C++11及以后推荐)MyUniquePtr(const MyUniquePtr& other) = delete;MyUniquePtr& operator=(const MyUniquePtr& other) = delete;// 4. 允许移动构造和移动赋值(转移所有权)MyUniquePtr(MyUniquePtr&& other) noexcept : m_ptr(other.m_ptr) {other.m_ptr = nullptr;  // 原指针置空,避免重复释放}MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {if (this != &other) {  // 避免自赋值delete m_ptr;       // 先释放当前资源m_ptr = other.m_ptr; // 接管对方资源other.m_ptr = nullptr; // 原指针置空}return *this;}// 5. 模拟指针行为:* 和 -> 运算符重载T& operator*() const { return *m_ptr; }T* operator->() const { return m_ptr; }// 6. 辅助接口:获取裸指针(谨慎使用)、判断是否为空T* get() const { return m_ptr; }bool is_null() const { return m_ptr == nullptr; }private:T* m_ptr;  // 管理的裸指针
};// 测试 MyUniquePtr
void test_unique_ptr() {// 构造:接管 new 出来的资源MyUniquePtr<int> up1(new int(10));std::cout << "*up1: " << *up1 << std::endl;  // 输出 10// 移动:所有权从 up1 转移到 up2MyUniquePtr<int> up2 = std::move(up1);std::cout << "up1 is null? " << (up1.is_null() ? "yes" : "no") << std::endl; // yesstd::cout << "*up2: " << *up2 << std::endl;  // 输出 10// 拷贝会编译报错(禁用拷贝)// MyUniquePtr<int> up3 = up2;  // 编译失败:拷贝构造已delete
}
2. 简化版 std::shared_ptr

核心:用「控制块」存储引用计数,拷贝时计数 + 1,析构时计数 - 1,计数为 0 时释放资源。

#include <atomic>  // 简化版用普通int(非线程安全),生产级需用 std::atomic<int>// 第一步:定义控制块(存储引用计数和资源指针)
template <typename T>
struct ControlBlock {T* m_resource;    // 管理的资源指针int m_ref_count;  // 引用计数(简化版:非线程安全)// 控制块构造:初始化资源和计数(计数初始为1,代表第一个shared_ptr持有)ControlBlock(T* res) : m_resource(res), m_ref_count(1) {}// 控制块析构:释放资源(计数为0时调用)~ControlBlock() {delete m_resource;m_resource = nullptr;}// 引用计数增加void inc_ref() { m_ref_count++; }// 引用计数减少,返回是否需要销毁控制块bool dec_ref() {m_ref_count--;return m_ref_count == 0;  // 计数为0 → 需要销毁}
};// 第二步:简化版 shared_ptr
template <typename T>
class MySharedPtr {
public:// 1. 构造函数:创建控制块,接管资源explicit MySharedPtr(T* ptr = nullptr) : m_ctrl_block(nullptr) {if (ptr != nullptr) {m_ctrl_block = new ControlBlock<T>(ptr);  // 分配控制块}}// 2. 拷贝构造:共享资源,引用计数+1MySharedPtr(const MySharedPtr& other) : m_ctrl_block(other.m_ctrl_block) {if (m_ctrl_block != nullptr) {m_ctrl_block->inc_ref();  // 计数+1}}// 3. 拷贝赋值:先释放当前资源,再共享新资源MySharedPtr& operator=(const MySharedPtr& other) {if (this != &other) {  // 避免自赋值// 第一步:释放当前控制块(计数-1,需判断是否销毁)release_ctrl_block();// 第二步:共享对方的控制块,计数+1m_ctrl_block = other.m_ctrl_block;if (m_ctrl_block != nullptr) {m_ctrl_block->inc_ref();}}return *this;}// 4. 析构函数:释放控制块(计数-1,为0则销毁)~MySharedPtr() {release_ctrl_block();}// 5. 模拟指针行为T& operator*() const { return *(m_ctrl_block->m_resource); }T* operator->() const { return m_ctrl_block->m_resource; }// 6. 辅助接口:获取引用计数(调试用)int get_ref_count() const {return m_ctrl_block ? m_ctrl_block->m_ref_count : 0;}private:ControlBlock<T>* m_ctrl_block;  // 指向控制块的指针// 辅助函数:释放控制块(计数-1,为0则删除)void release_ctrl_block() {if (m_ctrl_block != nullptr) {if (m_ctrl_block->dec_ref()) {  // 计数为0 → 销毁控制块delete m_ctrl_block;m_ctrl_block = nullptr;}}}
};// 测试 MySharedPtr
void test_shared_ptr() {// 第一个shared_ptr:控制块计数=1MySharedPtr<int> sp1(new int(20));std::cout << "*sp1: " << *sp1 << ", ref count: " << sp1.get_ref_count() << std::endl; // 20, 1// 拷贝sp1 → 控制块计数=2MySharedPtr<int> sp2 = sp1;std::cout << "*sp2: " << *sp2 << ", ref count: " << sp2.get_ref_count() << std::endl; // 20, 2// 再拷贝sp2 → 控制块计数=3MySharedPtr<int> sp3(sp2);std::cout << "sp3 ref count: " << sp3.get_ref_count() << std::endl; // 3// sp3析构 → 计数=2{MySharedPtr<int> sp4 = sp3;std::cout << "sp4 ref count: " << sp4.get_ref_count() << std::endl; // 4} // sp4出作用域,计数=3std::cout << "after sp4 destroy, sp1 ref count: " << sp1.get_ref_count() << std::endl; // 3// sp1、sp2、sp3全部析构后,控制块计数=0 → 资源释放
}
3. 简化版 std::weak_ptr

核心:持有控制块指针(不增计数),通过 lock() 升级为 shared_ptr(增计数,检查资源是否存活)。

// 简化版 weak_ptr(必须和 MySharedPtr 配合使用)
template <typename T>
class MyWeakPtr {
public:// 1. 默认构造:空弱指针MyWeakPtr() : m_ctrl_block(nullptr) {}// 2. 从 shared_ptr 构造:持有控制块,但不增计数(关键)MyWeakPtr(const MySharedPtr<T>& sp) : m_ctrl_block(sp.m_ctrl_block) {}// 3. 拷贝构造/赋值:共享控制块,不增计数MyWeakPtr(const MyWeakPtr& other) : m_ctrl_block(other.m_ctrl_block) {}MyWeakPtr& operator=(const MyWeakPtr& other) {if (this != &other) {m_ctrl_block = other.m_ctrl_block;}return *this;}// 4. 核心接口:lock() → 升级为 shared_ptr(检查资源是否存活)MySharedPtr<T> lock() const {MySharedPtr<T> sp;// 控制块存在 + 资源未释放(计数>0)→ 升级成功if (m_ctrl_block != nullptr && m_ctrl_block->m_ref_count > 0) {sp.m_ctrl_block = m_ctrl_block;sp.m_ctrl_block->inc_ref();  // 引用计数+1}return sp;  // 资源已释放则返回空shared_ptr}// 5. 辅助接口:检查资源是否已过期(expired)bool expired() const {return m_ctrl_block == nullptr || m_ctrl_block->m_ref_count == 0;}private:ControlBlock<T>* m_ctrl_block;  // 持有控制块指针(不增计数)// 友元声明:让 MySharedPtr 能访问 m_ctrl_blockfriend class MySharedPtr<T>;
};// 测试 MyWeakPtr(重点:打破循环引用)
void test_weak_ptr() {// 场景1:正常升级MySharedPtr<int> sp(new int(30));MyWeakPtr<int> wp = sp;std::cout << "wp expired? " << (wp.expired() ? "yes" : "no") << std::endl; // noMySharedPtr<int> locked_sp = wp.lock();  // 升级成功if (locked_sp.get_ref_count() > 0) {std::cout << "*locked_sp: " << *locked_sp << ", ref count: " << locked_sp.get_ref_count() << std::endl; // 30, 2}// 场景2:资源释放后,升级失败sp.reset();  // 假设sp是最后一个持有资源的shared_ptr(计数=0,资源释放)std::cout << "after sp reset, wp expired? " << (wp.expired() ? "yes" : "no") << std::endl; // yesMySharedPtr<int> null_sp = wp.lock();  // 升级失败,返回空shared_ptrstd::cout << "null_sp is valid? " << (null_sp.get_ref_count() > 0 ? "yes" : "no") << std::endl; // no// 场景3:打破循环引用(核心价值)struct Node {int val;MyWeakPtr<Node> next;  // 用weak_ptr,避免循环// MySharedPtr<Node> next; // 若用shared_ptr,会形成循环引用~Node() { std::cout << "Node destroyed, val: " << val << std::endl; }};MySharedPtr<Node> node1(new Node{1});MySharedPtr<Node> node2(new Node{2});node1->next = node2;  // weak_ptr 指向 node2,不增计数node2->next = node1;  // weak_ptr 指向 node1,不增计数// node1和node2析构时,计数=0 → 资源释放(无内存泄漏)
}

关键说明(避免误解)

非生产级特性缺失

  • 线程安全:简化版用普通 int 计数,生产级需用 std::atomic<int> 保证原子操作;
  • 自定义删除器:未支持 MyUniquePtr<T, Deleter> 这种自定义释放逻辑(如 delete[]fclose);
  • 数组特化:简化版只支持单个对象,生产级需 template <typename T> class MyUniquePtr<T[]> 特化数组;
  • 异常安全:未处理 new ControlBlock 失败等异常场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94627.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94627.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue SFC Playground 如何正确引入 naive-ui

网罗开发&#xff08;小红书、快手、视频号同名&#xff09;大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等方…

音频转文本技术详解:API接口、实用示例与最佳实践

音频转文本技术详解&#xff1a;API接口、实用示例与最佳实践 目录 概述接口类型与模型说明支持的音频格式与文件大小限制快速入门音频转录&#xff08;Transcription&#xff09;音频翻译&#xff08;Translation&#xff09;支持的语言列表时间戳功能处理较长音频上下文提示…

QT-布局管理器

Qt布局管理器 一、布局管理器介绍布局管理器&#xff08;Layout Manager&#xff09;是在图形用户界面&#xff08;GUI&#xff09;应用程序中用于自动管理和排列窗口部件&#xff08;Widget&#xff09;的工具。Qt 共提供了 5 种布局管理器&#xff0c;来帮助开发者方便地组织…

Linux CentOS 安装 .net core 3.1

打开终端&#xff0c;输入以下命令以添加 .NET Core Yum 仓库&#xff1a;sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm安装 .NET Core SDK&#xff1a;sudo yum install dotnet-sdk-3.1验证安装&#xff1a;dotnet --versionre…

深度剖析Spring AI源码(三):ChatClient详解,优雅的流式API设计

深度剖析Spring AI源码&#xff08;三&#xff09;&#xff1a;ChatClient详解&#xff0c;优雅的流式API设计“The best APIs are those that make simple things simple and complex things possible.” —— Alan Kay (计算机科学巨匠) Spring AI的ChatClient API正是这句话…

C语言基础:(二十五)预处理详解

目录 前言 一、预处理符号 二、#define 定义常量 三、#define 定义宏 四、带有副作用的宏参数 五、宏替换的规则 六、宏函数对比 七、# 和 ## 7.1 #运算符 7.2 ##运算符 八、命名约定 九、#undef 十、命令行定义 十一、条件编译 十二、头文件的包含 12.1 头…

本地文件夹即时变身 Web 服务器(文件服务器)

一&#xff1a;http-server npm install --global http-server 使用&#xff0c;在一个目录下打开 cmd http-server [path] [options] [path] defaults to ./public if the folder exists, and ./ otherwise. 可以下载文件&#xff0c;但是不能下载文件夹。 二&#xff1a;…

Golang云端编程入门指南:前沿框架与技术全景解析

Golang云端编程入门指南&#xff1a;前沿框架与技术全景解析 1 引言&#xff1a;Go语言在云原生时代的优势 Go语言&#xff08;Golang&#xff09;由Google开发&#xff0c;凭借其简洁的语法、卓越的并发性能和高效的编译速度&#xff0c;已成为云端应用开发的首选语言之一。…

蓝凌EKP产品:从 XML 到 JSON ——表单存储的性能优化实践

1. 背景介绍蓝凌 EKP 的表单引擎&#xff0c;是整个低代码平台的核心能力之一。它不仅仅是“存储表单”&#xff0c;更是 企业级应用快速构建的基础设施。它支持各种复杂表单配置&#xff08;字段、布局、校验、权限、联动、子表单&#xff09;。它能灵活绑定流程&#xff0c;实…

STM32高级定时器-输出比较模式

一.输出比较原理1.输出比较 通过定时器的外部引脚对外输出控制信号&#xff0c;将通道X(x1,2,3,4)通常设置为PWM1、PWM2模式。 2.比较寄存器 当计数器CNT和比较寄存器CCR的值相等时&#xff0c;输出参考信号OCxREF的信号的极性发生改变&#xff0c;其中OCxREF1(高电平)称为有效…

深入理解Unity中的`.meta`文件:以纹理文件为例

在Unity开发中&#xff0c;.meta文件是一个经常被提及但又容易被忽视的组成部分。这些隐藏的元数据文件在项目的稳定性和一致性中扮演着重要角色&#xff0c;尤其是在处理纹理文件时。本文将深入探讨.meta文件的作用、内容、版本控制以及常见问题&#xff0c;帮助开发者更好地理…

【机器学习】3 Generative models for discrete data

本章目录 3 Generative models for discrete data 65 3.1 Introduction 65 3.2 Bayesian concept learning 65 3.2.1 Likelihood 67 3.2.2 Prior 67 3.2.3 Posterior 68 3.2.4 Posterior predictive distribution 71 3.2.5 A more complex prior 72 3.3 The beta-binomial mod…

Gemini CLI 与 MCP 服务器:释放本地工具的强大潜力

前言 Gemini CLI 是一款强大的命令行工具&#xff0c;它将 Google 的 Gemini 模型带入了您的终端。然而&#xff0c;其真正的潜力在于通过 模型上下文协议&#xff08;Model Context Protocol, MCP&#xff09; 与外部工具集成。本文将结合两篇关键文章&#xff0c;深入探讨什…

HTTP、HTTPS 与 WebSocket 详解

HTTP、HTTPS 与 WebSocket 详解 在网络通信中&#xff0c;HTTP、HTTPS 和 WebSocket 是三种常见的应用层协议&#xff0c;分别适用于不同的场景。以下从定义、特点、工作原理和适用场景等方面详细解析&#xff1a; 一、HTTP&#xff08;HyperText Transfer Protocol&#xff0c…

8月21日

#include "head.h"seq_p create_seq() {seq_p S(seq_p)malloc(sizeof(seq_list));if(SNULL){printf("malloc error");return NULL;}memset(S,0,sizeof(seq_list));return S; }//头插 void insert_head(seq_p S,int value,int len) {//判NULLif(SNULL){prin…

视频号存在争议了...

目前实测到&#xff1a;视频号里那套 争议信息提示加AI真相雷达&#xff0c;已经在不少视频下上线了&#xff08;这是一个非常火爆的趋势&#xff01;&#xff09;伙伴们都知道&#xff0c;短视频里的观点来得快、走得也快&#xff0c;很多人看完就转发。你想想看&#xff0c;要…

音视频处理工作室:实时通信的媒体层设计

在开发视频会议、语音聊天等实时通信应用时&#xff0c;媒体层&#xff08;Media Layer&#xff09; 是整个系统的核心。它就像是一个专业的"音视频处理工作室"&#xff0c;负责从采集声音画面到最终播放的全流程。本文将通过通俗易懂的比喻&#xff0c;解析媒体层中…

读《精益数据分析》:A/B测试与多变量测试

A/B测试与多变量测试&#xff1a;从入门到实战的完整指南 在数据驱动的时代&#xff0c;实验已经成为产品优化和商业决策的核心工具。而在众多实验方法中&#xff0c;A/B测试与多变量测试几乎是每一位产品经理、数据分析师、增长团队绕不开的关键词。 很多人第一次听到它们时&a…

中介者模式及优化

中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式&#xff0c;其核心思想是通过引入一个“中介者”对象&#xff0c;封装多个对象&#xff08;称为“同事对象”&#xff09;之间的复杂交互关系&#xff0c;使同事对象无需直接相互引用&#xff0c;而是…

卷积神经网络的基本概念

卷积神经网络 CNN&#xff0c;即卷积神经网络&#xff0c;是一种深度学习算法&#xff0c;在图像处理&#xff0c;视觉识别等任务中表现出色。 卷积神经网络的组成 CNN模型的组件包括卷积层&#xff0c;池化层&#xff0c;全连接层。 卷积层&#xff1a;提取图像中的局部特征池…