当前位置: 首页 > news >正文

从零开始的C++学习生活 8:list的入门使用

个人主页:Yupureki-CSDN博客

C++专栏:C++_Yupureki的博客-CSDN博客

目录

前言

1. list简介

1.1 什么是list?

1.2 list的底层结构

2. list的基本使用

2.1 构造list

2.2 迭代器使用

2.3 容量操作

2.4 元素访问

2.5 修改操作

2.6 list特有操作

3. 迭代器失效问题

3.1 安全的操作(不会导致迭代器失效)

3.2 危险的操作(会导致当前迭代器失效)

3.3 批量删除的正确方式

4. list的模拟实现

4.1 节点结构

4.2 list基础框架

4.3 反向迭代器实现

5. list与vector的对比

选择指南:

6. 性能优化建议


上一篇:从零开始的C++学习生活 7:vector的入门使用-CSDN博客

前言

在C++标准模板库(STL)中,list是一个基于双向链表实现的序列容器,与vector的连续内存布局形成鲜明对比。虽然vector因其随机访问特性而广受欢迎,但在某些场景下,list的插入删除效率和无序存储特性使其成为不可替代的选择。

list中每个元素独立存储,通过指针相互连接。这种结构使得在任意位置的插入和删除操作都能在常数时间内完成,但代价是失去了随机访问的能力。

本文将深入探讨list的各个方面,从基本使用到底层实现,帮助你理解何时以及如何正确使用这个重要的容器。

1. list简介

1.1 什么是list?

list是C++标准库中的一个序列容器,它基于带头节点的双向循环链表实现。主要特点包括:

  • 双向遍历:支持从前向后和从后向前的遍历

  • 高效插入删除:在任意位置插入删除元素的时间复杂度为O(1)

  • 非连续存储:元素在内存中分散存储,无扩容开销

  • 迭代器稳定性:插入操作不会使迭代器失效

1.2 list的底层结构

简易的list由3个类构成:list本体(集成功能函数,头节点和节点个数),listnode(单个list的节点),iterator(list专属的迭代器)

template<class T>
class List {//list本体的结构typedef ListNode<T> Node;//list节点typedef List_Iterator<T> iterator;//迭代器
public://函数实现......
private:Node* head;//链表的哨兵位size_t size;//链表的有效结点个数(不包含哨兵位)
};

// list的节点结构示意
struct ListNode {T data;           // 存储的数据ListNode* prev;   // 指向前驱节点ListNode* next;   // 指向后继节点
};

//list的迭代器
template<class T>
struct List_Iterator {typedef ListNode<T> Node;typedef List_Iterator iterator;Node* node;......
};

list的整体结构是一个带头节点的双向循环链表

  • 头节点的prev指向尾节点

  • 尾节点的next指向头节点

  • 头节点不存储实际数据

为什么要集成list的迭代器?

由于物理空间的特性,list不像string和vector是线性的,普通的++,--,*和其他操作符无法直接用在节点的指针上,而为了保持统一,例如我们当然也得实现list的迭代器++指向下一个节点,*表示该节点的值

2. list的基本使用

2.1 构造list

list和srting,vector的构造极为相似

#include <list>
using namespace std;// 1. 默认构造 - 空list
list<int> l1;// 2. 构造包含n个val的list
list<int> l2(5, 10);  // {10, 10, 10, 10, 10}// 3. 拷贝构造
list<int> l3(l2);     // 与l2相同// 4. 使用迭代器范围构造
int arr[] = {1, 2, 3, 4, 5};
list<int> l4(arr, arr + 5);  // {1, 2, 3, 4, 5}// 5. 初始化列表构造 (C++11)
list<int> l5 = {1, 2, 3, 4, 5};

2.2 迭代器使用

集成后的迭代器可像string和vector那样使用

list<int> lst = {1, 2, 3, 4, 5};// 正向迭代器
cout << "正向遍历: ";
for (auto it = lst.begin(); it != lst.end(); ++it) {cout << *it << " ";  // 1 2 3 4 5
}// 反向迭代器
cout << "\n反向遍历: ";
for (auto rit = lst.rbegin(); rit != lst.rend(); ++rit) {cout << *rit << " ";  // 5 4 3 2 1
}// 范围for循环
cout << "\n范围for: ";
for (auto& elem : lst) {cout << elem << " ";
}

注意:list的迭代器是双向迭代器,不支持随机访问,不能进行it + n操作。

2.3 容量操作

list没有capacity容量的概念,只有size有效节点个数的概念

list<int> lst = {1, 2, 3};cout << lst.size();        // 元素个数: 3
cout << lst.empty();       // 是否为空: false
cout << lst.max_size();    // 理论最大容量// list没有capacity概念,因为不需要预分配空间

2.4 元素访问

list和string,vector不同的是,list不是线性的,不能使用[]下标来访问数据(实际上也可以实现,但不觉的别扭?链表用[]来访问?)

list<int> lst = {1, 2, 3, 4, 5};// 访问首尾元素
cout << lst.front();      // 1
cout << lst.back();       // 5// 注意:list不支持下标访问!
// cout << lst[0];       // 错误!编译不通过

2.5 修改操作

list的增删查改与string,vector也基本一致

list<int> lst = {1, 2, 3};// 头部操作
lst.push_front(0);        // {0, 1, 2, 3}
lst.pop_front();          // {1, 2, 3}// 尾部操作
lst.push_back(4);         // {1, 2, 3, 4}
lst.pop_back();           // {1, 2, 3}// 插入操作
auto it = lst.begin();
++it;                     // 指向第二个元素
lst.insert(it, 10);       // {1, 10, 2, 3}// 删除操作
it = lst.begin();
++it;                     // 指向10
lst.erase(it);            // {1, 2, 3}// 清空
lst.clear();              // {}// 交换
list<int> lst2 = {4, 5, 6};
lst.swap(lst2);           // 交换两个list的内容

2.6 list特有操作

list不受连续空间的限制,相对自由且高效

list<int> lst1 = {1, 3, 5};
list<int> lst2 = {2, 4, 6};// 合并两个有序链表(lst2会被清空)
lst1.merge(lst2);         // lst1: {1, 2, 3, 4, 5, 6}, lst2: {}// 排序
list<int> lst3 = {3, 1, 4, 2};
lst3.sort();              // {1, 2, 3, 4}// 去重(需要先排序)
list<int> lst4 = {1, 2, 2, 3, 3, 3};
lst4.unique();            // {1, 2, 3}// 反转
list<int> lst5 = {1, 2, 3};
lst5.reverse();           // {3, 2, 1}// 拼接:将另一个list的部分元素移动到当前list
list<int> lst6 = {1, 2, 3};
list<int> lst7 = {4, 5, 6};
auto pos = lst6.begin();
++pos;                    // 指向2
lst6.splice(pos, lst7);   // lst6: {1, 4, 5, 6, 2, 3}, lst7: {}

3. 迭代器失效问题

vector不同,list的迭代器失效规则更加简单:

3.1 安全的操作(不会导致迭代器失效)

  • 插入操作push_back()push_front()insert()

  • 其他操作merge()sort()reverse()

3.2 危险的操作(会导致当前迭代器失效)

  • 删除操作pop_back()pop_front()erase()

// 错误示例
list<int> lst = {1, 2, 3, 4, 5};
auto it = lst.begin();
++it;                    // 指向2lst.erase(it);           // 删除2
// cout << *it;         // 错误!it已失效// 正确做法
it = lst.begin();
++it;
it = lst.erase(it);      // erase返回下一个有效迭代器
cout << *it;             // 现在it指向3

节点删除后直接使it成为野指针,因此会导致迭代器失效

3.3 批量删除的正确方式

list<int> lst = {1, 2, 3, 4, 5, 6};// 删除所有偶数 - 正确方式
auto it = lst.begin();
while (it != lst.end()) {if (*it % 2 == 0) {it = lst.erase(it);  // 重要:接收返回值} else {++it;}
}// 或者使用remove_if(更简洁)
lst.remove_if([](int x) { return x % 2 == 0; });

4. list的模拟实现

4.1 节点结构

template<typename T>
struct ListNode {T _data;ListNode<T>* _prev;ListNode<T>* _next;ListNode(const T& val = T()): _data(val), _prev(nullptr), _next(nullptr){}
};

4.2 list基础框架

template<typename T>
class list {
private:ListNode<T>* _head;  // 头节点(不存储实际数据)public:// 迭代器类(简化版)class iterator {private:ListNode<T>* _node;public:iterator(ListNode<T>* node = nullptr) : _node(node) {}T& operator*() { return _node->_data; }T* operator->() { return &(_node->_data); }iterator& operator++() {_node = _node->_next;return *this;}iterator operator++(int) {iterator temp = *this;_node = _node->_next;return temp;}iterator& operator--() {_node = _node->_prev;return *this;}iterator operator--(int) {iterator temp = *this;_node = _node->_prev;return temp;}bool operator!=(const iterator& it) const {return _node != it._node;}bool operator==(const iterator& it) const {return _node == it._node;}};public:// 构造函数list() {_head = new ListNode<T>;_head->_prev = _head;_head->_next = _head;  // 循环链表}// 析构函数~list() {clear();delete _head;_head = nullptr;}// 迭代器相关iterator begin() { return iterator(_head->_next); }iterator end() { return iterator(_head); }// 容量相关bool empty() const { return _head->_next == _head; }size_t size() const {size_t count = 0;ListNode<T>* cur = _head->_next;while (cur != _head) {++count;cur = cur->_next;}return count;}// 元素访问T& front() { return _head->_next->_data; }T& back() { return _head->_prev->_data; }// 修改操作void push_back(const T& val) {insert(end(), val);}void push_front(const T& val) {insert(begin(), val);}void pop_back() {erase(iterator(_head->_prev));}void pop_front() {erase(begin());}// 插入操作iterator insert(iterator pos, const T& val) {ListNode<T>* newNode = new ListNode<T>(val);ListNode<T>* cur = pos._node;ListNode<T>* prev = cur->_prev;// 调整指针newNode->_prev = prev;newNode->_next = cur;prev->_next = newNode;cur->_prev = newNode;return iterator(newNode);}// 删除操作iterator erase(iterator pos) {ListNode<T>* cur = pos._node;ListNode<T>* prev = cur->_prev;ListNode<T>* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}void clear() {ListNode<T>* cur = _head->_next;while (cur != _head) {ListNode<T>* next = cur->_next;delete cur;cur = next;}_head->_next = _head;_head->_prev = _head;}
};

4.3 反向迭代器实现

template<class Iterator>
class ReverseListIterator {
private:Iterator _it;public:typedef ReverseListIterator<Iterator> Self;ReverseListIterator(Iterator it) : _it(it) {}// 解引用:需要向前移动一位,因为反向迭代器指向实际的前一个位置typename Iterator::reference operator*() {Iterator temp = _it;--temp;return *temp;}typename Iterator::pointer operator->() {return &(operator*());}Self& operator++() {--_it;return *this;}Self operator++(int) {Self temp = *this;--_it;return temp;}Self& operator--() {++_it;return *this;}Self operator--(int) {Self temp = *this;++_it;return temp;}bool operator!=(const Self& rit) const {return _it != rit._it;}bool operator==(const Self& rit) const {return _it == rit._it;}
};

5. list与vector的对比

特性vectorlist
底层结构动态数组,连续内存双向链表,非连续内存
随机访问O(1),支持下标访问O(n),不支持下标访问
插入删除尾部O(1),中间O(n)任意位置O(1)
内存使用内存连续,利用率高每个元素额外存储指针
缓存友好是,空间局部性好否,空间局部性差
迭代器失效插入删除可能导致全部失效只有被删除元素迭代器失效
适用场景随机访问频繁,尾部操作多任意位置插入删除频繁

选择指南:

使用vector的情况:

  • 需要频繁随机访问元素

  • 主要在尾部进行插入删除操作

  • 内存效率要求高

  • 元素数量相对稳定

使用list的情况:

  • 需要在任意位置频繁插入删除

  • 不需要随机访问,主要是顺序访问

  • 对迭代器稳定性要求高

  • 元素大小较大,移动成本高

6. 性能优化建议

  1. 批量操作:使用范围插入而不是多次单元素插入

  2. 算法选择:利用list特有的sort、merge等算法

  3. 迭代器缓存:对于频繁访问的位置可以缓存迭代器

  4. 避免不必要的拷贝:使用emplace操作

http://www.dtcms.com/a/481915.html

相关文章:

  • 平面设计师网站宁波制作网站哪个好
  • 简单的网站制作wordpress添加广告插件吗
  • 应用软件程序页面类型与核心元素解析
  • 从基金入门到长期主义:我如何建立自己的投资认知体系
  • 微算法科技MLGO推出隐私感知联合DNN模型部署和分区优化技术,开启协作边缘推理新时代
  • (20)100天python从入门到拿捏《JSON 数据解析》
  • 战略携手!沃飞长空与西门子数字化工业软件签约
  • 从零开始的C++学习生活 7:vector的入门使用
  • 队列数据结构详解:从原理到实现
  • JavaScript构造函数详解
  • 学习无刷直流电机驱动硬件
  • 案例应聘网络营销做网站推广网络营销的种类有哪些
  • 西安网站建设网莱芜网站推广
  • 从RNN到LSTM:深入理解循环神经网络与长短期记忆网络
  • AIDL 接口的定义与生成,使用
  • 深度解析过拟合与欠拟合:从诊断到正则化策略的全面应对
  • python - 装箱项目/3D Bin Packing problem
  • 【自动驾驶】自动驾驶概述 ⑨ ( 自动驾驶软件系统概述 | 预测系统 | 决策规划 | 控制系统 )
  • STM32F103C8T6 GY-906 MLX90614ESF 无线测温传感器模块的使用方法和代码驱动
  • 常规的紫外工业镜头有哪些?能做什么?
  • 香洲网站建设品牌形象设计方案
  • 突破AR视觉交互边界:Unity赋能Rokid AR眼镜实现高精度图像识别与实时跟踪
  • zabbix安装
  • 【VTK实战】vtkDepthImageToPointCloud:从2D深度图到3D点云,手把手教你落地3D扫描/AR场景
  • 【Git版本控制】Git初识、安装、仓库初始化与仓库配置(含git init、git config与配置无法取消问题)
  • 浅谈目前主流的LLM软件技术栈:Kubernetes + Ray + PyTorch + vLLM 的协同架构
  • 北京企业建站团队30岁转行做网站编辑
  • Kubernetes云平台管理实战:滚动升级与秒级回滚
  • 苹果智能眼镜研发进度更新,三星/微美全息提速推进AI+AR产业化进程
  • vue3+ts+uniapp微信小程序xr-frame实现AR追踪器(ARTracker)