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

初识C++|list类的使用及模拟实现

🍬 mooridy-CSDN博客

🧁C++专栏(更新中!)

目录

list的介绍

list的使用

list的构造

list 容量

list 访问

list 增删查改

迭代器

迭代器失效问题

list模拟实现

list与vector的对比

emplace_back和push_back的区别


list的介绍

list底层——带头双向循环链表

list的使用

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

list<int> lt1;                                // 构造空列表
list<int> lt2 (4,100);                       // 构造有4个100的列表
list<int> lt3 (lt2.begin(),lt2.end());  // 用迭代器构造
list<int> lt4 (third);                       // 拷贝构造

list 容量

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

list 访问

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

list 增删查改

函数声明接口说明
push_frontlist首元素前插入值为val的元素
pop_front删除list中第一个元素
push_backlist尾部插入值为val的元素
pop_back删除list中最后一个元素
insertlist position 位置前插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素
list<int> first (3,100);   
list<int> second (5,200);  it = first.begin();
first.insert(it,5);//在it前加1个5
first.insert(it,2,6);//在it前加2个6first.swap(second);

迭代器

1.

之前我们学习vector容器时可以简单的将迭代器理解为指针。然而在list中,这有的理解显然是有失偏颇的。如list的介绍中所提到的,list的底层实现是带头双向循环链表,而我们知道链表的空间是不连续的,如果还以为迭代器是指针,那么++的位置只能是结点的下一个位置,但这个理解显然不对。

那又为什么我们却能像vector一样的去使用list的迭代器呢?
而且*就是访问的数据,++和- -就能找到结点的后一个结点和前一个结点。

我们能用类似vector迭代器的方式操作list迭代器,是因为C++的类封装和操作符重载。迭代器封装了复杂的容器遍历逻辑,通过重载++--*等操作符,让我们能以直观方式访问元素,而无需关心其底层实现细节。

具体是如何实现的,可以看看下面的list模拟实现部分。

2.

在使用过程中,需注意list的迭代器只有++,--的功能,一步步的移动迭代器,是无法像vector的迭代器一样实现+或-功能进行跨越的加减。

迭代器按性质划分:

性质代表容器功能
单向ForwardIteratorforward_lisr/unordered_map...++
双向BidirectionalIteratorlist/map/set...++/--
随机RandomAcessIteratorvector/string/deque++/--/+/-

底层结构可以决定使用哪些算法。

迭代器失效问题

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

insert不会失效——只是修改了指针指向关系,而没有修改迭代器本身

erase——指向删除元素的迭代器失效

list模拟实现

//list.h
#include<iostream>
using namespace std;namespace AA
{// List的节点类template<class T>struct ListNode{ListNode(const T& val = T()){_val = val;_prev = nullptr;_next=nullptr;}ListNode<T>* _prev;ListNode<T>* _next;T _val;};//List的迭代器类template<class T,class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T,Ref,Ptr> Self;public:ListIterator(Node* pNode = nullptr) {_pNode = pNode;}ListIterator(const Self& l) {*this = l;}Ref operator*() {return _pNode->_val;}Ptr operator->() {return &_pNode->_val;}Self& operator++() {_pNode = _pNode->_next;return *this;}Self operator++(int) {Self* tmp(*this);_pNode = _pNode->_next;return tmp;}Self& operator--() {_pNode = _pNode->_prev;return *this;}Self& operator--(int) {Self* tmp(*this);_pNode = _pNode->_prev;return tmp;}bool operator!=(const Self& l) {return _pNode != l._pNode;}bool operator==(const Self& l) {return _pNode == l._pNode;}private:Node* _pNode;};//list类template<class T>class list{typedef ListNode<T> Node;//typedef Node* PNode;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T&> const_iterator;public:///// List的构造list() {_pHead = new Node;_pHead->next = _pHead;_pHead->prev = _pHead;_size = 0;}list(int n, const T& value = T()) {_pHead = new Node;Node* pcur = _pHead;for (int i = 0; i < n; i++) {Node* newnode = new Node;newnode->val = value;newnode->next = _pHead;newnode->prev = pcur;pcur->next = newnode;pcur = newnode;_pHead->prev = pcur;}_size = n;}template <class Iterator>list(Iterator first, Iterator last);list(const list<T>& l);list<T>& operator=(const list<T> l);~list() {clear();delete _pHead;_pHead = nullptr;_size = 0;}///// List Iteratoriterator begin() {return _pHead->next;}iterator end() {return _pHead->prev;}const_iterator begin() {return _pHead->next;}const_iterator end() {return _pHead->prev;}///// List Capacitysize_t size()const {return _size;}bool empty()const {return size == 0;}// List AccessT& front() {return _pHead->next->_val;}const T& front()const {return _pHead->next->_val;}T& back() {return _pHead->prev->_val;}const T& back()const {return _pHead->prev->_val;}// List Modifyvoid 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* newnode = new Node;newnode->_val = val;Node* pcur = pos._pNode;Node* prev = pcur->_prev;newnode->_next = pcur;newnode->_prev = prev;prev->_next = newnode;pcur->_prev = newnode;++_size;return newnode;}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos) {Node* pcur = pos._pNode;Node* prev = pcur->_prev;Node* next = pcur->_next;delete pcur;pcur = nullptr;prev->_next = next;next->_prev = prev;--_size;return next;}void clear() {Node* pcur = _pHead->next;while (pcur!=_pHead) {pcur = erase(pcur);}}void swap(list<T>& l) {std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:size_t _size;Node* _pHead;};
};

list与vector的对比

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

emplace_back和push_back的区别

push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);

而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。这样效率更高。

同时在使用上,有下图代码所展示的小区别:

请注意:emplace_back() 是 C++ 11 标准新增加的,如果程序需要兼顾之前的版本,还是应该使用 push_back()。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Qt 调用MFC dll,动态库中有界面
  • windows C++ 并行编程-使用 Lambda 表达式
  • MYSQL数据库(四)
  • JavaScript学习文档(11):Window对象、本地存储、数组中一些方法、学生就业统计表案例
  • 15行为型设计模式——责任链模式
  • Java基础入门【第六章 static、继承、重写、多态】(二)
  • 数据合规性分析:守护信息安全的关键防线
  • MySQL的安装配置以及可视化工具的安装
  • 【拉取Git项目到本地,知识小记,后续再改】
  • 春秋云镜initial
  • 宏集MIRO-L230工业路由器: 一站式全球联网解决方案
  • C++ 图形框架 Duilib
  • UE5学习笔记18-使用FABRIK确定骨骼的左手位置
  • 安装vue-cli2.0并创建项目
  • 实习项目|苍穹外卖|day2
  • DOM的那些事
  • JavaScript-Array类型
  • Java应用性能调优
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • leetcode386. Lexicographical Numbers
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • Vue 重置组件到初始状态
  • 跨域
  • 前端之Sass/Scss实战笔记
  •  一套莫尔斯电报听写、翻译系统
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (web自动化测试+python)1
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (分布式缓存)Redis持久化
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (强烈推荐)移动端音视频从零到上手(上)
  • (限时免费)震惊!流落人间的haproxy宝典被找到了!一切玄妙尽在此处!
  • (转)【Hibernate总结系列】使用举例
  • .bat批处理(六):替换字符串中匹配的子串
  • .net Application的目录
  • .Net core 6.0 升8.0
  • .Net Core中的内存缓存实现——Redis及MemoryCache(2个可选)方案的实现
  • .NET Core中的去虚
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .NET企业级应用架构设计系列之结尾篇
  • .NET中统一的存储过程调用方法(收藏)
  • .net中我喜欢的两种验证码
  • .stream().map与.stream().flatMap的使用
  • @Transactional 竟也能解决分布式事务?
  • [Angular] 笔记 7:模块
  • [BT]小迪安全2023学习笔记(第15天:PHP开发-登录验证)
  • [Debugger]调试Arm设备
  • [ffmpeg] av_opt_set 解析
  • [HarekazeCTF2019]encode_and_encode 不会编程的崽