C++ Primer 第12章:动态内存

C++ Primer 第12章:动态内存


12.1 动态内存与智能指针

12.1.1 为什么需要动态内存?

复制代码
内存区域:
┌─────────────────────────────────────────────────────┐
│ 静态内存(Static)                                   │
│  - 全局变量、static变量                              │
│  - 程序启动时分配,程序结束时销毁                     │
├─────────────────────────────────────────────────────┤
│ 栈内存(Stack)                                      │
│  - 局部变量、函数参数                                │
│  - 函数调用时分配,函数返回时销毁                     │
├─────────────────────────────────────────────────────┤
│ 堆内存(Heap / 自由存储区)                          │
│  - new 分配,delete 释放                             │
│  - 生命周期由程序员控制                              │
│  - 智能指针自动管理                                  │
└─────────────────────────────────────────────────────┘

动态内存的使用场景:
1. 运行时才知道需要多少对象
2. 运行时才知道对象的类型
3. 需要在多个对象间共享数据

12.1.2 直接使用 new 和 delete 的问题

cpp 复制代码
// raw_pointer_problems.cpp -- 裸指针的问题
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    // ===== 问题1:忘记释放内存(内存泄漏)=====
    {
        int* p = new int(42);
        // ... 某些操作 ...
        // 忘记 delete p;   // 内存泄漏!
    }

    // ===== 问题2:重复释放(未定义行为)=====
    {
        int* p = new int(42);
        delete p;
        // delete p;   // ❌ 重复释放!未定义行为
    }

    // ===== 问题3:使用已释放的内存(悬空指针)=====
    {
        int* p = new int(42);
        delete p;
        // *p = 100;   // ❌ 悬空指针!未定义行为
        p = nullptr;   // 好习惯:释放后置nullptr
    }

    // ===== 问题4:异常安全问题 =====
    {
        int* p = new int(42);
        // 如果这里抛出异常,delete p 不会执行
        // throw runtime_error("error");   // 内存泄漏!
        delete p;
    }

    // ===== 解决方案:使用智能指针 =====
    // 智能指针自动管理内存,解决上述所有问题

    return 0;
}

12.1.3 shared_ptr

cpp 复制代码
// shared_ptr_demo.cpp -- shared_ptr详解
#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Resource
{
public:
    string name;
    Resource(const string& n) : name(n)
    {
        cout << "[创建] " << name << endl;
    }
    ~Resource()
    {
        cout << "[销毁] " << name << endl;
    }
    void use() const { cout << "[使用] " << name << endl; }
};

int main()
{
    using namespace std;

    // ===== 创建 shared_ptr =====
    // 方式1:make_shared(推荐,更安全高效)
    auto sp1 = make_shared<Resource>("资源A");

    // 方式2:直接构造(不推荐)
    shared_ptr<Resource> sp2(new Resource("资源B"));

    // ===== 引用计数 =====
    cout << "\n引用计数:" << endl;
    cout << "sp1.use_count() = " << sp1.use_count() << endl;   // 1

    {
        auto sp3 = sp1;   // 拷贝,引用计数+1
        auto sp4 = sp1;   // 拷贝,引用计数+1
        cout << "sp1.use_count() = " << sp1.use_count() << endl;   // 3
        sp3->use();
    }   // sp3, sp4 离开作用域,引用计数-2

    cout << "sp1.use_count() = " << sp1.use_count() << endl;   // 1

    // ===== 使用 shared_ptr =====
    sp1->use();           // 通过 -> 访问成员
    (*sp1).use();         // 通过 * 解引用
    sp1.get();            // 获取裸指针(谨慎使用)

    // ===== 检查 shared_ptr =====
    if (sp1)   // 隐式转换为bool
        cout << "sp1 非空" << endl;

    // ===== reset =====
    sp1.reset();   // 释放所有权,引用计数-1
    cout << "reset后 sp1 为空:" << boolalpha << !sp1 << endl;

    sp1.reset(new Resource("资源C"));   // 重新指向新对象
    cout << "sp1.use_count() = " << sp1.use_count() << endl;   // 1

    // ===== shared_ptr 在容器中 =====
    vector<shared_ptr<Resource>> resources;
    resources.push_back(make_shared<Resource>("资源D"));
    resources.push_back(make_shared<Resource>("资源E"));
    resources.push_back(sp1);   // sp1 和 resources[2] 共享资源C

    cout << "\n资源C引用计数:" << sp1.use_count() << endl;   // 2

    cout << "\n程序结束,所有资源自动释放:" << endl;
    return 0;
}

12.1.4 shared_ptr 与 new

cpp 复制代码
// shared_ptr_new.cpp -- shared_ptr与new的配合
#include <iostream>
#include <memory>
#include <string>

int main()
{
    using namespace std;

    // ===== make_shared vs new =====
    // make_shared:一次内存分配(对象+控制块)
    auto sp1 = make_shared<int>(42);

    // new:两次内存分配(对象 + 控制块分开)
    shared_ptr<int> sp2(new int(42));

    // ===== 不要混用裸指针和 shared_ptr =====
    int* raw = new int(42);
    shared_ptr<int> sp3(raw);
    // shared_ptr<int> sp4(raw);   // ❌ 危险!两个独立的引用计数
    // 当 sp3 和 sp4 都析构时,raw 会被释放两次!

    // ===== 不要用 get() 初始化另一个 shared_ptr =====
    auto sp5 = make_shared<int>(42);
    // shared_ptr<int> sp6(sp5.get());   // ❌ 危险!独立的引用计数

    // ===== 正确的共享方式:拷贝 shared_ptr =====
    auto sp7 = sp5;   // ✅ 正确:共享引用计数

    // ===== 自定义删除器 =====
    // 用于管理非内存资源(如文件句柄)
    auto fileDeleter = [](FILE* f) {
        if (f) { fclose(f); cout << "文件已关闭" << endl; }
    };

    shared_ptr<FILE> filePtr(fopen("test.txt", "w"), fileDeleter);
    if (filePtr)
        fputs("Hello, World!\n", filePtr.get());
    // filePtr 离开作用域时自动调用 fileDeleter

    return 0;
}

12.1.5 shared_ptr 的循环引用问题

cpp 复制代码
// circular_reference.cpp -- 循环引用问题
#include <iostream>
#include <memory>
#include <string>

struct Node
{
    string name;
    shared_ptr<Node> next;   // ⚠️ 可能导致循环引用
    weak_ptr<Node>   prev;   // ✅ 使用 weak_ptr 打破循环

    Node(const string& n) : name(n)
    {
        cout << "[创建] " << name << endl;
    }
    ~Node()
    {
        cout << "[销毁] " << name << endl;
    }
};

int main()
{
    using namespace std;

    // ===== 循环引用导致内存泄漏 =====
    {
        auto a = make_shared<Node>("A");
        auto b = make_shared<Node>("B");

        // 如果使用 shared_ptr:
        // a->next = b;   // a 持有 b
        // b->next = a;   // b 持有 a → 循环引用!
        // 离开作用域时,a 和 b 的引用计数都是 1,不会释放!

        // ✅ 使用 weak_ptr 打破循环
        a->next = b;   // a 持有 b(shared_ptr)
        b->prev = a;   // b 弱引用 a(weak_ptr,不增加引用计数)

        cout << "a.use_count() = " << a.use_count() << endl;   // 1
        cout << "b.use_count() = " << b.use_count() << endl;   // 2(a->next持有)
    }
    // a 和 b 都正确销毁

    return 0;
}

12.2 unique_ptr

12.2.1 unique_ptr 基础

cpp 复制代码
// unique_ptr_demo.cpp -- unique_ptr详解
#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Widget
{
public:
    string name;
    Widget(const string& n) : name(n)
    {
        cout << "[创建] " << name << endl;
    }
    ~Widget()
    {
        cout << "[销毁] " << name << endl;
    }
    void show() const { cout << "[显示] " << name << endl; }
};

int main()
{
    using namespace std;

    // ===== 创建 unique_ptr =====
    // 方式1:make_unique(C++14,推荐)
    auto up1 = make_unique<Widget>("Widget1");

    // 方式2:直接构造
    unique_ptr<Widget> up2(new Widget("Widget2"));

    // ===== unique_ptr 独占所有权 =====
    // 不能拷贝
    // auto up3 = up1;   // ❌ 错误!unique_ptr 不能拷贝

    // 可以移动(转移所有权)
    auto up3 = move(up1);   // up1 变为空,up3 接管所有权
    cout << "up1 为空:" << boolalpha << !up1 << endl;   // true
    up3->show();

    // ===== 使用 unique_ptr =====
    up2->show();           // 通过 -> 访问
    (*up2).show();         // 通过 * 解引用
    up2.get();             // 获取裸指针

    // ===== release:放弃所有权,返回裸指针 =====
    Widget* raw = up2.release();   // up2 变为空,raw 需要手动管理
    cout << "up2 为空:" << !up2 << endl;
    delete raw;   // 必须手动释放!

    // ===== reset:重置 =====
    up3.reset();                    // 释放当前对象,up3 变为空
    up3.reset(new Widget("Widget3")); // 释放旧对象,指向新对象

    // ===== unique_ptr 作为函数参数和返回值 =====
    auto makeWidget = [](const string& name) -> unique_ptr<Widget> {
        return make_unique<Widget>(name);   // 移动语义,高效
    };

    auto w = makeWidget("Widget4");
    w->show();

    // ===== unique_ptr 数组 =====
    auto arr = make_unique<int[]>(5);   // 分配5个int的数组
    for (int i = 0; i < 5; i++)
        arr[i] = i * 10;
    cout << "arr[2] = " << arr[2] << endl;

    cout << "\n程序结束:" << endl;
    return 0;
}

12.2.2 unique_ptr 的自定义删除器

cpp 复制代码
// unique_ptr_deleter.cpp -- unique_ptr自定义删除器
#include <iostream>
#include <memory>
#include <cstdio>

// 自定义删除器(函数)
void closeFile(FILE* f)
{
    if (f)
    {
        fclose(f);
        std::cout << "文件已关闭" << std::endl;
    }
}

int main()
{
    using namespace std;

    // 使用函数作为删除器
    // 注意:删除器类型是 unique_ptr 的模板参数
    unique_ptr<FILE, decltype(&closeFile)> fp(fopen("test.txt", "w"),
                                               closeFile);
    if (fp)
        fputs("Hello!\n", fp.get());
    // fp 离开作用域时自动调用 closeFile

    // 使用 lambda 作为删除器
    auto deleter = [](int* p) {
        cout << "自定义删除:" << *p << endl;
        delete p;
    };

    unique_ptr<int, decltype(deleter)> up(new int(42), deleter);
    cout << "*up = " << *up << endl;

    return 0;
}

12.3 weak_ptr

cpp 复制代码
// weak_ptr_demo.cpp -- weak_ptr详解
#include <iostream>
#include <memory>
#include <string>

class Cache
{
private:
    // 使用 weak_ptr 缓存,不影响对象的生命周期
    weak_ptr<string> cached_;

public:
    shared_ptr<string> get(const string& key)
    {
        // 尝试从缓存获取
        auto sp = cached_.lock();   // 尝试获取 shared_ptr
        if (sp)
        {
            cout << "缓存命中:" << *sp << endl;
            return sp;
        }

        // 缓存未命中,创建新对象
        cout << "缓存未命中,创建新对象" << endl;
        sp = make_shared<string>(key + "_value");
        cached_ = sp;   // 存入缓存(weak_ptr)
        return sp;
    }
};

int main()
{
    using namespace std;

    // ===== weak_ptr 基础 =====
    auto sp = make_shared<int>(42);
    weak_ptr<int> wp = sp;   // 不增加引用计数

    cout << "sp.use_count() = " << sp.use_count() << endl;   // 1(wp不计入)

    // ===== 使用 weak_ptr =====
    // 不能直接解引用 weak_ptr,必须先 lock()
    if (auto locked = wp.lock())   // 成功时返回 shared_ptr
    {
        cout << "*locked = " << *locked << endl;
    }

    // ===== 检查 weak_ptr 是否有效 =====
    cout << "wp.expired() = " << boolalpha << wp.expired() << endl;   // false

    sp.reset();   // 释放 shared_ptr

    cout << "sp释放后:" << endl;
    cout << "wp.expired() = " << wp.expired() << endl;   // true

    if (auto locked = wp.lock())
        cout << "获取成功" << endl;
    else
        cout << "对象已销毁,获取失败" << endl;

    // ===== 实际应用:缓存 =====
    cout << "\n===== 缓存示例 =====" << endl;
    Cache cache;

    {
        auto v1 = cache.get("key1");   // 创建新对象
        auto v2 = cache.get("key1");   // 缓存命中
        cout << "v1.use_count() = " << v1.use_count() << endl;   // 2
    }   // v1, v2 离开作用域,对象销毁

    auto v3 = cache.get("key1");   // 缓存失效,重新创建

    return 0;
}

12.4 动态数组

12.4.1 new 和 delete 数组

cpp 复制代码
// dynamic_array.cpp -- 动态数组
#include <iostream>
#include <string>
#include <memory>

int main()
{
    using namespace std;

    // ===== 分配动态数组 =====
    int n = 5;
    int* arr = new int[n];   // 分配n个int

    // 初始化
    for (int i = 0; i < n; i++)
        arr[i] = i * 10;

    // 使用
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;

    // 释放(必须用 delete[])
    delete[] arr;   // ⚠️ 不能用 delete arr!

    // ===== 值初始化 =====
    int* zeros = new int[5]();   // 所有元素初始化为0
    int* ones  = new int[5]{1, 2, 3, 4, 5};   // 列表初始化

    cout << "zeros: ";
    for (int i = 0; i < 5; i++) cout << zeros[i] << " ";
    cout << endl;

    cout << "ones: ";
    for (int i = 0; i < 5; i++) cout << ones[i] << " ";
    cout << endl;

    delete[] zeros;
    delete[] ones;

    // ===== 动态分配大小为0的数组 =====
    // 合法,但不能解引用
    int* empty = new int[0];
    delete[] empty;   // 必须释放

    // ===== 使用 unique_ptr 管理动态数组 =====
    auto uarr = make_unique<int[]>(5);
    for (int i = 0; i < 5; i++)
        uarr[i] = i * i;

    cout << "unique_ptr数组: ";
    for (int i = 0; i < 5; i++) cout << uarr[i] << " ";
    cout << endl;
    // 自动释放,无需 delete[]

    // ===== shared_ptr 管理动态数组(需要自定义删除器)=====
    shared_ptr<int> sarr(new int[5], [](int* p) { delete[] p; });
    // C++17:shared_ptr<int[]> sarr(new int[5]);

    return 0;
}

12.4.2 allocator 类

cpp 复制代码
// allocator_demo.cpp -- allocator类
#include <iostream>
#include <memory>
#include <string>
#include <algorithm>

int main()
{
    using namespace std;

    // allocator:将内存分配和对象构造分离
    // 适用于需要分配大块内存但延迟构造对象的场景

    allocator<string> alloc;

    // ===== 分配内存(不构造对象)=====
    int n = 5;
    string* p = alloc.allocate(n);   // 分配n个string的内存

    // ===== 构造对象 =====
    string* q = p;
    alloc.construct(q++, "Hello");   // 在p[0]构造"Hello"
    alloc.construct(q++, "World");   // 在p[1]构造"World"
    alloc.construct(q++, 3, 'X');    // 在p[2]构造"XXX"

    cout << "构造的对象:" << endl;
    for (string* it = p; it != q; ++it)
        cout << "  " << *it << endl;

    // ===== 销毁对象(不释放内存)=====
    while (q != p)
        alloc.destroy(--q);   // 调用析构函数

    // ===== 释放内存 =====
    alloc.deallocate(p, n);

    // ===== 拷贝和填充未初始化内存 =====
    vector<string> words = {"Hello", "World", "C++", "STL"};

    // 分配足够的内存
    string* buf = alloc.allocate(words.size() * 2);

    // uninitialized_copy:拷贝到未初始化内存
    auto end1 = uninitialized_copy(words.begin(), words.end(), buf);

    // uninitialized_fill_n:填充未初始化内存
    uninitialized_fill_n(end1, words.size(), string("填充"));

    cout << "\n拷贝和填充后:" << endl;
    for (size_t i = 0; i < words.size() * 2; i++)
        cout << "  " << buf[i] << endl;

    // 销毁并释放
    for (size_t i = 0; i < words.size() * 2; i++)
        alloc.destroy(buf + i);
    alloc.deallocate(buf, words.size() * 2);

    return 0;
}

12.5 综合示例:内存安全的对象池

cpp 复制代码
// object_pool.cpp -- 综合示例:对象池
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <functional>
#include <stdexcept>

// 使用智能指针实现的对象池
template <typename T>
class ObjectPool
{
private:
    // 可用对象列表
    vector<unique_ptr<T>> available_;
    // 已分配对象(使用 weak_ptr 跟踪)
    vector<weak_ptr<T>>   inUse_;

    // 工厂函数
    function<unique_ptr<T>()> factory_;

    size_t maxSize_;
    size_t created_ = 0;

public:
    ObjectPool(function<unique_ptr<T>()> factory, size_t maxSize = 10)
        : factory_(factory), maxSize_(maxSize) {}

    // 获取对象(返回 shared_ptr,自动归还)
    shared_ptr<T> acquire()
    {
        T* rawPtr = nullptr;

        if (!available_.empty())
        {
            // 从可用列表取出
            auto up = move(available_.back());
            available_.pop_back();
            rawPtr = up.release();
            cout << "[复用] 对象" << endl;
        }
        else if (created_ < maxSize_)
        {
            // 创建新对象
            auto up = factory_();
            rawPtr = up.release();
            created_++;
            cout << "[创建] 对象(总数:" << created_ << ")" << endl;
        }
        else
        {
            throw runtime_error("对象池已满");
        }

        // 创建 shared_ptr,自定义删除器将对象归还池中
        auto self = this;
        auto deleter = [self](T* p) {
            cout << "[归还] 对象" << endl;
            self->available_.push_back(unique_ptr<T>(p));
        };

        auto sp = shared_ptr<T>(rawPtr, deleter);
        inUse_.push_back(sp);
        return sp;
    }

    size_t availableCount() const { return available_.size(); }
    size_t createdCount()   const { return created_; }

    // 清理已失效的 weak_ptr
    void cleanup()
    {
        inUse_.erase(
            remove_if(inUse_.begin(), inUse_.end(),
                      [](const weak_ptr<T>& wp) { return wp.expired(); }),
            inUse_.end());
    }
};

// 数据库连接(模拟)
class DBConnection
{
private:
    static int nextId;
    int id_;

public:
    DBConnection() : id_(++nextId)
    {
        cout << "  连接 #" << id_ << " 已建立" << endl;
    }
    ~DBConnection()
    {
        cout << "  连接 #" << id_ << " 已关闭" << endl;
    }

    void query(const string& sql) const
    {
        cout << "  [连接#" << id_ << "] 执行:" << sql << endl;
    }

    int getId() const { return id_; }
};

int DBConnection::nextId = 0;

int main()
{
    using namespace std;

    // 创建连接池(最多3个连接)
    ObjectPool<DBConnection> pool(
        []() { return make_unique<DBConnection>(); },
        3
    );

    cout << "===== 获取连接 =====" << endl;
    {
        auto conn1 = pool.acquire();
        conn1->query("SELECT * FROM users");

        auto conn2 = pool.acquire();
        conn2->query("INSERT INTO orders VALUES (...)");

        cout << "\n可用连接:" << pool.availableCount() << endl;
        cout << "已创建:" << pool.createdCount() << endl;
    }   // conn1, conn2 自动归还

    cout << "\n===== 连接归还后 =====" << endl;
    cout << "可用连接:" << pool.availableCount() << endl;

    cout << "\n===== 复用连接 =====" << endl;
    {
        auto conn3 = pool.acquire();   // 复用之前的连接
        conn3->query("UPDATE products SET price = 99");

        auto conn4 = pool.acquire();   // 复用
        auto conn5 = pool.acquire();   // 新建

        cout << "\n可用连接:" << pool.availableCount() << endl;

        // 尝试超出限制
        try
        {
            auto conn6 = pool.acquire();   // 超出限制!
        }
        catch (const runtime_error& e)
        {
            cout << "错误:" << e.what() << endl;
        }
    }

    cout << "\n===== 所有连接归还 =====" << endl;
    cout << "可用连接:" << pool.availableCount() << endl;

    return 0;
}

12.6 智能指针使用规范

cpp 复制代码
// smart_ptr_guidelines.cpp -- 智能指针使用规范
#include <iostream>
#include <memory>
#include <string>

class Widget
{
public:
    string name;
    Widget(const string& n) : name(n) {}
    ~Widget() { std::cout << "销毁 " << name << std::endl; }
};

// ===== 规范1:优先使用 make_shared 和 make_unique =====
void rule1()
{
    // ✅ 推荐
    auto sp = make_shared<Widget>("A");
    auto up = make_unique<Widget>("B");

    // ❌ 不推荐(两次内存分配,异常不安全)
    shared_ptr<Widget> sp2(new Widget("C"));
}

// ===== 规范2:不要用裸指针初始化多个智能指针 =====
void rule2()
{
    Widget* raw = new Widget("D");
    shared_ptr<Widget> sp1(raw);
    // shared_ptr<Widget> sp2(raw);   // ❌ 危险!双重释放

    // ✅ 正确:拷贝 shared_ptr
    auto sp3 = sp1;
}

// ===== 规范3:不要保存 get() 返回的裸指针 =====
void rule3()
{
    auto sp = make_shared<Widget>("E");
    Widget* raw = sp.get();   // 获取裸指针

    // ❌ 不要用 raw 初始化另一个智能指针
    // shared_ptr<Widget> sp2(raw);

    // ❌ 不要 delete raw
    // delete raw;

    // ✅ raw 只用于临时访问
    std::cout << raw->name << std::endl;
}

// ===== 规范4:选择合适的智能指针 =====
// unique_ptr:独占所有权,零开销
// shared_ptr:共享所有权,有引用计数开销
// weak_ptr:观察者,不影响生命周期

// ===== 规范5:函数参数传递规范 =====
// 传递 shared_ptr:表示共享所有权
void takeOwnership(shared_ptr<Widget> sp) { sp->name; }

// 传递引用:不转移所有权,只使用
void useWidget(const Widget& w) { std::cout << w.name << std::endl; }

// 传递 unique_ptr:转移所有权
void transferOwnership(unique_ptr<Widget> up) { up->name; }

int main()
{
    using namespace std;

    rule1();
    rule2();
    rule3();

    auto sp = make_shared<Widget>("F");
    takeOwnership(sp);   // 共享所有权
    useWidget(*sp);      // 只使用,不转移

    auto up = make_unique<Widget>("G");
    transferOwnership(move(up));   // 转移所有权

    return 0;
}

📝 第12章知识点总结

知识点 核心要点
动态内存问题 内存泄漏、重复释放、悬空指针、异常安全问题
shared_ptr 共享所有权,引用计数,计数为0时自动释放
make_shared 推荐方式,一次内存分配(对象+控制块),更安全高效
引用计数 use_count() 查询,拷贝+1,析构-1,为0时释放
循环引用 shared_ptr 循环引用导致内存泄漏,用 weak_ptr 打破
unique_ptr 独占所有权,不能拷贝,可以移动,零开销
make_unique C++14,推荐方式,异常安全
release() 放弃所有权,返回裸指针,需手动管理
reset() 释放当前对象,可选择指向新对象
weak_ptr 弱引用,不增加引用计数,lock() 获取 shared_ptr
expired() 检查 weak_ptr 是否有效(对象是否已销毁)
动态数组 new T[n] 分配,delete[] p 释放,make_unique<T[]>(n) 更安全
allocator 分离内存分配和对象构造,用于高性能场景
自定义删除器 shared_ptr 和 unique_ptr 都支持,用于管理非内存资源
使用规范 优先 make_shared/make_unique,不混用裸指针,不保存 get() 结果
相关推荐
踏着七彩祥云的小丑4 小时前
Go学习第1天:入门
开发语言·学习·golang·go
thisiszdy4 小时前
<C++> 浅拷贝与深拷贝
c++
2023自学中4 小时前
Linux虚拟机 CMakeLists.txt:x86 与 ARM 双架构编译脚本
linux·c语言·c++·嵌入式
眠りたいです5 小时前
现代C++:C++17中的新库特性
开发语言·c++·c++20·c++17
devnullcoffee5 小时前
亚马逊 Buy Box 数据采集完全指南(2026):Python 实战 + Pangolinfo API
开发语言·python·亚马逊数据采集·亚马逊数据 api·pangolinfo api·亚马逊 buy box 数据·亚马逊数据采集软件
sleven fung5 小时前
Whisper库
开发语言·人工智能·python·算法·ai·whisper
天若有情6735 小时前
【C++趣味实战】仿写Burp代理逻辑!自定义可控迭代器:拦截Intercept/放行Forward/重放Repeater全实现
java·开发语言·c++
l1t5 小时前
DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程37-38
开发语言·python
迷藏4945 小时前
Python+DuckDB:轻量级BI流水线实战
java·开发语言·python·原型模式