c++中的list容器讲解

文章目录

  • 1. list的介绍及使用
    • 1.1 list的介绍
    • 1.2 list的使用
      • 1.2.1 list的构造
      • 1.2.2 list iterator的使用
      • 1.2.3 list capacity
      • 1.2.4 list element access
      • 1.2.6 list的迭代器失效
  • 2. list的模拟实现
    • 2.1 模拟实现list
  • 3. list与vector的对比

1. list的介绍及使用

1.1 list的介绍

list的文档介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
    其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
    效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
    更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
    的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
    开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这
    可能是一个重要的因素)

1.2 list的使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展
的能力。以下为list中一些常见的重要接口

1.2.1 list的构造

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

1.2.2 list iterator的使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

函数声明接口说明
begin +end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rebegin +rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置

【注意】:

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

1.2.3 list capacity

函数说明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

1.2.4 list element access

函数说明接口说明
push_front 在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase 删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

list中还有一些操作,需要用到时大家可参阅list的文档说明。

1.2.6 list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节
点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代
器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
list的各个接口代码,演示:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<list>
#include<vector>using namespace std;//list的构造
void constructor()
{/*list<int> l1();*/list<int> l2(4, 100);list<int> l3(l2.begin(), l2.end());list<int> l4(l3);int array[5] = { 1,2,3,4,5 };list<int> l5(array, array+sizeof(array) / sizeof(array[0]));list<int> l6{ 9,8,7,6,5,4 };// 用迭代器方式打印l5中的元素list<int>::iterator it = l5.begin();for (; it != l5.end(); ++it){cout << *it << " ";}cout << endl;for (auto e : l5){cout << e << " ";}cout << endl;
}
// list迭代器的使用
// 注意:遍历链表只能用迭代器和范围forvoid Printlist(const list<int>& l)
{// 注意这里调用的是list的 begin() const,返回list的const_iterator对象for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";}cout << endl;/*std::list<int> mylist;for (int i = 1; i <= 5; ++i) mylist.push_back(i);std::cout << "mylist backwards:";for (std::list<int>::reverse_iterator rit = mylist.rbegin(); rit != mylist.rend(); ++rit)std::cout << ' ' << *rit;std::cout << '\n';*/}void TestList2()
{int array[10] = { 1,2,3,4,5,6,7,8,9,10 };list<int> ls(array, array + sizeof(array) / sizeof(array[0]));for (auto e : ls){cout << e << " ";}cout << endl;
}void test3()
{list<int> lt{ 9,8,7,6,5,4 };int sz = lt.size();list<int>::iterator it = lt.begin();for (int i = 0; i < sz; ++i){cout << *it << " ";++it;}cout << endl;
}

// list插入和删除
// push_back/pop_back/push_front/pop_front
void TestList3()
{int array[] = { 1,2,3 };list<int> L(array, array + sizeof(array) / sizeof(array[0]));L.push_back(4);L.push_front(0);Printlist(L);L.pop_back();L.pop_front();Printlist(L);
}void TestList4()
{int array1[] = { 1,2,3 };list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));auto pos = ++L.begin();cout << *pos << endl;L.insert(pos, 5, 5);Printlist(L);vector<int> v{ 7,8,9 };L.insert(pos, v.begin(), v.end());Printlist(L);L.erase(pos);Printlist(L);L.erase(L.begin(), L.end());Printlist(L);
}void TestList5()
{// 用数组来构造listint array1[] = { 1, 2, 3 };list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));Printlist(l1);// 交换l1和l2中的元素list<int> l2;l1.swap(l2);Printlist(l1);Printlist(l2);// 将l2中的元素清空l2.clear();cout << l2.size() << endl;
}void Testiterator()
{int array[10] = { 1,2,3,4,5,6,7,8,9,10 };list<int> l1(array, array + sizeof(array) / sizeof(array[0]));auto it = l1.begin();while (it != l1.end()){l1.erase(it);++it;}
}// 改正
void TestListIterator()
{int array[] = { 1,2,3,4,5,6,7,8 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++);//erase后it的所指的被删除位置的迭代器失效,通过it++来解决,//因为失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响}
}int main()
{constructor();cout << endl;list<int> l1{ 1,2,3,45 };Printlist(l1);cout << endl;TestList2();cout << endl;test3();cout << endl;TestList3();cout << endl;TestList4();cout << endl;TestList5();cout << endl;//Testiterator();//cout << endl;TestListIterator();cout << endl;return 0;
}

在这里插入图片描述

2. list的模拟实现

2.1 模拟实现list

要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,通过上面的学习,这些内容已基本掌握,现
在我们来模拟实现list。
list.h

#pragma once
#include<iostream>
#include<assert.h>using namespace std;namespace hcm
{template<class T>struct ListNode{ListNode(const T& val = T()):_prev(nullptr), _next(nullptr), _val(val){}ListNode<T>* _prev;ListNode<T>* _next;T _val;};template<class T,class Ref,class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;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 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& 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){// 有效元素个数减少到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){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(hcm::list<T>& l){std::swap(_head, l._head);}private:void CreateHead(){_head = new Node;_head->_prev = _head;_head->_next = _head;}private:Node* _head;};}template<class T>
void PrintList(const hcm::list<T>& l)
{auto it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;
}

list.cpp

#include"list.h"// 测试List的构造
void TestList1()
{hcm::list<int> l1;hcm::list<int> l2(10, 5);PrintList(l2);int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };hcm::list<int> l3(array, array + sizeof(array) / sizeof(array[0]));PrintList(l3);hcm::list<int> l4(l3);PrintList(l4);l1 = l4;PrintList(l1);}
// PushBack()/PopBack()/PushFront()/PopFront()
void TestBiteList2()
{hcm::list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(10);PrintList(lt);lt.pop_back();PrintList(lt);lt.push_front(0);PrintList(lt);lt.pop_front();PrintList(lt);}// 测试insert和erase
void TestBiteList3()
{int array[] = { 1, 2, 3, 4, 5 };hcm::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 };hcm::list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;const hcm::list<int> cl(l);auto crit = l.rbegin();while (crit != l.rend()){cout << *crit << " ";++crit;}cout << endl;
}int main()
{TestList1();TestBiteList2();TestBiteList3();TestBiteList4();return 0;
}

运行结果如下:
在这里插入图片描述

3. list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不
同,其主要不同如下:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/102163.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka3.0.0版本——增加副本因子

目录 一、服务器信息二、启动zookeeper和kafka集群2.1、先启动zookeeper集群2.2、再启动kafka集群 三、增加副本因子3.1、增加副本因子的概述3.2、增加副本因子的示例3.2.1、创建topic(主题)3.2.2、手动增加副本存储 一、服务器信息 四台服务器 原始服务器名称原始服务器ip节点…

时序预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神…

Web framework-Gin(二)

目录 一、Gin 1、Ajax 2、文件上传 2.1、form表单中文件上传(单个文件) 2.2、form表单中文件上传(多个文件) 2.3、ajax上传单个文件 2.4、ajax上传多个文件 3、模板语法 4、数据绑定 5、路由组 6、中间件 一、Gin 1、Ajax AJAX 即“Asynchronous Javascript And XM…

数据库 范式化和反范式化

第一范式 1NF 主要确保数据表中每个字段的值必须具有原子性&#xff0c;也就是说数据表中每个字段的值为不可再次拆分的最小数据单元 第二范式 2NF 在满足第一范式的基础上&#xff0c;还要满足数据表里的每一条数据记录&#xff0c;都是可唯一标识的&#xff0c;而且所有非…

使用半导体材料制作霍尔元件的优点

霍尔元件是一种基于霍尔效应的传感器&#xff0c;可以测量磁场强度和电流等物理量。霍尔效应是指&#xff0c;当电流通过一块导体时&#xff0c;如果该导体置于垂直于电流方向的磁场中&#xff0c;就会在导体两侧出现一定的电势差&#xff0c;这就是霍尔效应。霍尔元件可以利用…

C++-map和set

本期我们来学习map和set 目录 关联式容器 键值对 pair 树形结构的关联式容器 set multiset map multimap 关联式容器 我们已经接触过 STL 中的部分容器&#xff0c;比如&#xff1a; vector 、 list 、 deque 、forward_list(C11)等&#xff0c;这些容器统称为序列式…

【Rust】001-基础语法:变量声明及数据类型

【Rust】001-基础语法&#xff1a;变量声明及数据类型 文章目录 【Rust】001-基础语法&#xff1a;变量声明及数据类型一、概述1、学习起源2、依托课程 二、入门程序1、Hello World2、交互程序代码演示执行结果 3、继续上难度&#xff1a;访问链接并打印响应依赖代码执行命令 三…

《Java程序设计》实验报告

实验内容&#xff1a;面向对象程序设计 1、定一个名为Person的类&#xff0c;其中含有一个String类型的成员变量name和一个int类型的成员变量age&#xff0c; 分别为这两个变量定义访问方法和修改方法&#xff0c;另外再为该类定义一个名为speak的方法&#xff0c; 在其中输出n…

role、user、schema在Oracle、MySQL、PostgreSQL的区别

0.先上结论 数据库逻辑可以细分为&#xff1a;角色、用户、数据库、模式PostgreSQL和MySQL合并了角色和用户&#xff0c;MySQL还合并了数据库、模式Oracle合并了用户、数据库、模式 1.图 1.1.架构 1.2.用户和角色 1.2.1.PostgreSQL 1.2.2.MySQL 1.2.3.Oracle 参考文章 数据…

MATLAB创建avi文件

简介 MATLAB可以对音频和视频文件进行处理&#xff0c;这里简单说明一下MATLAB创建avi文件的用法。 常用函数 aviinfo 测试用用例&#xff0c;如下所示 用于avi格式的音频视频文件&#xff0c;返回一个对该文件的描述&#xff0c;只能用于avi格式的音频视频文件。 仔细看…

E. Nastya and Potions

Problem - E - Codeforces 思路&#xff1a;想到用图论前驱图了&#xff0c;但是因为考虑可能有环的存在&#xff0c;但是其实题干中说明了不能通过一种或几种混合得到自己&#xff0c;所以就保证了不存在环&#xff0c;那就能用拓扑结构的性质做&#xff0c;用记忆化搜索就可以…

Linux之SELinux

目录 概述 定义 作用 SELinux与传统的权限区别 SELinux工作原理 名词解释 主体&#xff08;Subject&#xff09; 目标&#xff08;Object&#xff09; 策略&#xff08;Policy&#xff09; 安全上下文&#xff08;Security Context&#xff09; 文件安全上下文查看 …