【探寻C++之旅】C++ 智能指针完全指南:从原理到实战,彻底告别内存泄漏

前言

作为 C++ 开发者,你是否曾因以下场景头疼不已?

  • 函数中new了数组,却因异常抛出导致后续delete没执行,排查半天定位到内存泄漏;
  • 多模块共享一块内存,不知道该由谁负责释放,最后要么重复释放崩溃,要么漏释放泄漏;
  • 用了auto_ptr后,拷贝对象导致原对象 "悬空",访问时直接崩溃却找不到原因。

如果你有过这些经历,那智能指针一定是你必须掌握的现代 C++ 工具。它基于 RAII 思想,自动管理动态资源,让你无需手动delete,从根源上减少内存泄漏风险。今天,我们就从 "为什么需要智能指针" 到 "不同智能指针的实战场景",带你系统掌握这一核心特性。

请君浏览

    • 前言
    • [一、智能指针的诞生:解决手动管理内存的 "千古难题"](#一、智能指针的诞生:解决手动管理内存的 “千古难题”)
      • [1.1 一个典型的内存泄露场景](#1.1 一个典型的内存泄露场景)
      • [1.2 智能指针的核心:RAII 思想](#1.2 智能指针的核心:RAII 思想)
    • [二、C++ 标准库智能指针:4 种指针的特性与适用场景](#二、C++ 标准库智能指针:4 种指针的特性与适用场景)
      • [2.1 auto_ptr:被淘汰的 "过渡品"(C++98)](#2.1 auto_ptr:被淘汰的 “过渡品”(C++98))
      • [2.2 unique_ptr:不可共享的 "独占指针"(C++11)](#2.2 unique_ptr:不可共享的 “独占指针”(C++11))
      • [2.3 shared_ptr:可共享的 "计数指针"(C++11)](#2.3 shared_ptr:可共享的 “计数指针”(C++11))
      • [2.4 weak_ptr:解决循环引用的 "辅助指针"(C++11)](#2.4 weak_ptr:解决循环引用的 “辅助指针”(C++11))
      • [2.5 删除器](#2.5 删除器)
    • [3. shared_ptr的模拟实现](#3. shared_ptr的模拟实现)
      • [3.1 原理](#3.1 原理)
      • [3.2 代码](#3.2 代码)
    • [4. 总结:智能指针的最佳实践](#4. 总结:智能指针的最佳实践)
    • 尾声

一、智能指针的诞生:解决手动管理内存的 "千古难题"

在 C++ 中,内存泄漏的核心原因往往是 "资源申请与释放不匹配"------ 尤其是当程序流程被异常、分支跳转打断时,手动编写的delete可能永远不会执行。

**内存泄漏:**内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。

**危害:**普通程序运⾏⼀会就结束了,出现内存泄漏问题也不大,进程正常结束,⻚表的映射关系解除,物理内存也可以释放。但⻓期运⾏的程序出现内存泄漏影响就很⼤了,如操作系统、后台服务、⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越慢,最终卡死。

1.1 一个典型的内存泄露场景

若函数中存在异常抛出,裸指针会因delete未执行导致泄漏,例如:我们在Func函数中new了两个数组,但如果Divide抛异常,后续的delete会被跳过,导致内存泄漏,如下面代码所示:

cpp 复制代码
double Divide(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
    {
        throw "Divide by zero condition!";
    } 
    else
    {
        return (double)a / (double)b;
    }
} 
void Func()
{
    int* array1 = new int[10];
    int* array2 = new int[10]; 
    //...
    int len, time;
    cin >> len >> time;
    cout << Divide(len, time) << endl;
    // ...
    cout << "delete []" << endl;
    delete[] array1;
    delete[] array2;
}
int main()
{
    try
    {
        Func();
    } 
    catch (...)
    {
        cout << "abnormal" << endl;
    }
    return 0;
}

可以看到当Divide抛出异常时,我们new的两个数组就无法正常释放,导致内存泄漏:

即使我们加了try-catch,若new array2时本身抛异常,array1也无法释放,代码会变得臃肿且脆弱:

cpp 复制代码
void Func()
{
    int* array1 = new int[10];
    int* array2 = new int[10]; 
    //...
    try
    {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    } 
    catch (...)
    {
        cout << "delete []" << endl;
        delete[] array1;
        delete[] array2;
        throw; // 异常重新抛出,捕获到什么抛出什么
    }
    // ...
    cout << "delete []" << endl;
    delete[] array1;
    delete[] array2;
}

即便如此,因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很麻烦,这种场景下,我们需要一种 "能自动释放资源" 的机制 ------ 这就是智能指针的设计初衷。

1.2 智能指针的核心:RAII 思想

在 C++ 中,智能指针 是一种封装了裸指针(raw pointer )的模板类,其核心作用是自动管理动态内存 ,避免因手动调用delete疏忽导致的内存泄漏、重复释放或悬空指针等问题。它基于RAII(资源获取即初始化) 机制:在智能指针构造时获取资源(如动态内存),在析构时自动释放资源,无需手动干预。

智能指针的本质是RAII(Resource Acquisition Is Initialization,资源获取即初始化) 的实践:

  • 资源(如动态内存、文件句柄)在智能指针对象构造时获取,并委托给该对象管理;
  • 智能指针对象析构时自动释放资源,无论程序是正常结束还是异常退出(对象生命周期由作用域管理,析构总会执行);
  • 为了方便使用,智能指针会重载*->[]等运算符,模拟原生指针的行为。

基于此,我们可以先来自己简单粗略的实现一下智能指针,如下面代码所示:

cpp 复制代码
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		: _ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete[] " << _ptr << endl;
		delete[] _ptr;
	}

	// 重载运算符,模拟指针的⾏为,⽅便访问资源
	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	T& operator[](size_t i)
	{
		return _ptr[i];
	}
private:
	T* _ptr;
};

有了智能指针,我们的func函数就可以改成:

cpp 复制代码
void Func()
{
    SmartPtr<int> sp1 = new int[10];
    SmartPtr<int> sp2 = new int[10];
    
    int len, time;
    cin >> len >> time;
    cout << Divide(len, time) << endl;
}

通过运行结果我们可以看到即便Divide函数抛出异常也不会影响我们new出来两个数组的释放:

虽然我们上面设计的智能指针是十分粗略的,但是可以看到即便如此也可以帮助我们解决内存泄漏的问题。那么接下来让我们看一看标准库中是如何设计智能指针的。

二、C++ 标准库智能指针:4 种指针的特性与适用场景

C++ 标准库(<memory>头文件)提供了 4 种智能指针,它们分别是auto_ptr、unique_ptr、shared_ptr、weak_ptr。其中除了auto_ptr外的三个都是在C++11中提出的。除weak_ptr外均遵循 RAII,原理上而⾔主要是解决智能指针拷⻉时的思路不同。下面来让我们看一看它们之间的区别,以及在不同场景下该如何选择。

2.1 auto_ptr:被淘汰的 "过渡品"(C++98)

auto_ptr是C++98时设计出来的智能指针,设计思路是 "拷贝时转移资源管理权"------ 但这是一个致命缺陷:拷贝后原对象会 "悬空"(资源指针被置空),后续访问原对象会触发空指针错误。如下面代码所示:

cpp 复制代码
int main() 
{
    auto_ptr<Date> ap1(new Date); // ap1管理Date对象
    auto_ptr<Date> ap2(ap1);      // 拷贝:ap2获取管理权,ap1->_ptr被置空

    // ap1->_year++; // 崩溃!ap1已悬空,访问空指针
    return 0;
}

auto_ptr拷贝的原理是将自己的指针赋值给新的auto_ptr,并且使自己的指针置为空,这样我们再去访问这个对象时,就会因为访问空指针而导致报错。可以看到,当我们将ap1拷贝给ap2后,我们就访问ap1时就会报错,因为ap1已经悬空,我们不能去访问空指针。

正因如此,C++11 推出后,auto_ptr被明确标记为 "不推荐使用",多数公司的编码规范也会直接禁止它。

相关文档auto_ptr

2.2 unique_ptr:不可共享的 "独占指针"(C++11)

unique_ptr(唯⼀指针)的设计思路是禁止拷贝、仅支持移动 ------ 确保同一时间只有一个unique_ptr管理资源,从根源上避免 "多个指针竞争释放" 的问题。它是 C++11 中最常用的智能指针之一,适用于 "资源无需共享" 的场景。

核心特性:

  1. 不可复制,只能移动:由于是独占所有权,unique_ptr 不支持复制构造或赋值(会编译报错),但可以通过 std::move 转移所有权(转移后原 unique_ptr 会失效,变为空指针)。
  2. 高效轻量:无额外引用计数开销,性能接近裸指针。
cpp 复制代码
int main() 
{
    // 创建unique_ptr(推荐用make_unique,C++14起支持)
    std::unique_ptr<int> ptr1 = std::make_unique<int>(10); 
    std::cout << *ptr1 << std::endl; // 输出:10

    // 转移所有权(ptr1失效,ptr2拥有对象)
    std::unique_ptr<int> ptr2 = std::move(ptr1); 
    if (ptr1 == nullptr) {
        std::cout << "ptr1已失效" << std::endl; // 输出:ptr1已失效
    }

    // 超出作用域时,ptr2析构,自动释放内存
    return 0;
}

适用场景:管理独占资源(如局部动态对象、类的成员变量),作为函数返回值(无需手动释放,避免返回裸指针的风险)。

相关文档unique_ptr

2.3 shared_ptr:可共享的 "计数指针"(C++11)

shared_ptr(共享指针)允许多个 shared_ptr 共同拥有同一个动态对象。。也就是说支持资源共享,其核心是通过 "引用计数" 跟踪管理资源的指针数量:

  • 当新的shared_ptr拷贝或赋值时,引用计数+1
  • shared_ptr析构时,引用计数-1
  • 当引用计数减至0时,代表当前是最后一个管理资源的指针,自动释放资源。

核心特性:

  1. 引用计数透明管理:用户无需手动维护计数,use_count()方法可查看当前计数;
  2. 支持拷贝与移动:拷贝时计数+1,移动时计数不变(原对象悬空);
cpp 复制代码
int main() {
    // 创建shared_ptr(推荐用make_shared,更高效)
    std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
    std::cout << "引用计数:" << ptr1.use_count() << std::endl; // 输出:1

    // 复制ptr1,引用计数+1
    std::shared_ptr<int> ptr2 = ptr1; 
    std::cout << "引用计数:" << ptr1.use_count() << std::endl; // 输出:2

    // ptr2超出作用域,引用计数-1
    {
        std::shared_ptr<int> ptr3 = ptr1;
        std::cout << "引用计数:" << ptr1.use_count() << std::endl; // 输出:3
    }
    std::cout << "引用计数:" << ptr1.use_count() << std::endl; // 输出:2

    // 最后ptr1和ptr2析构,引用计数变为0,内存释放
    return 0;
}

shared_ptr的构造有两种方式,除了⽀持⽤指向资源的指针构造,还⽀持 make_shared ⽤初始化资源对象的值直接构造:

  1. shared_ptr<Date> sp(new Date(2024, 10, 1));:两次内存分配(一次给Date对象,一次给引用计数);

  2. auto sp = make_shared<Date>(2024, 10, 1);:一次内存分配(同时存储Date对象和引用计数),效率更高,且避免内存泄漏风险(若new成功但计数分配失败,new的对象无法释放)。

    cpp 复制代码
    template <class T, class... Args> 
    shared_ptr<T> make_shared(Args&&... args);

对于shared_ptrunique_ptr,我们还需要注意下面几点:

  • shared_ptrunique_ptr 都⽀持了operator bool的类型转换:如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

  • shared_ptrunique_ptr 的构造函数都使⽤explicit 修饰,防⽌普通指针隐式类型转换成智能指针对象。

    cpp 复制代码
    // 报错:无法进行隐式类型转换
    shared_ptr<Date> sp5 = new Date(2024, 9, 11);
    unique_ptr<Date> sp6 = new Date(2024, 9, 11);

使用shared_ptr还要注意线程安全问题,shared_ptr的引⽤计数对象在堆上,如果多个shared_ptr对象在多个线程中,进⾏shared_ptr的拷贝和析构时会访问修改引⽤计数,就会存在线程安全问题,所以shared_ptr引⽤计数是需要加锁或者原⼦操作保证线程安全的。
相关文档shared_ptr

2.4 weak_ptr:解决循环引用的 "辅助指针"(C++11)

weak_ptr(弱指针)完全不同于上⾯的智能指针,是一个特殊的智能指针。它不⽀持RAII,也就意味着不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr的⼀个循环引⽤导致内存泄漏的问题。

什么是循环引用呢?shared_ptr 的引用计数机制可能导致循环引用问题:两个对象互相持有对方的 shared_ptr,此时它们的引用计数永远不会变为 0,导致内存泄漏。

例如:我们有两个链表结点,把它们分别交给智能指针shared_ptr管理,然后将它们连接起来,如下面代码所示:

cpp 复制代码
struct ListNode 
{
    int _data;
    shared_ptr<ListNode> _next; // 指向后一个节点
    shared_ptr<ListNode> _prev; // 指向前一个节点
    
    ~ListNode() 
    { 
        cout << "~ListNode()" << endl; 
    }
};

int main() 
{
    shared_ptr<ListNode> n1(new ListNode);
    shared_ptr<ListNode> n2(new ListNode);
    cout << n1.use_count() << endl; // 1
    cout << n2.use_count() << endl; // 1

    n1->_next = n2; // n2的计数+1 → 2
    n2->_prev = n1; // n1的计数+1 → 2

    // 析构n1和n2:计数各减1 → 1(而非0)
    // 节点资源永远无法释放,内存泄漏!
    return 0;
}

循环引用的逻辑链:n1->_next依赖n2释放,n2->_prev依赖n1释放,最终谁都无法释放。

那么该如何解决循环引用呢?这时候weak_ptr就派上用场了。weak_ptr 是一种弱引用 智能指针,它不拥有对象的所有权,也不会增加引用计数,因此我们修改链表节点为weak_ptr后,循环引用被打破:

cpp 复制代码
struct ListNode 
{
    int _data;
    weak_ptr<ListNode> _next; // 改为weak_ptr,不增加计数
    weak_ptr<ListNode> _prev;
    ~ListNode() { cout << "~ListNode()" << endl; }
};

int main() 
{
    shared_ptr<ListNode> n1(new ListNode);
    shared_ptr<ListNode> n2(new ListNode);

    n1->_next = n2; // 不增加n2的计数(仍为1)
    n2->_prev = n1; // 不增加n1的计数(仍为1)

    // 析构n1和n2:计数各减1 → 0,节点资源正常释放
    return 0;
}

weak_ptr不⽀持RAII,也不⽀持访问资源,所以weak_ptr构造时不⽀持绑定到资源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。

weak_ptr的核心特性是:绑定到shared_ptr时不增加引用计数 ,仅作为 "观察者" 跟踪资源是否有效。weak_ptr也没有重载operator*operator->等,因为他不参与资源管理 。那么如果它绑定的shared_ptr已经释放了资源,那么它去访问资源就是很危险的,为此它提供了两个关键方法:

  • expired():判断绑定的shared_ptr资源是否已释放(计数为 0);
  • lock():若资源有效,返回一个shared_ptr(计数 + 1,安全访问资源);若无效,返回空shared_ptr
cpp 复制代码
int main()
{
    std::shared_ptr<string> sp1(new string("111111"));
    std::shared_ptr<string> sp2(sp1);
    
    std::weak_ptr<string> wp = sp1;
    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;
    
    // sp1和sp2都指向了其他资源,则weak_ptr就过期了
    sp1 = make_shared<string>("222222");
    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;
    
    sp2 = make_shared<string>("333333");
    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;
    
    wp = sp1;
    //std::shared_ptr<string> sp3 = wp.lock();
    auto sp3 = wp.lock();
    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;
    
    *sp3 += "###";
    
    cout << *sp1 << endl;
    return 0;
}

weak_ptr仅作为shared_ptr的辅助工具,解决循环引用(如链表、树、图等数据结构的节点引用)。

相关文档weak_ptr

2.5 删除器

智能指针析构时默认是进⾏delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。**因此当管理非new资源(如new[]、文件指针)时,需自定义删除器。**智能指针⽀持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调⽤对象,在这个可调⽤对象中实现你想要的释放资源的⽅式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调⽤删除器去释放资源。因为new[]经常使⽤,所以为了简洁,unique_ptrshared_ptr都特化了⼀份[]的版本:

cpp 复制代码
int main()
{
    // 这样实现程序会崩溃
    // unique_ptr<Date> up1(new Date[10]);
    // shared_ptr<Date> sp1(new Date[10]);
    
    // 因为new[]经常使⽤,所以unique_ptr和shared_ptr
    // 实现了⼀个特化版本,这个特化版本析构时⽤的delete[]
    unique_ptr<Date[]> up1(new Date[5]);
    shared_ptr<Date[]> sp1(new Date[5]);
    return 0;
}

除此之外,我们还可以自定义删除器,这里我们有三种方式:

  • 仿函数

    cpp 复制代码
    template<class T>
    class DeleteArray
    { 
    public:
    	void operator()(T* ptr)
        {
        	delete[] ptr;
        }
    };
    
    unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
    shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());
  • 函数指针

    cpp 复制代码
    template<class T>
    class DeleteArray
    { 
    public:
        void operator()(T* ptr)
        {
        	delete[] ptr;
        }
    }
    
    unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
    shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);
  • lambda表达式

    cpp 复制代码
    auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
    
    unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);
    shared_ptr<Date> sp4(new Date[5], delArrOBJ);

我们可以看到,使用不同的可调用对象,unique_ptrshared_ptr需要传入的参数也是不同的,这是因为unique_ptrshared_ptr⽀持删除器的⽅式有所不同:

  • unique_ptr是在类模板参数⽀持的;
  • shared_ptr是构造函数参数⽀持的。

这⾥没有使⽤相同的⽅式还是挺不方便,也是标准库中的一点小弊端。

使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调用,但是函数指针和lambda表达式的类型是不可以的,所以在传参时不仅要在模板传类型,还要在构造传入相应的对象。

3. shared_ptr的模拟实现

想要加深对智能指针的印象,我们可以自己来模拟实现一下智能指针,在标准库中的四种智能指针中shared_ptr涉猎最广,所以我们来模拟实现一下shared_ptr,当然我们这里只是简单的模拟,标准库中的shared_ptr的实现是极为复杂的。

3.1 原理

实现shared_ptr我们需要搞定两个比较重要的东西,其中之一是引用计数的设计,主要这⾥⼀份资源就需要⼀个引⽤计数,所以引⽤计数采⽤静态成员的⽅式是⽆法实现的,要使⽤堆上动态开辟的⽅式,构造智能指针对象时来⼀份资源,就要new⼀个引⽤计数出来。多个shared_ptr指向资源时就++引⽤计数,shared_ptr对象析构时就--引⽤计数,引⽤计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象,则析构资源。

其次就是删除器,我们要在构造时传入删除器,但是在析构时才会使用删除器,也就是说我们需要将删除器保存为成员函数,这样才能在析构时去调用,那么我们该如何保存删除器呢?我们知道函数指针、仿函数、lambda表达式这些都可以做删除器,这时候我们就需要用到function包装器(详情点击)了,通过包装器来存储删除器,这样就可以存储不同的删除器了:function<void(T*)> _del = [](T* ptr) {delete ptr; };

3.2 代码

下面让我们来看具体的代码实现:

cpp 复制代码
template<class T>
class shared_ptr
{
public:
    explicit shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        , _pcount(new int(1))
    {}
    
    template<class D>
    shared_ptr(T* ptr, D del)
        : _ptr(ptr)
        , _pcount(new int(1))
        , _del(del)
    {}
    
    shared_ptr(const shared_ptr<T>& sp)
        :_ptr(sp._ptr)
        , _pcount(sp._pcount)
        , _del(sp._del)
    {
        ++(*_pcount);
    }
    
    void release()
    {
        if (--(*_pcount) == 0)
        {
            // 最后⼀个管理的对象,释放资源
            _del(_ptr);
            delete _pcount;
            _ptr = nullptr;
            _pcount = nullptr;
        }
    }
    
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        if (_ptr != sp._ptr)
        {
            release();
            _ptr = sp._ptr;
            _pcount = sp._pcount;
            ++(*_pcount);
            _del = sp._del;
        }
        return *this;
    }
    
    ~shared_ptr()
    {
        release();
    }
    
    //获得原生指针
    T* get() const
    {
        return _ptr;
    }
    
    int use_count() const
    {
        return *_pcount;
    }
    
    T& operator*()
    {
        return *_ptr;
    }
    
    T* operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
    int* _pcount;
    function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

需要注意的是我们这⾥实现的shared_ptr是以最简洁的⽅式实现的,只能满⾜基本的功能。感兴趣的可以去查看源代码。

4. 总结:智能指针的最佳实践

掌握智能指针后,我们可以从 "手动管理内存" 的焦虑中解放出来。以下是核心实践原则:

  1. 优先用 unique_ptr :若资源无需共享,unique_ptr是最高效的选择(无引用计数开销);
  2. 共享用 shared_ptr :需多模块共享资源时用shared_ptr,优先用make_shared优化;
  3. 循环引用用 weak_ptr :链表、树等结构中,节点间引用用weak_ptr避免泄漏;
  4. 自定义删除器 :管理new[]、文件句柄等非new资源时,务必指定删除器;
  5. 避免裸指针混用:尽量不要用智能指针管理 "已被裸指针管理的资源",避免重复释放。

最后记住:智能指针不是 "银弹",但它是现代 C++ 中避免内存泄漏的最有效工具。用好智能指针,让你的代码更安全、更优雅!

尾声

若有纰漏或不足之处欢迎大家在评论区留言或者私信,同时也欢迎各位一起探讨学习。感谢您的观看!

相关推荐
草莓熊Lotso5 小时前
《算法闯关指南:优选算法--前缀和》--27.寻找数组的中心下标,28.除自身以外数组的乘积
开发语言·c++·算法·rpc
Cx330❀5 小时前
《C++ 继承》三大面向对象编程——继承:派生类构造、多继承、菱形虚拟继承概要
开发语言·c++
cookies_s_s5 小时前
项目--缓存系统(C++)
c++·缓存
筱砚.5 小时前
【STL——set与multiset容器】
开发语言·c++·stl
Elias不吃糖5 小时前
C++ 中的浅拷贝与深拷贝:概念、规则、示例与最佳实践(笔记)
开发语言·c++·浅拷贝·深拷贝
恒者走天下5 小时前
cpp / c++春招辅导5k吗
c++
喜欢吃燃面5 小时前
C++:红黑树
开发语言·c++·学习
给大佬递杯卡布奇诺6 小时前
FFmpeg 基本数据结构 URLContext分析
数据结构·c++·ffmpeg·音视频
橘子师兄6 小时前
c++中list详解
开发语言·c++