【C++】STL — List的接口讲解 +详细模拟实现

devtools/2024/9/23 9:23:40/

前言:
本章我们将学习STL中另一个重要的类模板list

  • list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  • list的底层是带头双向循环链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  • list与forward_list非常相似:主要区别在于forward_list对象是单链接列表,因此它们只能向前迭代,以换取更小、更高效。
  • 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  • 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,需要线性的时间开销。

list是带哨兵位头节点的双向循环链表,
list中进行插入节点不会导致list的迭代器失效,
只有删除节点时才会出现失效问题,并且失效的只是指向被删除节点的迭代器,会有野指针问题,其他迭代器不受影响

目录

    • 1. list的使用
      • 1.1 list的初始化 + 迭代器的使用
      • 1.2 对list的排序
    • 2. list的模拟实现(list.h)
      • 2.1 链表结点的申请:
      • 2.2 用类封装List 的迭代器
      • 2.3 List链表的实现
      • 2.4 迭代器失效的问题

list_15">1. list的使用

我们学习的STL中的list是一种:带头双向循环链表。(带有哨兵位头结点的)

  • 带头双向循环链表 – 链表中的最优设计
  • 可以实现任意位置〇(1)的插入删除,只需要改前后的关系

list___20">1.1 list的初始化 + 迭代器的使用

在我们使用list之前我们需要先包一下头文件#include< list >
在这里插入图片描述

#include <iostream>
#include <list>int main ()
{// constructors used in the same order as described above:std::list<int> first;                                // empty list of intsstd::list<int> second (4,100);                       // four ints with value 100std::list<int> third (second.begin(),second.end());  // iterating through secondstd::list<int> fourth (third);                       // a copy of third// the iterator constructor can also be used to construct from arrays:int myints[] = {16,2,77,29};std::list<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );return 0;
}

list_43">1.2 对list的排序

对于一般的容器而言,我们包一个算法库 #incldue < alogrithm > 可以对普通的容器进行排序。

void test_list2()
{vector<int> v;v.push_back(1);v.push_back(4);v.push_back(2);v.push_back(4);v.push_back(3);sort(v.begin(), v.end());for (auto e : v){cout << e << " ";}cout << endl;list<int> lt;lt.push_back(1);lt.push_back(4);lt.push_back(2);lt.push_back(4);lt.push_back(3);lt.sort();for (auto e : lt){cout << e << " ";}cout << endl;
}
  • 像vector和string而言,这种连续的容器可以直接用库中的sort
  • 而对于list而言和之前的顺序容器有所区别,因为其链式结构,库中的算法不支持
  • list单独实现了一个自己的排序
  • 但是list的排序效率很低
    在这里插入图片描述

注意:
可见把list的数据拷贝到vector中再,用sort算法对vector中排序,再将vector中的数据拷贝到list中都比直接用list排序要快,所以list的排序效率很低。

listlisth_88">2. list的模拟实现(list.h)

在这里插入图片描述

2.1 链表结点的申请:

namespace Joker
{//list的节点类template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _data;list_node(const T& val = T()):_next(nullptr), _prev(nullptr), _data(val){}};

注意:

- 默认生成的析构函数够用。

2.2 用类封装List 的迭代器

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

    1. 原生态指针,比如:vector
    1. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
      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;public:// 构造ListIterator(Node* node = nullptr): _node(node){}// 具有指针类似行为Ref operator*() { return _node->_data;}Ptr operator->() { //return &(operator*()); return &_node->_data;}	//// 迭代器支持移动Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self temp(*this);_node = _node->_next;return temp;}//--itSelf& operator--(){_node = _node->_prev;return *this;}//it--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;};

注意:

  • list-item">

    list-item-checkbox" checked="true" disabled="disabled" /> 析构函数(不需要写)-- 节点不属于迭代器,不需要迭代器释放

  • 编译器生成的默认析构函数够用,对内置类型不敢处理,只对自定义类型处理

  • list-item">

    list-item-checkbox" checked="true" disabled="disabled" /> 拷贝构造和赋值重载(不需要写)

  • 默认生成的浅拷贝就可以

(2)运算符重载 - > :

Ptr operator->() {//return &(operator*());0return &_node->_data;}返回的是一个指针。

优化如下

  • it.operator->() – 返回类型是AA*的迭代器
  • it.operator->() ->_data;
  • 编译器为了可读性进行了优化处理
  • 如果不优化应该是it->->_data;
  • 优化以后,省略了一个->

2.3 List链表的实现

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;// List的构造list(){_head = new Node();_head->_next = _head;_head->_prev = _head;}list(int n, const T& value = T()){empty_init();for (int i = 0; i < n; ++i)push_back(value);}//创造一个哨兵位头结点出来,通用void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;}template <class Iterator(大写I)>list(Iterator first, Iterator last){empty_init();while (first != last){push_back(*first);++first;}}
//拷贝构造现代写法:--需要使用深拷贝list(const list<T>& l){//创造一个哨兵位头节点出来,不初始化就是随机值empty_init();// 用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){// 有效元素个数减少到newsizewhile (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) { //Node* tail=_head->_prev;//Node* newnode=new node(x);_head     tail  newnode//tail->_next=newnode;//newnode->_prev=tail;//newnode->_next=_head;//_head->prev=newnode;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(bite::list<T>& l){std::swap(_head, l._head);}private:Node* _head;};
}

2.4 迭代器失效的问题

  • list-item">list-item-checkbox" checked="true" disabled="disabled" /> list insert迭代器不失效,不存在野指针的问题,也不存在意义变了的问题
  • list-item">list-item-checkbox" checked="true" disabled="disabled" /> list erase(it)以后,迭代器是会失效的,是野指针问题

解决办法:

  • 之前vector容器迭代器失效时解决办法一样,使用返回值接收。

尾声
看到这里,相信大家对这个C++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦


http://www.ppmy.cn/devtools/33111.html

相关文章

rknn adb shell error: closed

博主的答案&#xff1a; 【Android测试】adb shell回车后出现 error closed的解决办法-CSDN博客 第1种&#xff1a;重启电脑&#xff0c;之后把手机查到电脑上&#xff0c;启动idea 第2种&#xff1a;手机-设置-应用程序-开发-usb调试打开再关闭一次 第3种&#xff1a;重启手…

从 Word 文档中提取所有的有效 JSON 对象(包含跨段落)

文章目录 一、概述二、代码 一、概述 从 word 中提取所有有效 json &#xff08;包含跨段落的 json&#xff09;。 二、代码 """ 从 Word 文档中提取所有的 JSON 对象 """from docx import Document import jsondef extract_json_from_docx(d…

脉冲激活图(PAM)

脉冲激活图&#xff08;Pulse Activation Map&#xff0c;PAM&#xff09;是一种用于解释神经网络决策过程的技术&#xff0c;特别是在处理时间序列数据或脉冲信号时。PAM通过可视化神经网络中特定层的激活情况&#xff0c;帮助理解模型是如何响应不同类型的输入信号的。以下是…

LeetCode LCR 179. 和为s的两个数字

原题链接&#xff1a;LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09; 题目的意思&#xff1a;通过给定的数组&#xff0c;找出两个值&#xff0c;相加并等于目标值。 第一种思路&#xff0c;暴力枚举&#xff0c;伪代码如下&#xff1a; for (…

【大数据】利用 Apache Ranger 管理 Amazon EMR 中的数据权限

利用 Apache Ranger 管理 Amazon EMR 中的数据权限 1.需求背景简介2.系统方案架构图3.主要服务和组件简介3.1 Amazon EMR3.2 Simple Active Directory3.3 Apache Ranger 4.部署步骤4.1 部署 Simple AD 服务4.2 部署 Apache Ranger4.3 部署 Amazon EMR4.4 在 Amazon EMR 的主节点…

STM32解决空闲中断误触发问题.

在用串口传输大量数据时&#xff0c;发现空闲中断误触发 我是在做用串口将大量数据传入MCU这易操作时&#xff0c;发现一帧数据还没发完成&#xff0c;就进如来空闲中断&#xff0c;导致数据不完整&#xff0c;有点数据混乱了。 参考别的博主说法&#xff0c;在1个或1.5个字节时…

手机恢复出厂设置ip地址会变吗

当我们对手机进行恢复出厂设置时&#xff0c;很多人会担心手机的IP地址是否会发生变化。IP地址对于手机的网络连接至关重要&#xff0c;它决定了手机在网络中的身份和位置。那么&#xff0c;手机恢复出厂设置后&#xff0c;IP地址到底会不会发生变化呢&#xff1f;虎观代理小二…

使用MATLAB/Simulink点亮STM32开发板LED灯

使用MATLAB/Simulink点亮STM32开发板LED灯-笔记 一、STM32CubeMX新建工程二、Simulink 新建工程三、MDK导入生成的代码 一、STM32CubeMX新建工程 1. 打开 STM32CubeMX 软件&#xff0c;点击“新建工程”&#xff0c;选择中对应的型号 2. RCC 设置&#xff0c;选择 HSE(外部高…