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

深入理解C++ 中的list容器

目录

一、引言

二、list的基本介绍

2.1 底层结构

2.2 特性

三、list的使用

3.1 构造函数

3.2 迭代器的使用

3.3 容量相关操作

3.4 元素访问相关操作

3.5 修改器操作

3.6 迭代器失效问题

四、list的模拟实现

4.1 节点定义

4.2 迭代器实现

4.3 list类实现

五、list与vector的对比

表格

六、总结


一、引言

在C++ 的标准模板库(STL)中, list  是一种非常重要的序列式容器。它以其独特的底层结构和高效的操作接口,在许多场景下发挥着重要作用。今天,我们就来深入探讨一下  list  容器的方方面面。

二、list的基本介绍

2.1 底层结构

 list  的底层是双向链表结构每个元素存储在独立的节点中,节点通过指针指向前一个元素和后一个元素 ,并且是带头结点的双向循环链表。这意味着在链表中,头结点可以作为一个哨兵节点,方便进行插入和删除操作,同时循环的特性使得从链表的尾部可以无缝连接到头部。

2.2 特性

- 插入和删除高效:由于其链表结构,在任意位置进行插入和删除操作的时间复杂度为 O(1) ,不需要像  vector  那样移动大量元素。

- 双向迭代:支持前后双向迭代,提供了正向迭代器和反向迭代器,方便从不同方向遍历容器。

- 不支持随机访问:与  vector  不同, list  不支持任意位置的随机访问。如果要访问第  n  个元素,必须从已知位置(如头部或尾部)开始迭代,时间复杂度为 O(n) 。

三、list的使用

3.1 构造函数

 list  提供了多种构造方式:

cpp#include <list>#include <iostream>int main() {// 构造包含n个值为val的元素的liststd::list<int> list1(5, 10); // 包含5个值为10的元素// 构造空的liststd::list<int> list2; // 拷贝构造std::list<int> list3(list1); // 用[first, last)区间中的元素构造liststd::list<int> list4(list1.begin(), list1.end()); for (const auto& num : list1) {std::cout << num << " ";}std::cout << std::endl;return 0;}

3.2 迭代器的使用

迭代器是访问  list  元素的关键工具。 list  提供正向迭代器  begin()  和  end()  ,以及反向迭代器  rbegin()  和  rend()  。

cpp#include <list>#include <iostream>int main() {std::list<int> myList = {1, 2, 3, 4, 5};// 正向遍历for (auto it = myList.begin(); it != myList.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 反向遍历for (auto rit = myList.rbegin(); rit != myList.rend(); ++rit) {std::cout << *rit << " ";}std::cout << std::endl;return 0;}

需要注意的是, begin()  和  end()  是正向迭代器,对其执行  ++  操作,迭代器向后移动; rbegin()  和  rend()  是反向迭代器,对其执行  ++  操作,迭代器向前移动。

3.3 容量相关操作

-  empty() :检测  list  是否为空,返回  true  或  false  。

-  size() :返回  list  中有效节点的个数。

cpp#include <list>#include <iostream>int main() {std::list<int> myList;std::cout << "Is myList empty? " << (myList.empty()? "Yes" : "No") << std::endl;myList.push_back(1);std::cout << "Size of myList: " << myList.size() << std::endl;return 0;}

3.4 元素访问相关操作

-  front() :返回  list  的第一个节点中值的引用。

-  back() :返回  list  的最后一个节点中值的引用。

cpp#include <list>#include <iostream>int main() {std::list<int> myList = {10, 20, 30};std::cout << "First element: " << myList.front() << std::endl;std::cout << "Last element: " << myList.back() << std::endl;return 0;}

3.5 修改器操作

-  push_front(val) :在  list  首元素前插入值为  val  的元素。

-  pop_front() :删除  list  中第一个元素。

-  push_back(val) :在  list  尾部插入值为  val  的元素。

-  pop_back() :删除  list  中最后一个元素。

-  insert(position, val) :在  list  的  position  位置中插入值为  val  的元素。

-  erase(position) :删除  list  中  position  位置的元素。

-  swap(other_list) :交换两个  list  中的元素。

-  clear() :清空  list  中的有效元素。

cpp#include <list>#include <iostream>int main() {std::list<int> myList;myList.push_back(1);myList.push_back(2);myList.push_front(0);myList.pop_back();myList.pop_front();auto it = myList.begin();myList.insert(it, 10);myList.erase(it);std::list<int> anotherList = {3, 4};myList.swap(anotherList);myList.clear();return 0;}

3.6 迭代器失效问题

由于  list  的底层是双向链表,在插入元素时,不会导致迭代器失效。但在删除元素时,指向被删除节点的迭代器会失效,其他迭代器不受影响。例如:

cpp#include <list>#include <iostream>void TestListIterator() {int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};std::list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()) {l.erase(it++); // 正确做法,erase后it已经指向下一个有效元素}}

如果写成  l.erase(it); ++it;  ,在  erase  操作后  it  已经失效,再对其进行  ++  操作就会出错。

四、list的模拟实现

4.1 节点定义

cpptemplate <typename T>struct ListNode {T data;ListNode* prev;ListNode* next;ListNode(const T& val) : data(val), prev(nullptr), next(nullptr){}};

4.2 迭代器实现

cpptemplate <typename T, typename Ref, typename Ptr>struct ListIterator {using iterator_category = std::bidirectional_iterator_tag;using value_type = T;using reference = Ref;using pointer = Ptr;using node = ListNode<T>;node* ptr;ListIterator(node* p) : ptr(p) {}reference operator*() { return ptr->data; }pointer operator->() { return &(ptr->data); }ListIterator& operator++() {ptr = ptr->next;return *this;}ListIterator operator++(int) {ListIterator temp = *this;ptr = ptr->next;return temp;}ListIterator& operator--() {ptr = ptr->prev;return *this;}ListIterator operator--(int) {ListIterator temp = *this;ptr = ptr->prev;return temp;}bool operator!=(const ListIterator& other) const { return ptr != other.ptr; }bool operator==(const ListIterator& other) const { return ptr == other.ptr; }};

4.3 list类实现

cpptemplate <typename T>class List {private:ListNode<T>* head;public:using iterator = ListIterator<T, T&, T*>;using const_iterator = ListIterator<T, const T&, const T*>;List() {head = new ListNode<T>(T());head->prev = head;head->next = head;}~List() {clear();delete head;}iterator begin() { return iterator(head->next); }iterator end() { return iterator(head); }const_iterator begin() const { return const_iterator(head->next); }const_iterator end() const { return const_iterator(head); }void push_back(const T& val) {insert(end(), val);}void push_front(const T& val) {insert(begin(), val);}iterator insert(iterator pos, const T& val) {ListNode<T>* newNode = new ListNode<T>(val);ListNode<T>* cur = pos.ptr;newNode->prev = cur->prev;newNode->next = cur;cur->prev->next = newNode;cur->prev = newNode;return iterator(newNode);}iterator erase(iterator pos) {ListNode<T>* cur = pos.ptr;ListNode<T>* nextNode = cur->next;cur->prev->next = cur->next;cur->next->prev = cur->prev;delete cur;return iterator(nextNode);}void clear() {iterator it = begin();while (it != end()) {it = erase(it);}}};

五、list与vector的对比

表格

特性vectorlist 
底层结构动态顺序表,一段连续空间带头结点的双向循环链表 
随机访问支持随机访问,访问某个元素效率 O(1) 不支持随机访问,访问某个元素效率   O(n)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为 O(n)  ,插入时可能需要增容任意位置插入和删除效率高,不需要搬移元素,时间复杂度为   O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 
迭代器原生态指针对原生态指针(节点指针)进行封装 
迭代器失效插入元素时,可能导致重新扩容,使原来迭代器失效;删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问 

完整代码

#define _CRT_SECURE_NO_WARNINGS
#pragma once 
#include <iostream> 
using namespace std;
#include <assert.h> 
namespace ldg
{// List的节点类 template<class T>struct ListNode{ListNode(const T& val = T()): _prev(nullptr), _next(nullptr), _val(val){}ListNode<T>* _prev;ListNode<T>* _next;T _val;};/*List 的迭代器迭代器有两种实现方式,具体应根据容器底层数据结构实现:1. 原生态指针,比如:vector2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:1. 指针可以解引用,迭代器的类中必须重载operator*()2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前 移动,所以需要重载,如果是forward_list就不需要重载--4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()*/template<class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;// Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到 public:typedef Ref Ref;typedef Ptr Ptr;public:// // 构造 ListIterator(Node* node = nullptr): _node(node){}// // 具有指针类似行为 Ref operator*(){return _node->_val;}Ptr operator->(){return &(operator*());}// // 迭代器支持移动 Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self temp(*this);_node = _node->_next;return temp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self temp(*this);_node = _node->_prev;return temp;}// // 迭代器支持比较 bool operator!=(const Self& l)const{return _node != l._node;}bool operator==(const Self& l)const{return _node != l._node;}Node* _node;};template<class Iterator>class ReverseListIterator{// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量 // 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量 // 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的 public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;public:// // 构造 ReverseListIterator(Iterator it): _it(it){}// // 具有指针类似行为 Ref operator*(){Iterator temp(_it);--temp;return *temp;}Ptr 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& l)const{return _it != l._it;}bool operator==(const Self& l)const{return _it != l._it;}Iterator _it;};template<class T>class list{typedef ListNode<T> Node;public:// 正向迭代器 typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T&> const_iterator;// 反向迭代器 typedef ReverseListIterator<iterator> reverse_iterator;typedef ReverseListIterator<const_iterator> const_reverse_iterator;public:/// // List的构造 list(){CreateHead();}list(int n, const T& value = T()){CreateHead();for (int i = 0; i < n; ++i)push_back(value);}template <class Iterator>list(Iterator first, Iterator last){CreateHead();while (first != last){push_back(*first);++first;}}list(const list<T>& l){CreateHead();// 用l中的元素构造临时的temp,然后与当前对象交换 list<T> temp(l.begin(), l.end());this->swap(temp);}list<T>& operator=(list<T> l){this->swap(l);return *this;}~list(){clear();delete _head;_head = nullptr;}/// // List的迭代器 iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin()const{return const_reverse_iterator(end());}const_reverse_iterator rend()const{return const_reverse_iterator(begin());}/// // List的容量相关 size_t size()const{Node* cur = _head->_next;size_t count = 0;while (cur != _head){count++;cur = cur->_next;}return count;}bool empty()const{return _head->_next == _head;}void resize(size_t newsize, const T& data = T()){size_t oldsize = size();if (newsize <= oldsize){// 有效元素个数减少到newsize while (newsize < oldsize){pop_back();oldsize--;}}else{while (oldsize < newsize){push_back(data);oldsize++;}}}// List的元素访问操作 // 注意:List不支持operator[] T& front(){return _head->_next->_val;}const T& front()const{return _head->_next->_val;}T& back(){return _head->_prev->_val;}const T& back()const{return _head->_prev->_val;}// List的插入和删除 void push_back(const T& val){insert(end(), val);}void pop_back(){erase(--end());}void push_front(const T& val){insert(begin(), val);}void pop_front(){erase(begin());}// 在pos位置前插入值为val的节点 iterator insert(iterator pos, const T& val){Node* pNewNode = new Node(val);Node* pCur = pos._node;// 先将新节点插入 pNewNode->_prev = pCur->_prev;pNewNode->_next = pCur;pNewNode->_prev->_next = pNewNode;pCur->_prev = pNewNode;return iterator(pNewNode);}// 删除pos位置的节点,返回该节点的下一个位置 iterator erase(iterator pos){// 找到待删除的节点 Node* pDel = pos._node;Node* pRet = pDel->_next;// 将该节点从链表中拆下来并删除 pDel->_prev->_next = pDel->_next;pDel->_next->_prev = pDel->_prev;delete pDel;return iterator(pRet);}void clear(){Node* cur = _head->_next;// 采用头删除删除 while (cur != _head){_head->_next = cur->_next;delete cur;cur = _head->_next;}_head->_next = _head->_prev = _head;}void swap(ldg::list<T>& l){std::swap(_head, l._head);}private:void CreateHead(){_head = new Node;_head->_prev = _head;_head->_next = _head;}private:Node* _head;};
}
/// 
// 对模拟实现的list进行测试 
// 正向打印链表 
template<class T>
void PrintList(const ldg::list<T>& l)
{auto it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;
}
// 测试List的构造 
void TestBiteList1()
{ldg::list<int> l1;ldg::list<int> l2(10, 5);PrintList(l2);int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };ldg::list<int> l3(array, array + sizeof(array) / sizeof(array[0]));PrintList(l3);ldg::list<int> l4(l3);PrintList(l4);l1 = l4;PrintList(l1);
}
// PushBack()/PopBack()/PushFront()/PopFront() 
void TestBiteList2()
{// 测试PushBack与PopBack ldg::list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);PrintList(l);l.pop_back();l.pop_back();PrintList(l);l.pop_back();cout << l.size() << endl;// 测试PushFront与PopFront l.push_front(1);l.push_front(2);l.push_front(3);PrintList(l);l.pop_front();l.pop_front();PrintList(l);l.pop_front();cout << l.size() << endl;
}// 测试insert和erase 
void TestBiteList3()
{int array[] = { 1, 2, 3, 4, 5 };ldg::list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto pos = l.begin();l.insert(l.begin(), 0);PrintList(l);++pos;l.insert(pos, 2);PrintList(l);l.erase(l.begin());l.erase(pos);PrintList(l);// pos指向的节点已经被删除,pos迭代器失效 cout << *pos << endl;auto it = l.begin();while (it != l.end()){it = l.erase(it);}cout << l.size() << endl;
}// 测试反向迭代器 void TestBiteList4()
{int array[] = { 1, 2, 3, 4, 5 };ldg::list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;const ldg::list<int> cl(l);auto crit = l.rbegin();while (crit != l.rend()){cout << *crit << " ";++crit;}cout << endl;
}

六、总结

 list  容器凭借其独特的双向链表结构,在插入和删除操作上具有高效性,适用于需要频繁进行这些操作的场景。通过深入理解其接口和实现原理,我们可以在实际编程中更好地运用它,同时与  vector  等其他容器对比,能更清晰地知道在不同需求下如何选择合适的容器。希望这篇博客能帮助大家对  list  容器有更全面、深入的认识。

http://www.xdnf.cn/news/134011.html

相关文章:

  • 在 Java 项目中搭建和部署 Docker 的详细流程
  • Jenkins流水线管理工具
  • Estimands与Intercurrent Events:临床试验与统计学核心框架
  • Flink TaskManager详解
  • Unity开发者快速认识Unreal 的BluePrint(二)
  • 软件测试流程
  • 代理ip和实际ip的区别和联系
  • MySQL的MVCC【学习笔记】
  • stone 3d v3.3.0版本发布,含时间线和连接器等新功能
  • 零信任架构:重塑网络安全的IT新范式
  • Redis ⑥-string | hash | list
  • 金仓数据库征文-政务领域国产化数据库更替:金仓 KingbaseES 应用实践
  • 使用springboot+easyexcel实现导出excel并合并指定单元格
  • VuePress可以做什么?
  • Keras中Lambda层的常用方法
  • 告别默认配置!Xray自定义POC开发指南
  • Linux字符设备驱动开发的详细步骤
  • ubuntu22.04 命令行修改静态ip
  • 贝叶斯优化GAM回归(matlab代码)
  • 牛客小白月赛115-B题:签到题
  • Python Cookbook-6.9 快速复制对象
  • 代码随想录算法训练营第60期第十七天打卡
  • 小白如何使用Cursor运行python程序(含环境配置教程)
  • 隐形革命:环境智能如何重构“人-机-境“共生新秩序
  • 关于循环缓冲区
  • 【java源码】AI智能导诊系统,基于H5、小程序、app等多端,引导患者自助就诊挂号,实现科学就诊
  • 4G卡的DTU固件TCP通讯
  • 【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
  • 秒级到毫秒:BFD的速度革命
  • Swift闭包(Closure)深入解析与底层原理