当前位置: 首页 > 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  容器有更全面、深入的认识。

相关文章:

  • python四级考试经验(考生必看)
  • 风光储能+智能调度,这才是企业未来能源管理的最优解
  • velero备份与恢复流程是什么?
  • 《WebGIS之Vue零基础教程》(5)计算属性与侦听器
  • 如何利用CST把PCB的S参数生成Spice电路模型
  • Java常用API详解
  • Shell脚本-for循环语法结构
  • VLA 论文精读(十九)Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware
  • 深度解析之算法之分治(快排)
  • 【金仓数据库征文】——选择金仓,选择胜利
  • 区块链如何达成共识:PoW/PoS/DPoS的原理、争议与适用场景全解
  • 从线性到非线性:简单聊聊神经网络的常见三大激活函数
  • STM32MPU开发之旅:从零开始构建嵌入式Linux镜像
  • 《深入理解 AOP》
  • 【金仓数据库征文】-金仓数据库性能调优 “快准稳” 攻略:实战优化,让数据处理飞起来
  • 新闻速递丨Altair 与 Databricks 达成合作,加速数据驱动型创新
  • 银发科技:AI健康小屋如何破解老龄化困局
  • Qt知识点1『16进制数值与文本互相转换』
  • 【金仓数据库征文】- 国产化迁移实战:从Oracle到KingbaseES的平滑过渡
  • day32 学习笔记
  • 上海嘉定远香文化环启用,运动、看展、听歌“一站式解决”
  • 科普|结石疼痛背后的危机信号:疼痛消失≠警报解除
  • 何以中国|福建武夷山:千年茶道的文旅“破圈”与新生
  • 金湘军辞去山西省省长职务
  • 用一生走丝路,91岁艺术家耿玉琨的书旅奇遇
  • 成都两宗宅地成功出让,民企四川联投溢价33%竞得郫都区宅地