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

C++ STL list 容器详解:使用与模拟实现
在这里插入图片描述


文章目录

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

1. list 的介绍及使用

1.1 list 的介绍

list 是 C++ STL 中的一个重要容器,它是一个带头结点的双向循环链表。与 vector 不同,list 在任意位置插入和删除元素的时间复杂度都是 O(1),但不支持随机访问(即不能通过下标直接访问元素)。

1.2 list 的使用

list 提供了丰富的接口,下面我们介绍其中一些常见且重要的接口。

1.2.1 list 的构造
构造函数接口说明
list(size_type n, const value_type& val)构造包含 n 个值为 val 的元素的 list
list()构造空的 list
list(const list& x)拷贝构造函数
list(InputIterator first, InputIterator last)用区间 [first, last) 中的元素构造 list
1.2.2 list 迭代器的使用

迭代器可以暂时理解为指向 list 中某个节点的指针。

  • begin():返回指向第一个元素的迭代器
  • end():返回指向最后一个元素下一个位置的迭代器
  • rbegin():返回指向最后一个元素的反向迭代器(即 end() 位置)
  • rend():返回指向第一个元素前一个位置的反向迭代器(即 begin() 位置)

注意

  1. beginend 是正向迭代器,++ 向后移动。

rbeginrend 是反向迭代器,++ 向前移动。迭代器分类

在这里插入图片描述
1.2.3 list capacity
函数声明接口说明
empty()检测 list 是否为空
size()返回 list 中有效节点的个数
1.2.4 list element access
函数声明接口说明
front()返回第一个节点中值的引用
back()返回最后一个节点中值的引用
1.2.5 list modifiers
函数声明接口说明
push_front在 list 首元素前插入值为 val 的元素
pop_front删除 list 中第一个元素
push_back在 list 尾部插入值为 val 的元素
pop_back删除 list 中最后一个元素
insert在 position 位置插入值为 val 的元素
erase删除 position 位置的元素
swap交换两个 list 中的元素
clear清空 list 中的有效元素
1.2.6 list 的迭代器失效

由于 list 的底层是双向循环链表,插入操作不会导致迭代器失效。只有在删除元素时,指向被删除节点的迭代器才会失效,其他迭代器不受影响。

错误的删除写法:

while (it != l.end()) { l.erase(it); // it 失效 ++it; // 错误,it 已无效 } 

2. list 的模拟实现

2.1 基本结构

2.1.1 节点类 (list_node)
template<class T> class list_node { public: T _data; list_node<T>* _next; list_node<T>* _prev; list_node(const T& data = T()) :_data(data) ,_next(nullptr) ,_prev(nullptr) { } }; 

这是 list 的基础节点结构,采用双向链表设计,每个节点包含:

  • _data: 存储实际数据
  • _next: 指向下一个节点
  • _prev: 指向上一个节点
2.1.2 迭代器类 (list_iterator)
template<class T, class Ref, class Ptr> struct list_iterator { typedef list_node<T> Node; typedef list_iterator<T, Ref, Ptr> Self; Node* _node; list_iterator(Node* node) :_node(node) { } // 解引用操作符 Ref operator*() { return _node->_data; } // 箭头操作符 Ptr operator->() { return &_node->_data; } // 前置++ Self& operator++() { _node = _node->_next; return *this; } // 后置++ Self operator++(int) { Self tmp(*this); _node = _node->_next; return tmp; } // 前置-- Self& operator--() { _node = _node->_prev; return *this; } // 后置-- Self operator--(int) { Self tmp(*this); _node = _node->_prev; return tmp; } // 不等于操作符 bool operator!=(const Self& s) { return _node != s._node; } }; 

关键特性

  • 通过模板参数 RefPtr 实现 const 和非 const 迭代器的统一
  • 支持前后遍历操作
  • 支持箭头操作符访问成员

2.2 list 容器类

2.2.1 类型定义和迭代器
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 begin() { return _head->_next; // 隐式类型转换 } iterator end() { return _head; } const_iterator begin() const { return _head->_next; } const_iterator end() const { return _head; } 

迭代器转换机制

  • Node* 可以隐式转换为 iterator,因为 list_iterator 有单参数构造函数
  • begin() 返回第一个有效元素位置
  • end() 返回头节点位置,符合 STL 左闭右开原则
2.2.2 构造函数和初始化
void empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; _size = 0; } list() { empty_init(); } // 拷贝构造 list(list<T>& lt) { empty_init(); for (auto& e : lt) { push_back(e); } } // 初始化列表构造 list(initializer_list<int> il) { empty_init(); for (auto& e : il) { push_back(e); } } 
2.2.3 插入操作
iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(x); prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode; ++_size; return newnode; // 返回新插入节点的迭代器 } void push_back(const T& x) { insert(end(), x); // 复用 insert } void push_front(const T& x) { insert(begin(), x); // 复用 insert } 
2.2.4 删除操作
iterator erase(iterator pos) { assert(pos != end()); // 不能删除头节点 Node* prev = pos._node->_prev; Node* next = pos._node->_next; prev->_next = next; next->_prev = prev; delete pos._node; --_size; return next; // 返回下一个有效位置的迭代器 } void pop_back() { erase(--end()); } void pop_front() { erase(begin()); } 

迭代器失效处理

  • erase 返回下一个有效位置的迭代器
  • 正确处理删除操作后的迭代器续接
2.2.5 容量操作
size_t size() const { return _size; } bool empty() const { return _size == 0; } void clear() { auto it = begin(); while (it != end()) { it = erase(it); // erase 返回下一个地址 } } 
2.2.6 赋值操作
list<T>& operator=(list<T> lt) { swap(lt); return *this; } void swap(list<T>& lt) { std::swap(_head, lt._head); std::swap(_size, lt._size); } 

2.3 迭代器模板技巧

// 使用模板参数实现 const 和非 const 迭代器的统一 typedef list_iterator<T, T&, T*> iterator; typedef list_iterator<T, const T&, const T*> const_iterator; 

这种设计避免了重复代码,只需要一个模板类就能同时支持普通迭代器和 const 迭代器。

2.4 测试示例

// 测试基本功能 void test_list01() { list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); // 遍历输出 list<int>::iterator it = lt.begin(); while (it != lt.end()) { cout << *it << " "; ++it; } cout << endl; } // 测试迭代器失效 void test_list02() { list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); // insert 不会导致迭代器失效 list<int>::iterator it = lt.begin(); lt.insert(it, 10); *it += 100; // 仍有效 // erase 会导致当前迭代器失效 it = lt.begin(); while (it != lt.end()) { if (*it % 2 == 0) { it = lt.erase(it); // 必须接收返回值 } else { ++it; } } } 
源代码
#pragma once #include<assert.h> namespace bit { template<class T> class list_node { public: T _data; list_node<T>* _next; list_node<T>* _prev; list_node(const T& data = T()) :_data(data) ,_next(nullptr) ,_prev(nullptr) { } }; template<class T,class Ref,class Ptr> struct list_iterator { typedef list_node<T> Node; typedef list_iterator<T,Ref,Ptr> Self; Node* _node; list_iterator(Node* node) :_node(node) { } Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } Self& operator++() { _node = _node->_next; return *this; } Self operator++(int) { Self tmp(*this); _node = _node->_next; return tmp; } Self operator--(int) { Self tmp(*this); _node = _node->_prev; return tmp; } Self& operator--() { _node = _node->_prev; return *this; } bool operator!=(const Self& s) { return _node != s._node; } }; /*template<class T> struct list_const_iterator { typedef list_node<T> Node; typedef list_const_iterator<T> Self; Node* _node; list_const_iterator(Node* node) :_node(node) { } const T& operator*() { return _node->_data; } const T* operator->() { return &_node->_data; } Self& operator++() { _node = _node->_next; return *this; } Self operator++(int) { Self tmp(*this); _node = _node->_next; return tmp; } Self operator--(int) { Self tmp(*this); _node = _node->_prev; return tmp; } Self& operator--() { _node = _node->_prev; return *this; } bool operator!=(const Self& s) { return _node != s._node; } };*/ template<class T> class list { typedef list_node<T> Node; public: /*typedef list_iterator<T> iterator; typedef list_const_iterator<T> const_iterator;*/ typedef list_iterator<T,T&,T*> iterator; typedef list_iterator<T,const T&,const T*> const_iterator; iterator begin() { /*iterator it(_head->_next); return it;*/ //return iterator(_head->_next); return _head->_next;//节点的指针可以隐式类型转换成iterator的 //单参数构造函数可以隐式类型转换 } iterator end() { return _head; } const_iterator begin() const { /*iterator it(_head->_next); return it;*/ //return iterator(_head->_next); return _head->_next;//节点的指针可以隐式类型转换成iterator的 //单参数构造函数可以隐式类型转换 } const_iterator end() const { return _head; } void empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; _size = 0; } list() { empty_init(); } ~list() { clear(); delete _head; _head = nullptr; } void clear() { auto it = begin(); while (it != end()) { it = erase(it);//erase返回下一个地址 } } list<T>& operator=(list<T> lt) { swap(lt); return *this; } void swap(list<T>& lt) { std::swap(_head, lt._head); std::swap(_size, lt._size); } //lt2(lt1) 拷贝构造 list(list<T>& lt) { empty_init(); for (auto& e : lt) { push_back(e); } } list(initializer_list<int> il) { empty_init(); for (auto& e : il) { push_back(e); } } void push_back(const T&x) { /*Node* newnode = new Node(x); Node* tail = _head->_prev; tail->_next = newnode; newnode->_prev = tail; newnode->_next = _head; _head->_prev = newnode; ++_size;*/ insert(end(), x); } iterator insert(iterator pos,const T& x)//c++中插入默认在之前插 { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(x); prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode; ++_size; return newnode; } void push_front(const T& x) { insert(begin(), x); } void pop_back() { erase(--end()); } void pop_front() { erase(begin()); } iterator erase(iterator pos) { assert(pos != end()); Node* prev = pos._node->_prev; Node* next = pos._node->_next; prev->_next = next; next->_prev = prev; delete pos._node; return next;//走隐式类型转换 编译器发现next是Node*类型的,就会走构造函数iteratpor(Node* node) } size_t size() const { return _size; } bool empty() const { return _size == 0; } private: Node* _head; size_t _size; }; struct AA { int _a1; int _a2; }; template<class Container> void print_container(const Container& con) { //const iterator 迭代器本身不能修改 //const_iterator 指向内容不能修改 迭代器要修改(比如++) //list<int>::const_iterator it = con.begin(); //typename Container::const_iterator it = con.begin(); auto it = con.begin(); while (it != con.end()) { cout << *it << " "; ++it; } cout << endl; /* for (auto e : con) { cout << e << " "; } cout << endl;*/ } void test_list01() { list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); list<int>::iterator it = lt.begin(); while (it != lt.end()) { cout << *it << " "; ++it; } cout << endl; list<AA> lta; lta.push_back(AA()); lta.push_back(AA()); lta.push_back(AA()); lta.push_back(AA()); list<AA>::iterator ita = lta.begin(); while (ita != lta.end()) { //cout<< (*ita)._a1 << " "<<(*ita)._a2<<endl; //特殊处理 本来应该两个->才合理,为了可读性,省略一个 cout << ita->_a1 << ":" << ita->_a2 << endl; //cout << ita.operator->()->_a1 << ":" << ita->_a2 << endl; ++ita; } cout << endl; print_container(lt); } void test_list02() { list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); list<int>::iterator it = lt.begin(); lt.insert(it, 10); *it += 100; print_container(lt); //insert以后迭代器不失效 //删除偶数 it = lt.begin(); while (it != lt.end()) { if (*it % 2 == 0) { it=lt.erase(it);//erase以后 迭代器失效 } else { ++it; } } print_container(lt); } void test_list03() { list<int> lt1; lt1.push_back(1); lt1.push_back(2); lt1.push_back(3); lt1.push_back(4); list<int> lt2(lt1); print_container(lt1); print_container(lt2); list<int> lt3; lt3.push_back(10); lt3.push_back(20); lt3.push_back(30); lt3.push_back(40); lt1 = lt3; print_container(lt1); print_container(lt3); } void func(const list<int>& lt) { print_container(lt); } void test_list04() { //直接构造 list<int> lt0 ( { 1,3,5 }); //隐式类型转换 list<int> lt1= {1, 2, 3, 4, 5}; print_container(lt1); auto il = { 1,2,3,4 }; cout << typeid(il).name() << endl; func(lt1); func({ 1,3,6,7 });//隐式类型转换 } }; 

2.5 设计要点总结

  1. 双向循环链表:头节点连接首尾,简化边界处理
  2. 迭代器封装:隐藏底层指针,提供统一接口
  3. 模板复用:通过模板参数减少代码重复
  4. 异常安全:正确管理资源,避免内存泄漏
  5. STL 兼容:遵循 STL 接口规范

这个实现展示了 list 容器的核心机制,包括节点管理、迭代器设计、插入删除操作等关键技术点。

3. list 与 vector 的对比

对比维度vectorlist
底层结构动态顺序表,连续空间带头结点的双向循环链表
随机访问支持 O(1)不支持,访问元素 O(N)
插入删除任意位置效率低 O(N),可能增容任意位置效率高 O(1)
空间利用率连续空间,内存碎片少,缓存友好节点动态开辟,易产生内存碎片
迭代器类型原生态指针对节点指针进行封装
迭代器失效插入删除可能导致全部或部分失效删除时仅当前迭代器失效

Read more

华为OD机试双机位C卷:挑选宝石 (C/C++/Python/JAVA/JS/GO)

华为OD机试双机位C卷:挑选宝石 (C/C++/Python/JAVA/JS/GO)

挑选宝石 2025华为OD机试双机位C卷 - 华为OD上机考试双机位C卷 100分题型 华为OD机试双机位C卷真题目录点击查看: 华为OD机试双机位C卷真题题库目录|机考题库 + 算法考点详解 题目描述 游乐园有一款互动游戏,游戏开始时会提供n个宝石,每个宝石都一个属性值a1,a2,…an.玩家在游戏前可以挑选x颗宝石,将这些宝石的属性值相乘组成玩家的属性值。游戏玩家需要y点属性值,请帮助游戏玩家计算有多少种计算方式。 输入描述 第一行:三个整数n,x,y * 第一个整数n(0 < n <20)表示宝石总数量。 * 第二个整数x(0<x <=n),表示可以选择宝石个数 * 第三个整数y,表示通过游戏需要的属性值 第二行:n个整数,a1,a2,…an(-100 < ai < 100)

By Ne0inhk
【C++】map和set的介绍以及用法

【C++】map和set的介绍以及用法

个人主页 文章目录 * ⭐一、系列式容器和关联式容器 * 🚀二、set的使用 * 1. set类的介绍 * 2. set的构造 * 3. set的迭代器 * 4. set的常用函数 * 🎡三、multiset * 🎄四、map类的使用 * 1. map类的介绍 * 2. map的构造 * 3. map的迭代器 * 4. map的operator[] * 5. map的常用函数 * 🏠五、multimap * 🚆六、总结 ⭐一、系列式容器和关联式容器 在之前学习过程中,我们也接触了STL中的部分容器,如:vector、string、list、deque等,这些都属于序列式容器,因为其底层是线性结构,里面存储的为数据本身。 关联式容器也是用来存储数据的,但不同的是,关联式容器的底层通常是非线性结构的,也就意味着两个位置具有紧密的关联关系,如果进行交换,它们的存储结构就被破坏了。

By Ne0inhk
字符串处理总崩?那是你没解锁 string 的 “防坑 Buff”,C++er 必看

字符串处理总崩?那是你没解锁 string 的 “防坑 Buff”,C++er 必看

✨ 孤廖:个人主页 🎯 个人专栏:《C++:从代码到机器》 🎯 个人专栏:《Linux系统探幽:从入门到内核》 🎯 个人专栏:《算法磨剑:用C++思考的艺术》 折而不挠,中不为下 文章目录 * 正文 * 1. 为什么学习string类? * 1.1 C语言中的字符串 * 2. 标准库中的string类 * 2.1 string类(了解) * 2.2 auto和范围for * 2.3 string类的常用接口说明(注意下面我只讲解最常用的接口) * 1. string类对象的常见构造 * 2. string类对象的容量操作 * 3. string类对象的访问及遍历操作 * 4. string类对象的修改操作 * 5. string类非成员函数 * 6. vs和g++下string结构的说明 * 7. string 函数接口的应用

By Ne0inhk

C++逆向工程必备:用c++filt一键解析GCC编译后的神秘函数名(附实战案例)

C++逆向工程实战:用c++filt破解GCC函数名混淆的终极指南 1. 从崩溃日志到可读符号:逆向工程师的必备武器 当你在凌晨三点收到生产环境崩溃警报时,面对日志中_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc这样的符号,是否感到绝望?这就是C++的name mangling机制在"作祟"——编译器为了支持函数重载等特性,将函数名和参数类型编码成一串晦涩的符号。但别担心,c++filt正是为此而生的瑞士军刀。 典型崩溃分析场景中的痛点: * 核心转储文件中难以辨认的函数调用栈 * 动态链接库中无法直观理解的导出符号 * 跨语言调用时的链接错误 * 第三方库调试时的符号匹配问题 # 实战示例:解析崩溃堆栈 $ cat crash.log | grep '#' | awk '{print $4}' | c++filt std::__cxx11::basic_string<

By Ne0inhk