手把手模拟实现 C++ STL::list——从双向链表看懂迭代器设计

别再搞混了!Vue3里xgplayer播放FLV视频与FLV直播流,配置到底差在哪? 本文详细解析了在Vue3项目中使用xgplayer播放FLV视频与FLV直播流的配置差异,帮助开发者避免常见错误。通过对比点播与直播的核心协议差异、插件选择及配置参数,提供实战代码示例和性能优化技巧,确保流畅播放体验。特别针对直播流场景,深入讲解专属配置和问题排查方法。 阅读详情

摘要:本文从零开始模拟实现 C++ STL 中的 list 容器,核心内容包括:哨兵头节点的循环双向链表结构、通过三个模板参数统一 iterator 与 const_iterator 的设计技巧、insert/erase 的底层实现与复用,以及 list 与 vector 在迭代器失效问题上的本质区别。全文以"先写一版,发现问题,再优化"的演进思路展开,适合学完类和对象、模板基础后想要进阶的读者。


一、list 和 vector 有什么不同

上一篇文章我们模拟实现了 vector——它的底层是一块连续堆内存,T* 就是天然的迭代器。轮到 list,事情就完全不一样了。

list 是双向链表。每个元素是一个独立的节点,挂在堆上,节点之间用指针串起来。这带来了两个根本性的变化:

  1. 插入删除不用搬数据——不需要像 vector 那样把后面的元素全部前移或后移,改两根指针就行。
  2. 迭代器不能用原生指针——Node* 做 ++ 做不到跳到下一个节点,除非你重载 operator++。但原生指针的类型你能改吗?不能。

所以模拟实现 list 的关键战场不在 push_back、insert 这些操作本身——它们的链表逻辑非常直观。核心难点在于迭代器的设计。这也是为什么 STL 里 list 的源码,迭代器部分占了大头。

本文代码在 VS2022 下编写编译,文中出现的代码片段来自实际的学习过程——包括那些被注释掉的"第一版",它们记录了迭代器设计从笨拙到优雅的演进路径。


二、节点设计:一切从 list_node 开始

链表的最小单元是节点,它要存三样东西:数据、前驱指针、后继指针。

template<class T>
struct list_node
{
    T _data;
    list_node<T>* _next;
    list_node<T>* _prev;

    list_node(const T& data = T())
        :_data(data)
        ,_next(nullptr)
        ,_prev(nullptr)
    {}
};

几个细节值得一说:

为什么用 struct 不用 class? 因为后面 list 和 list_iterator 都要直接访问节点的 _data、_next、_prev,用 class 就必须加上 public:,否则默认全部私有,编译的时候一片报错。很多开源库都直接用 struct 定义节点,省得写 public。这是一个小习惯,但踩过坑就记住了。

构造函数默认参数 T()。 list_node<T> 构造时如果不传参数,会用 T() 作为默认值。对于 int,int() 就是 0;对于 string,string() 就是空串。这保证了泛型代码下内置类型和类类型的行为一致。

注意:构造函数把 _next 和 _prev 都初始化为 nullptr,但真正使用时链表会立刻把节点的前后指针接好,所以这个 nullptr 只是"出厂设置",不参与运行时的逻辑。


三、迭代器:整个 list 实现中最值得讲的部分

3.1 为什么必须自己写迭代器类

对 vector 来说,T* 就是迭代器——++p 就是指针偏移,*p 就是解引用。但对 list 来说:

list_node<int>* p = ...;
++p;   // p 指针自己加 1,指向了不相干的内存,根本不是"下一个节点"

要让 ++ 的行为变成 p = p->_next,就必须把节点指针包在一个类里,重载 operator++。这就是 list_iterator 存在的根本原因。

3.2 第一版:普通迭代器和 const 迭代器各写一份

最直观的思路是写两个类:list_iterator(可读写)和 list_const_iterator(只读)。代码大约长这样:

// 普通迭代器
template<class T>
struct list_iterator
{
    Node* _node;
    T& operator*() { return _node->_data; }
    Self& operator++() { _node = _node->_next; return *this; }
    // ...--, !=, ++(int), --(int)
};

// const 迭代器
template<class T>
struct list_const_iterator
{
    Node* _node;
    const T& operator*() { return _node->_data; }
    Self& operator++() { _node = _node->_next; return *this; }
    // ...--, !=, ++(int), --(int)
};

两个类除了 operator* 和 operator-> 的返回类型不同(T& vs const T&、T* vs const T*),其余代码完全一模一样。这是典型的"代码重复"——违反了 DRY 原则。

为了避免写出这样 80% 雷同的代码,我们用一记妙招来解决:用模板参数来区分。这是一个多震撼的时刻!模板不只是用来让容器支持不同类型的元素的,还可以用来描述接口的读写属性。

3.3 第二版:三个模板参数,一份代码兼容两种迭代器

template<class T, class Ref, class Ptr>
struct list_iterator
{
    typedef list_node<T> Node;
    typedef list_iterator<T, Ref, Ptr> Self;

    list_iterator(Node* node)
        :_node(node)
    {}

    Node* _node;

    Ref operator*() const
    {
        return _node->_data;
    }

    Ptr operator->() const
    {
        return &_node->_data;
    }

    // ...
};

然后在 list 类里用 typedef 实例化出两种迭代器:

template<class T>
class list
{
    typedef list_node<T> Node;
public:
    typedef list_iterator<T, T&,       T*>        iterator;
    typedef list_iterator<T, const T&, const T*>  const_iterator;
};

这招妙在哪? 编译期就把所有区别消化掉了:

  • iterator 的 Ref = T&,operator* 返回 T&,可读可写。
  • const_iterator 的 Ref = const T&,operator* 返回 const T&,只读。
  • Ptr 同理——T* vs const T*。

一份迭代器代码,两种行为,全在 typedef 那一行决定。这就是 C++ 编译期多态的威力——没有虚函数、没有运行时开销,却实现了接口的灵活切换。

易错点①:operator-> 返回的是地址,不是值。 _node->_data 是节点里存的数据本身(类型 T),但 operator-> 应该返回指针,所以必须写成 &_node->_data。第一次写很容易手滑漏掉 &,编译器报"无法将 T 转换为 T*"——你忘了取地址。

3.4 前置 ++/-- 和后置的区别

// 前置 ++it
Self& operator++()
{
    _node = _node->_next;
    return *this;           // 返回自增后的自己(引用)
}

// 后置 it++
Self operator++(int)
{
    Self tmp(*this);        // 保存自增前的副本
    _node = _node->_next;   // 自身前进
    return tmp;             // 返回旧副本(值,不是引用)
}

// 前置 --it
Self& operator--()
{
    _node = _node->_prev;
    return *this;
}

// 后置 it--
Self operator--(int)
{
    Self tmp(*this);
    _node = _node->_prev;
    return tmp;
}

两张表说清楚区分:

前置 ++it后置 it++
返回类型Self&(自身引用)Self(旧副本,按值返回)
核心逻辑前进 → 返回 *this保存 tmp → 前进 → 返回 tmp
能否链式++++it 可以it++++ 不行(右值)
前置 --it后置 it--
返回类型Self&Self
核心逻辑后退 → 返回 *this保存 tmp → 后退 → 返回 tmp

易错点②:后置 ++ 返回 Self&(加了个 &)是高频错误。 如果返回引用,那引用绑定到谁?绑定到 tmp?tmp 是栈上的局部变量,函数结束就没了——悬空引用。绑定到 *this?那返回的是前进之后的状态,跟前置 ++ 没有任何区别了。所以后置必须返回值,多一次拷贝,但行为正确。标准库也是这么设计的——这也是为什么建议优先用前置 ++,少一次拷贝。

3.5 一个容易被忽略的点:迭代器不需要深拷贝

迭代器不需要深拷贝,需要的就是浅拷贝。因为迭代器里面放的是指向资源的指针,而资源不属于迭代器所有。

这个理解很重要。list_iterator 只持有 Node* _node,拷贝迭代器时只拷贝这个指针——两个迭代器指向同一个节点,完全没问题。迭代器不拥有节点的所有权,不负责释放。所以编译器默认生成的拷贝构造和赋值就已经完全正确了。

这一点和 vector 里说的"深浅拷贝"问题正好形成对比:vector 本身拥有资源,必须深拷贝;迭代器不拥有资源,浅拷贝就是正确答案。 什么时候写拷贝构造、什么时候不写,判断标准就是"你到底有没有拥有那块资源"。


四、list 类:哨兵头节点的循环双向链表

4.1 哨兵头节点——一个不存数据的"看门人"

list 的底层结构不是"一条线段",而是一个环。环的入口是一个特殊的节点——哨兵头节点(sentinel node),它不存有效数据,只起标记作用。

void empty_init()
{
    _head = new Node(T());     // 头节点,它的 _data 没有实际意义
    _head->_next = _head;      // 自己指向自己,形成环
    _head->_prev = _head;
    _size = 0;
}

刚创建的空链表长这样:

     ┌──────────────┐
     │   _head      │
     │  (哨兵节点)    │
     │ _next ───────┼───┐
     │ _prev ───────┼─┐ │
     └──────────────┘ │ │
        ↑             ↓ ↓
        └─────────────┘─┘
        自己指自己,一个环

插入了 3 个元素之后:

_head  ⇄  node1  ⇄  node2  ⇄  node3  ⇄  _head
  ↑          ↑
end()      begin()

有了这个结构,很多操作变得极其简洁:

  • 第一个元素 = _head->_next
  • 最后一个元素 = _head->_prev
  • 链表为空 = _head->_next == _head(自己指自己)

哨兵头节点的存在消除了大量空链表的特殊判断——push_back 在空链表上也不需要区分"有没有第一个节点",因为始终有一个 _head 作为锚点。

4.2 begin 和 end

iterator begin()
{
    return _head->_next;   // 头节点的下一个,即第一个有效元素
}

iterator end()
{
    return _head;          // 头节点本身,不存数据,只做终止标记
}

const_iterator begin() const
{
    return _head->_next;
}

const_iterator end() const
{
    return _head;
}

begin() 返回第一个有效元素,end() 返回哨兵头节点自身。这形成了一个左闭右开的区间 [begin, end)——遍历时到了 end() 就停,end() 本身不参与解引用。

const 版本的重载让 print_container(const container& con) 这种函数能正常工作——它只能调用 const 版的 begin() 和 end(),拿到的是 const_iterator,不允许修改容器内容。

4.3 构造函数全家桶

// 默认构造
list()
{
    empty_init();
}

// 拷贝构造
list(const list<T>& lt)
{
    empty_init();           // 先搭好空壳
    for (auto& e : lt)
    {
        push_back(e);       // 逐个拷进来
    }
}

// initializer_list 构造——支持 {1,2,3,4,5} 语法
list(initializer_list<T> il)
{
    empty_init();
    for (auto& e : il)
    {
        push_back(e);
    }
}

拷贝构造必须先 empty_init() 再逐个 push_back——不能直接把 _head 指向源链表的头节点,不然两个 list 共享同一串节点,析构时 double free。这和 vector 的深浅拷贝问题本质相同,只是表现形式不同。

initializer_list 构造让我们能用 list<int> lt = {1,2,3,4,5} 来初始化,和标准库保持一致。

4.4 析构和清理

void clear()
{
    auto it = begin();
    while (it != end())
    {
        it = erase(it);     // erase 返回下一个有效迭代器
    }
}

~list()
{
    clear();
    delete _head;           // 别忘了头节点
    _head = nullptr;
}

clear() 遍历删除所有有效节点(不包括头节点),~list() 在 clear() 的基础上再删除头节点本身。顺序不能错——先删数据节点,最后才删哨兵。

4.5 赋值运算符

void swap(list<T>& lt)
{
    std::swap(_head, lt._head);
    std::swap(_size, lt._size);
}

list<T>& operator=(list<T> lt)   // 按值传递,自动拷贝
{
    swap(lt);                     // 交换内部指针
    return *this;                 // lt 析构时带走旧资源
}

这里用了和上一篇 vector 一样的 copy-and-swap 惯用法。参数按值传递,调用时触发拷贝构造生成了 lt,然后直接 swap 交换两个对象的内部数据,函数结束时形参 lt 析构,自动释放旧链表的所有节点。不需要手动 if (this != &lt) 判断自赋值,也天然异常安全。

易错点③:swap 交换的是指针,不是链表中的所有节点。 很多人第一反应是"交换两个链表"就需要遍历所有节点、逐个拷过去。实际上只需要交换 _head 和 _size,两个变量的值一换,整个链表的所有权就转移了——这就是指针的威力。


五、增删操作:insert 和 erase 是两大基石

你会发现 push_back、push_front、pop_back、pop_front 的代码都非常短——因为它们全是在调 insert 和 erase。这是一种"分层复用"的设计思路。

5.1 insert——在指定位置前插入

void insert(iterator pos, const T& x)
{
    Node* cur = pos._node;        // pos 指向的节点
    Node* prev = cur->_prev;      // pos 的前一个节点

    Node* newnode = new Node(x);

    // 四步接线:prev ⇄ newnode ⇄ cur
    newnode->_next = cur;
    cur->_prev = newnode;
    prev->_next = newnode;
    newnode->_prev = prev;

    ++_size;
}

图解一下这四步接线:

插之前:  prev  ⇄  cur

插之后:  prev  ⇄  newnode  ⇄  cur

先把将要插入的节点的前后位置用prev和cur记录下来,就不用担心先改 prev->_next会丢掉 cur 的入口。此时四条赋值语句之间没有依赖关系,顺序无所谓,只要四条都写上就对了。

push_back 和 push_front 不过是 insert 的"快捷方式":

void push_back(const T& x)
{
    insert(end(), x);         // 在 end()(即 _head)前面插入 → 相当于尾插
}

void push_front(const T& x)
{
    insert(begin(), x);       // 在 begin() 前面插入 → 相当于头插
}

这就是哨兵头节点带来的红利——push_back 在空链表上工作也不需要特殊处理,因为 end() 始终返回 _head,_head->_prev 又绕回来指到自己。

易错点④:list 的 insert 不会导致任何迭代器失效。 因为链表节点是独立分配的,插入新节点只是在堆上新开辟一块内存、改几根指针的指向。原来所有节点的地址完全不变,指向它们的迭代器当然也不受影响。这一点和 vector 完全不同,在第六节会详细对比。

5.2 erase——删除并返回下一个

iterator erase(iterator pos)
{
    assert(pos != end());              // 头节点不能删

    Node* prev = pos._node->_prev;
    Node* next = pos._node->_next;

    prev->_next = next;                // prev 绕过 pos 直接指向 next
    next->_prev = prev;
    delete pos._node;                  // 释放节点内存

    --_size;
    return next;                       // 返回被删节点的下一个
}

erase 返回 iterator(next) 非常重要——它指向被删除节点的下一个位置。调用方拿到这个返回值,就能在遍历删除时正确前进:

// 删除所有偶数——正确的写法
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
    if (*it % 2 == 0)
    {
        it = lt.erase(it);    // erase 返回下一个,不需要再 ++it
    }
    else
    {
        ++it;
    }
}

pop_back 和 pop_front 同样复用 erase:

void pop_back()
{
    erase(--end());           // end() 是 _head,--end() 就是最后一个有效节点
}

void pop_front()
{
    erase(begin());
}

六、迭代器失效:list vs vector

这是面试和笔试中的高频考点。一句话总结:list 只有被删除的那个元素的迭代器会失效,其余全部安全;vector 则广泛得多。

操作vectorlist
insert若触发扩容,所有迭代器失效;未扩容,插入位置及之后的迭代器失效永不失效
erase被删元素及之后的所有迭代器失效只有被删元素的迭代器失效
push_back若触发扩容,全部失效永不失效

这个差异的根源在于底层的物理结构:

  • vector 是连续内存。扩容时整块内存换了地址,所有指向旧地址的迭代器跟着全部作废。即便是 erase(不换地址),被删元素后面的所有元素都要前移一位,指向这些位置的迭代器自然无效。
  • list 是独立节点。每个节点有自己的堆地址,跟其他节点互不干扰。插入新节点不影响旧节点,删除节点只影响被删的那个——前后节点依然在原地,指向它们的迭代器依然有效。

这就是为什么 erase 必须返回 iterator:被删的那个 pos 已经失效了,但你需要知道下一个有效位置在哪,所以返回 next。而 insert 就不需要返回值——调用方传进来的 pos 依然有效(list 中 insert 不失效迭代器)。


七、辅助工具:print_container 的泛型实践

template <class container>
void print_container(const container& con)
{
    typename container::const_iterator it = con.begin();
    // const 对象只能调 const 版本的 begin/end,返回 const_iterator

    for (auto e : con)
    {
        cout << e << " ";
    }
    cout << endl;
    cout << "----------------" << endl;
}

这个函数不依赖具体类型——传 jys::list<int> 可以,传 std::vector<int> 也可以,只要容器有 const_iterator、begin()、end()。这就是面向接口编程。

注意第一行 typename container::const_iterator 的 typename 不能省——编译器在模板没实例化之前,不知道 container::const_iterator 是类型还是静态成员变量,必须显式声明它是一个类型名。

另外注意参数是 const container&,所以只能调 const 版本的 begin() 和 end()。如果我们的 list 类没有给 begin() 和 end() 写 const 重载,这个函数就编不过——这反过来验证了 const 重载的必要性。


八、易错点全景回顾

编号问题后果对策
①list_node 用 class 不加 public:所有成员私有,list 和迭代器全报访问错误加 public:,或用 struct
②operator-> 返回 _data 而非 &_data类型不匹配,编译器报错写 &_node->_data
③后置 ++/-- 返回 Self&返回局部对象引用,悬空引用 / UB改为 Self,按值返回 tmp
④erase 后不用返回值迭代器失效后继续使用,跳过元素或崩溃始终 it = lt.erase(it)
⑤list 的 insert 误以为迭代器会失效写了类似 vector 中的 pos 重新定位代码list 的 insert 不需要——直接传 pos._node 就行
⑥忘记给 begin()/end() 写 const 重载const 引用无法遍历,print_container 编不过iterator 和 const_iterator 各一套 begin/end

九、写在最后

写完 list,再回头看上一篇的 vector,你会发现这两个容器几乎是"互为镜像"的存在:

  • vector 的优势是随机访问 O(1),代价是中间插入删除要搬数据。
  • list 的优势是任意位置插入删除 O(1),代价是访问第 n 个元素要顺着指针走 n 步。

从实现角度看,vector 的挑战在容量管理(reserve / resize / 扩容策略),list 的挑战在迭代器设计。而这两个挑战背后的 C++ 核心知识——RAII、模板、运算符重载、编译器生成函数的规则、深浅拷贝——恰恰是同一个知识体系的不同侧面。

如果你能把 vector 和 list 都从头写一遍,理解为什么每一行代码那样写,C++ 的类和对象、模板、内存管理这三个重要知识点基本就掌握了。


完整代码可以在学习仓库中找到。文中保留了注释掉的"第一版"迭代器代码——这段演进的历史比一份完美的最终版更有学习价值,它记录了一个关键的思维跃迁:当代码出现大量重复时,模板参数可能是更好的抽象手段,而不只是加一个类。

inno setup自定义卸载程序和美化 也就是DisplayName,因为我们在第一步的时候,将卸载程序名称也写入了注册表,所以,我们直接从注册表读DisplayName字段即可。1.要实现inno setup自定义卸载程序,首先需要关闭自带的卸载程序。在[Setup]的字段关闭卸载程序步骤和页面。2.删除应用程序目录里能够删除的并且返回进度。3.自定义卸载程序相关的注册表信息。4.删除启动菜单栏和guid注册表。2.自定义快捷方式和程序菜单。 阅读详情

相关推荐

【PT+Arduino+OneNET基础教程1】PacketTracer中MCU芯片编程教程(零基础入门)

PacketTracer软件内MCU芯片编程入门 零基础

ChloeMax_的博客 9451

【C++】STL之list模拟实现:关于链表容器的双向迭代器你知道多少?

本文深入剖析了STL中的list容器底层实现,并通过对比查阅stl_list.h头文件中源码的方式,重点模拟实现了list容器的双向迭代器⭐️,通过模板参数Ref和Ptr区分const与非const迭代器,并重载了++、--、*、->等运算符。

Miun123的博客 3323

Qt6安装保姆级教程:从国内镜像下载到配置避坑指南(附清华/中科大源)

本文提供详细的Qt6安装与配置指南,重点介绍如何利用清华大学、中国科学技术大学等国内镜像源解决下载速度慢的问题。内容涵盖在线安装器获取、组件选择策略、环境变量配置以及常见编译与部署问题的排查方法,旨在帮助国内开发者高效、顺畅地完成Qt6开发环境搭建。

weixin_29281915的博客 633

【C++】- 掌握STL List类:带你探索双向链表的魅力

C++STL中list的介绍与剖析

2301_81290732的博客 3607

SAP成本中心费用分割(KSS2)实战:从原理到月末调整全解析

本文详细解析了SAP成本中心费用分割(KSS2)的核心逻辑与实战操作,从成本要素分组配置到月末调整全流程。通过KSS2将初级成本要素按作业类型重新分类为次级成本要素,确保成本结算准确。文章还提供了常见错误处理方法和典型业务场景实战,帮助用户高效完成SAP成本结算。

weixin_29279101的博客 1254

【STM32-DSP库的使用】基于Keil5 + STM32CubeMX + CMSIS-DSP 手动添加、库添加方式

CMSIS-DSP库在工程中应用遇到的所有问题进行归纳终结

洋洋的博客 1万+

【C++进阶】深入STL之list:模拟实现深入理解List与迭代器

在STL中,list是一种双向链表,它支持在序列的任何位置进行快速插入和删除操作。与此同时,迭代器是STL中非常重要的一个概念,它使得我们能够以统一的方式遍历和访问STL容器中的元素。在深入了解STL的过程中,模拟实现list和迭代器无疑是一个极有价值的学习过程。

EterNity_TiMe_的计算机学习之路 3312

099、多摄融合与变焦系统:广角到长焦的无缝切换算法

本文分享了多摄融合与变焦系统开发中的实战经验和关键挑战。作者通过一个旗舰机项目案例,揭示了广角到长焦切换时画面抖动问题的根源——融合算法误将视差当作运动补偿,并提出"视差冻结窗口"的解决方案。文章总结了三大多摄融合难点:物理视差处理、亮度差异补偿和切换时机把控,详细阐述了分层架构设计、融合权重计算等核心技术。同时强调了产线校准的重要性,并给出五项实用建议:允许边缘鬼影保留、参数场景化配置、加强产线检测、完善调试接口,以及合理运用AI技术。全文展现了多摄融合技术在时间、空间和感知维度上的复杂平衡艺术。

sundong_81的博客 45

探索解析C++ STL中的 list:双向链表的高效实现与迭代器

链表作为一种基础数据结构,其非连续内存存储特性使其在频繁的插入和删除操作中具有O(1)的时间复杂度优势,这与数组类型容器形成了鲜明对比。然而,这种优势的背后也伴随着随机访问性能的牺牲和额外的内存开销

月亮永悬不落,剑指巅峰 2110

Flutter应用安卓逆向之hook分析研究---附dart版本对照表(必看篇)

搜索md5、rsa、aes等关键字分析要破解的加密算法,使用frida进行hook可疑函数的偏移地址。,双击进去找到ssl_crypto_x509_session_verify_cert_chain函数的偏移地址进行hook掉;Flutter应用是内置进行ssl证书校验的,无法通过常规方法抓到包,需要通过frida把它内置的ssl校验hook掉。重构完成后会生成新的apk,把它安装到模拟器或者真机中并打开运行,此时系统会在。关键字的函数进行hook(因为其返回的是字符串类型)中,拖进ide搜索字符串。

云霄IT的博客 8440

2024年数字IC秋招-华为机试-数字芯片-实习笔试题

笔试题目:40个选择题A.综合时不同文件列表内的宏定义仅在本列表生效B.宏定义会覆盖,不需要单独undefC.仿真时出现宏定义冲突以最后一次值为准D.宏定义最好在各文件中随用随定义A.4/9B.4/6C.01D.0/6。

qq_42135020的博客 2891

C++ [STL之list模拟实现]

list的底层与vector和string不同,实现也有所差别,特别是在迭代器的设计上,本节将为大家介绍list简单实现,并揭开list迭代器的底层!list模拟实现到这里就介绍了,本篇我们简单介绍了一下list的增删功能实现(与链表差别不大),重点介绍了list的迭代器思想,深入理解list的迭代器可以让我们对类和对象又有进一步的认识,如果我们可以理解list迭代器思想,那么list的其他函数对于我们来说都不成问题!

ARMCSKGT的博客 6743

AM芯片性能测试

目前车载FM&AM没有明确的国标,因此各家测试项目可能不同,最主要测试标准比较混乱,多是根据具体芯片的规格书以及与芯片厂商协商。信号发生器:R&S SMCV100B音频分析仪:R&S UPV AUDIO AYALYZER DC 250KHZ(AP的也可以)专用USB-I2C控制模块(不同芯片控制模块不同,例如SPI或UART的控制模块)卡农线(针对模拟声道输出,+接左/右声道,-接GND,地可接GND也可悬空)

qflook的博客 902

深入理解C++ stl::list 底层实现+模拟实现

stl:: list 是 双向带头循环链表,对数据的插入和删除具有高效率。

2301_81978155的博客 2995

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

STL:List的使用和模拟实现

而earse必然会失效,因为该迭代器对应的节点被删除了。如果我们想继续用的话,就得利用返回值去更新迭代器,返回值是被删除元素的下一个位置的迭代器。

weixin_51142926的博客 3859

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

C++ STL学习之【list的模拟实现】

STL中的list是一个双向带头循环链表,作为链表的终极形态,各项操作性能都很优秀,尤其是list中迭代器的设计更是让人拍案叫绝,如此优秀的容器究竟是如何实现的?本文将带你共同揭晓

北 海的博客 5051

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【STL】C++ list 模拟实现:从底层链表到容器封装

list 是 STL 中支持在任意位置高效(常数时间)插入、删除的双向迭代序列式容器,底层基于带头双向链表实现 —— 每个元素存于独立节点,通过指针连接前后元素;它和 forward_list 类似但后者是单链表、仅支持前向迭代;和 array、vector、deque 相比,list 的插入删除效率更优,但缺点是不支持随机访问(访问第 n 个元素需线性遍历),且每个节点的指针会占用额外空间(对存储小元素的大 list 影响较明显)。

2402_86350387的博客 2151

C++ : STL容器之list剖析

摘要: 本文深入剖析了STL中list容器的关键接口与实现原理。首先对比了push_back与emplace_back的差异,指出emplace_back在直接构造场景下的性能优势。其次分析了list单独实现sort函数的原因,源于其只支持双向迭代器而非随机迭代器。文章还详细讲解了splice、unique和merge等独特接口的用法。最后探讨了list的模拟实现,包括结点类设计、迭代器封装与模板参数优化技巧,特别解释了->运算符重载的特殊处理方式。通过代码示例与图示,全面展示了list容器的内部机制

2301_81454749的博客 2404

C++:list(带头双向链表)增删查改模拟实现

本文详细讲解了C++中list容器的实现原理,重点分析了list与vector/string的差异。文章指出list底层采用带头双向链表结构,不支持[]运算符和reserve扩容,但提供reverse、merge等特有接口。特别强调了list迭代器的特殊性,需要封装实现运算符重载来满足核心功能。通过节点构造、迭代器分类与实现、常用接口实现等模块,系统性地剖析了list的内部机制。最后还介绍了迭代器的->操作符重载和容器打印优化技巧。文章代码示例丰富,适合希望深入理解STL list实现的读者学习参考。

2301_78901562的博客 2691

C++ STL list 容器详解:使用与模拟实现

本文详细介绍了C++ STL中的list容器及其模拟实现。list是带头结点的双向循环链表,支持O(1)时间复杂度的插入删除操作,但不支持随机访问。文章从list的基本使用入手,讲解了构造、迭代器、容量操作等接口,重点分析了迭代器失效问题。在模拟实现部分,详细展示了节点类、迭代器类的设计,以及list容器类的核心实现,包括类型定义、迭代器转换机制、构造函数和插入操作等关键功能。通过模板参数实现了const和非const迭代器的统一,支持前后遍历和箭头操作符访问成员。

凤年徐的博客 2168

【C++】list的使用与模拟实现

本文介绍了C++ STL中list容器的使用与模拟实现。list作为双向链表容器,与vector的动态数组结构不同,具有独特的插入删除优势。文章首先通过文档介绍list的基本用法,包括构造、迭代器分类等,重点讲解了list迭代器失效问题。随后详细展示了list的模拟实现过程,包括节点封装、迭代器封装(通过模板参数实现iterator和const_iterator的复用)以及list主框架设计。通过对比vector,突出了list作为链表结构的特点,并提供了完整的代码示例,帮助读者深入理解list的底层实现原

suxi_na的博客 2472

C++ stl容器list的底层模拟实现

list的底层模拟实现

2301_79698419的博客 2246

【C++】STL——list深度剖析 及 模拟实现

深度剖析list的底层结构及其模拟实现,详解list的迭代器——对结点指针的封装、list的迭代器失效等问题

m0_70980326的博客 1912

C++ STL 详解:list 的使用、迭代器失效、模拟实现与 vector 对比

在 C++ STL 中,vector和list都是非常重要的序列式容器。如果说vector更像是“可以自动扩容的数组”,那么list更像是“一串用指针连接起来的节点”。很多同学刚学list的时候,会觉得它没有vector好用:不能下标访问,遍历起来还要用迭代器,看起来没有那么直观。但list适合频繁插入和删除插入时不会整体搬移元素删除某个节点时,通常只影响被删除节点对应的迭代器底层结构非常适合帮助我们理解链表和 STL 迭代器所以学习list。

Face_FeaR的博客 5848

c++STL中list介绍,模拟实现和list与vector对比

我们在前几期已经详细介绍了STL中string,vector容器的使用及模拟实现,不难发现,string和vector的结构很相似,他们在物理上都是连续的结构,想访问下一个位置的数据,只需要让相应容器的迭代器自增就可以实现,而我们今天要介绍的list,则无法用这种方法访问下一个位置数据,这也是我们在这一期需要解决的问题。1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

qq_58761784的博客 2093

C++_STL之list篇

std::list是C++标准模板库(STL)中的一个双向链表容器。与vector和deque不同,list不支持随机访问,但它在任何位置插入和删除元素都非常高效。

h_number_1的博客 2097
上一篇: 手把手模拟实现 C++ STL::vector——从底层看懂动态数组
下一篇: 模拟实现 C++ 栈和队列——从适配器模式看懂 STL 容器之美
jinyishu_
博客等级 码龄1年 53粉丝 82原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值