【C++进阶】深入STL之list:模拟实现深入理解List与迭代器

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:初步了解 list
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀STL之list

  • 📒1. list的基本结构
  • 📕2. list的模拟实现
    • 🌈构造函数
    • 🌞析构函数
    • 🌙拷贝构造函数
    • ⭐赋值运算符重载
  • 📚3. list的迭代器
    • 🍂迭代器的基本结构
    • 🍁迭代器的运算符重载
    • 🌸list的迭代器
  • 📙4. list的const迭代器
    • 🎩方法一
    • 🎈方法二
  • 📜5. 统一的方式访问STL容器中的元素
  • 📔6. list与vector的对比
  • 📖7. 总结补充
    • 💧补充:insert和erase的模拟实现
    • 🔥总结


在软件开发中,数据结构和算法的选择与实现是每一个开发者都必须面对的问题。标准模板库(STL)为我们提供了一系列高效且通用的数据结构和算法模板,极大地简化了C++编程中的许多常见任务。然而,了解这些数据结构和算法背后的实现原理,不仅有助于我们更深入地理解STL,还能提升我们的编程能力和解决问题的能力

前言: 在STL中,list是一种双向链表,它支持在序列的任何位置进行快速插入和删除操作。与此同时,迭代器是STL中非常重要的一个概念,它使得我们能够以统一的方式遍历和访问STL容器中的元素。在深入了解STL的过程中,模拟实现list和迭代器无疑是一个极有价值的学习过程。

本节我们将从基本的链表结构开始,逐步构建出完整的list类,并实现相应的迭代器类。


📒1. list的基本结构

在这里插入图片描述
list是一个个带头双向循环链表,这意味着每个元素(通常称为节点)都有两个指针:一个指向前一个元素,另一个指向后一个元素,因此我们需要单独再定义一个类来表示节点结构,每个节点再串联起来构成list

节点定义(示例):

template<class T>
struct list_node
{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x),_next(nullptr),_prev(nullptr){}
};

而在构建list时,我们成员变量只需要一个头节点。

list定义(示例):

template<class T>
struct list
{typedef list_node<T> Node;
public:// 构造函数等可能的其他成员函数... 
private:Node* _head;
};

📕2. list的模拟实现

注意:关于eraseinsert这两个函数的模拟我们依然作为补充放在末尾

🌈构造函数

在拥有一个list我们只需要将它的头节点初始化一下

list构造(示例):

void empty_init()
{_head = new Node;_head->_prev = _head;_head->_next = _head;
}// 无参构造
list()
{empty_init();
}

🌞析构函数

关于析构函数,我们需要的是将所有节点一 一释放就ok啦!

在模拟析构函数之前,不得不先介绍一下clear这个函数,因为clear可以删除出头节点以外的所有节点,我们可以利用这一点帮助我们优化析构函数

list析构(示例):

void clear()
{// 依次清除节点itetator it = begin(); // 稍后会提到迭代器的模拟while(it != end()){it = erase(it);}	
}~list()
{clear(); // 删除出头节点以外的所有节点delete _head; // 单独删除一下头节点_head = nullptr;
}

🌙拷贝构造函数

在学习list时,我们发现list不会因为空间不够而需要扩容,因此在使用模拟list时,不用考虑是否会发生浅拷贝

list拷贝构造函数(示例):

//list(const list<T>& lt)
list(list<T>& lt) // 还未实现const迭代器,先使用常规的
{empty_init();for (auto e : lt){push_back(e); // push_back的实现其实是复用insert,文末有补充}
}

⭐赋值运算符重载

这里我们以让后传统写法和现代写法两种方法

list赋值运算符重载(示例):

// 传统写法
list<T>& operator=(const list<T>& lt)
{clear(); // 先将原来的list清空for (auto e : lt){push_back(e);}return *this;
}// 现代写法
void swap(list<T>& tmp)
{std::swap(_head, tmp._head);
}list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}

在介绍完list基本的结构后,让我们来看看今天的重点:迭代器


📚3. list的迭代器

在我们模拟实现stringvector时,我们认为迭代器就是一个原生指针,但是在list中迭代器底层不是简单的指针,因此我们要独立定义一个新的类


🍂迭代器的基本结构

迭代器定义(示例):

template<class T>
struct __list_iterator
{typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node;// 构造函数__list_iterator(Node* node):_node(node){}
};

我们将迭代器单独写作一个类,能解决更多的问题,以及避免其他麻烦


🍁迭代器的运算符重载

因为这些函数和前面差不太多,我们简单看看代码,带过了

代码(示例):

self& operator++() // 前置++
{_node = _node->_next;return *this;
}self& operator--() // 前置--
{_node = _node->_prev;return *this;
}self operator++(int) // 后置++
{self tmp(*this);_node = _node->_next;return &tmp;
}self operator--(int) // 后置--
{self tmp(*this);_node = _node->_prev;return &tmp;
}bool operator!=(const self& tmp)
{return _node != tmp._node; 
}bool operator-=(const self& tmp)
{return _node -= tmp._node;
}

而今天着重要强调以下两个运算符重载,因为const非const下这两个是有区别的:

//可读写
T& operator*()
{return _node->_data;
}
//可读写
T* operator->()
{return &_node->_data;
}
// it.operator->()-> 编译器帮我们省略了一个箭头->  it->

在定义完迭代器类之后,我们可以实现begin()end()来实现list范围for


🌸list的迭代器

迭代器代码(示例):

template<class T>
struct list
{typedef list_node<T> Node;
public:typedef __list_iterator<T> iterator;iterator begin(){//return iterator(_head->_next); // 匿名对象return _head->next;}iterator end(){//return iterator(_head); // 匿名对象return _head;}
private:Node* _head;
};

当然我们这里还没有实现const迭代器很多需要调用const对象的函数还无法使用,那么接下来让我们来模拟实现const迭代器,见证新的神奇


📙4. list的const迭代器

关于这个list的const迭代器其实有两种写法,常规的写法就是在定义一个新的const迭代器的类,虽然这样可以解决问题,但是会造成代码的冗余,让操作繁琐。而另一种方法就是在原有的迭代器类上进行修改,让它能具有两个迭代器都能使用的特点

🎩方法一

const迭代器实现(示例):

template<class T>
struct __list_const_iterator
{typedef list_node<T> Node;typedef __list_const_iterator<T> self;Node* _node;// 构造函数__list_const_iterator(Node* node):_node(node){}//可读不可写const T& operator*(){return _node->_data;}//可读不可写const T* operator->(){return &_node->_data;}// 可能的其他成员函数... 
};

🎈方法二

如果我们将这两个差异的内容单独表示出来归于模板中,因为在const与非const之间,无非就是T&,T*上能否读写的区别,不影响其他的函数实现,因此我们可以在模板上加上两个参数

模板参数实例化类型
RefT&,(const 变量时) const T&
PtrT*,(const 变量时) const T*

const迭代器实现(示例):

// 用一个模板来解决 const与非const
template<class T, class Ref, class Ptr>
struct __list_iterator
{typedef list_node<T> Node;// 会实例化成最匹配的typedef __list_iterator<T, Ref, Ptr> self;Node* _node;// 构造函数__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}// 可能的其他成员函数... 
};
template<class T>
struct list
{typedef list_node<T> Node;
public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_const_iterator<T, const T&, const T*>  const_iterator;iterator begin(){return _head->next;}iterator end(){return _head;}const_iterator begin() const{return _head->next;}const_iterator end() const{return _head;}
private:Node* _head;
};

关于list的模拟实现我们就讲到这里,让我看看如何以统一的方式遍历和访问STL容器中的元素


📜5. 统一的方式访问STL容器中的元素

在完成对list的模拟实现后,我们试着用来遍历和访问list中的元素

代码实现(示例):

void print_list(const list<int>& lt)
{// list<T>没有实例化话,就并不能去遍历寻找// 编译器不知道 list<T>::const_iterator 是内嵌类型,还是静态成员变量list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}
}
void test_list()
{list<int> lt;lt.push_back(1);lt.push_back(2);print_list(lt);cout << endl;
}

编译器不知道 list::const_iterator 是内嵌类型,还是静态成员变量,但是如果实例化成int后,有需要一个成员是string的列表这时我们有犯难了,这时我们就要用到typenametypename 就是告诉编译器,这是一个类型,等list实例化之后再去取

代码实现(示例):

template<typename T>
void print_list(const list<T>& lt)
{......// typename 就是告诉编译器,这是一个类型,等list实例化之后再去取typename list<T>::const_iterator it = lt.begin();......
}

但是更离谱的来了,这时又有人要求我们打印vector的值,容器都换了我们该怎么办呢?这时模板的作用又双体现出来了,这也体现了模板的本质,让我们能省的活交给编译器完成

代码实现(示例):

// 这里直接搞了一个Container来适配容器
template<typename Container>
void print_container(const Container& con)
{typename  Container::const_iterator it = con.begin();while (it != con.end()){cout << *it << " ";++it;}
}

它使得我们能够以统一的方式遍历和访问STL容器中的元素


📔6. list与vector的对比

我们可以发现list与之前学的竟然有那么多的差异,我们结合上节学的vector来分析一下它们的差异:vectorlist都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同

vectorlist
底 层 结 构动态顺序表,一段连续空间带头结点的双向循环链表
随 机 访 问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插 入 和 删 除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空 间 利 用 率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭 代 器插入删除时触发条件会导致迭代器失效删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使 用 场 景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

📖7. 总结补充

💧补充:insert和erase的模拟实现

代码实现(示例):

// insert会返回插入位置的一个迭代器
iterator insert(iterator pos, const T& x)
{Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;cur->_prev = newnode;newnode->_next = cur;return iterator(newnode); // 匿名对象
}
// erase会返回删除位置的next节点的迭代器
iterator erase(iterator pos)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;prev->_next = next->_prev;next->_prev = prev->_next;return iterator(next); // 匿名对象
}
// erase和insert的复用
void push_back(const T& x) // 尾插
{insert(end(), x);
}
void push_front(const T& x) // 头插
{insert(begin(), x);
}
void pop_back() // 尾删
{erase(end());
}
void pop_front() // 头删
{erase(begin());
}

🔥总结

通过本次对STL中list和迭代器模拟实现的探索,我们深入了解了双向链表的基本结构、操作原理以及迭代器在遍历和访问链表元素中的重要作用。模拟实现的过程不仅让我们对STL中的list容器有了更深刻的理解,也锻炼了我们的编程能力和解决问题的能力

  • 在模拟实现的过程中,我们学习了如何设计并实现一个双向链表结构,包括节点的定义、链表的插入、删除和遍历等操作。同时,我们也掌握了迭代器的基本概念和实现方法,理解了如何通过迭代器来统一访问和遍历不同的容器类型。
  • 模拟实现STL中的list和迭代器是一个既有趣又富有挑战性的过程。它让我们更加深入地理解了数据结构和算法的基本原理,也为我们日后在实际项目中高效应用STL容器打下了坚实的基础。

最后,感谢大家的耐心阅读和学习。希望本次介绍能够为大家在STL学习和编程实践中提供一些帮助和启示。在未来的学习和工作中,让我们继续深入探索STL的奥秘,不断提升自己的编程能力和解决问题的能力
在这里插入图片描述

谢谢大家支持本篇到这里就结束了,祝大家天天开心!
在这里插入图片描述

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

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

相关文章

计算机的存储规则

计算机中的数据只有三类&#xff1a;Text 文本&#xff0c;Image 图片&#xff0c;Sound 声音。 文本包括数字、字母和汉字等。 视频是图片和声音的组合。 在计算机中&#xff0c;任何数据都是以二进制的形式来存储的。 数字的存储&#xff1a;转换为二进制进行存储。 字符…

[线程与网络] 网络编程与通信原理(六):深入理解应用层http与https协议(网络编程与通信原理完结)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

【Java面试】九、微服务篇-SpringCloud(上)

文章目录 1、SpringCloud五大组件2、服务注册和发现2.1 Eurake2.2 Eurake和Nacos的区别 3、Ribbon负载均衡3.1 策略3.2 自定义负载均衡策略 4、服务雪崩与熔断降级4.1 服务雪崩4.2 服务降级4.3 服务熔断 5、服务限流5.1 Nginx限流5.2 网关限流 6、微服务监控7、面试 1、SpringC…

qq号码采集软件

寅甲QQ号码采集软件, 一款采集QQ号、QQ邮件地址&#xff0c;采集QQ群成员、QQ好友的软件。可以按关键词采集&#xff0c;如可以按地区、年龄、血型、生日、职业等采集。采集速度非常快且操作很简单。

【TIPs】 Visual Stadio 2019 中本地误使用“git的重置 - 删除更改 -- hard”后,如何恢复?

环境&#xff1a; VS 2019Windows10本地版本管理&#xff08;非远程&#xff09; 前言&#xff1a; git 在Visual Stadio 2019中集成了git的版本管理&#xff0c;在本地用来做版本管理&#xff0c;本来比较好用。 不过有一次&#xff0c;由于拿最初始的版本的时候&#xf…

C++教程(003):运算符

3 运算符 作用&#xff1a;用于执行代码的运算 我们主要讲解以下运算符&#xff1a; 运算符类型作用算术运算符用于处理四则运算赋值运算符用于将表达式的值赋给变量比较运算符用于表达式的比较&#xff0c;并返回一个真值或假值逻辑运算符用于根据表达式的值返回真值或假值 …

swaggerHole:针对swaggerHub的公共API安全扫描工具

关于swaggerHole swaggerHole是一款针对swaggerHub的API安全扫描工具&#xff0c;该工具基于纯Python 3开发&#xff0c;可以帮助广大研究人员检索swaggerHub上公共API的相关敏感信息&#xff0c;整个任务过程均以自动化形式实现&#xff0c;且具备多线程特性和管道模式。 工具…

TCP攻击是怎么实现的,如何防御?

TCP&#xff08;Transmission Control Protocol&#xff09;是互联网协议族中的重要组成部分&#xff0c;用于在不可靠的网络上提供可靠的数据传输服务。然而&#xff0c;TCP协议的一些特性也使其成为攻击者的目标&#xff0c;尤其是DDoS&#xff08;Distributed Denial of Ser…

解决方案:昇腾aarch64服务器安装CUDA+GCC+CMake,编译安装Pytorch,华为昇腾HPC服务器深度学习环境安装全流程

目录 一、安装CUDA和cudnn1.1、下载CUDA驱动1.2、安装CUDA驱动1.3、配置环境变量1.4、安装cudnn1.5、安装magma-cuda 二、安装gcc编译器三、安装CMake四、安装NCCL五、编译安装Pytorch5.1、前提准备5.2、下载pytorch源码5.3、配置环境变量5.4、Pytorch编译安装5.5、测试Pytorch…

mysql中 redo日志(下)

大家好。上篇文章我们介绍了什么是redo日志以及redo日志的写入过程。建议没看过上篇文章的同学先看一下《mysql那些事儿》之 redo日志&#xff08;上&#xff09;&#xff0c;今天我们继续来说一说redo日志。 一、redo日志文件 1. redo日志刷盘时机 我们知道mtr运行过程中产…

这才是计科之 Onix XV6 源码分析(3、Unix-like系统的进程调度模块)

这才是计科之 Onix & XV6 源码分析&#xff08;3、Unix-like系统的进程调度模块&#xff09; 前言 前面已经分析了XV6的启动流程以及内存管理&#xff0c;接下来&#xff0c;我们探究进程调度的实现。与其说进程调度&#xff0c;我觉得可以顺应内存的虚拟化的叫法&#x…

禁用layui树形表格的多选框checkbox

1. 背景 在使用树形表格渲染数据时&#xff0c;需要对数据进行批量操作。相对于选中数据后&#xff0c;再做错误提示。直接把数据的多选框禁用掉更加直观。 2. 实现 DisabledTableCheckBox: () > {// 获取所有行 var tableElem $(".layui-table-fixed-l");var …

ALSA 用例配置

ALSA 用例配置。参考 ALSA 用例配置 来了解更详细信息。 ALSA 用例配置 用例配置文件使用 配置文件 语法来定义静态配置树。该树在运行时根据配置树中的条件和动态变量进行评估&#xff08;修改&#xff09;。使用 用例接口 API 解析结果并将其导出到应用程序。 配置目录和主…

【Git】如何不管本地文件,强制git pull

要在 Git 中强制执行 git pull 操作&#xff0c;忽略本地文件的更改&#xff0c;可以按照以下步骤操作&#xff1a; 保存当前工作状态&#xff1a;如果你有未提交的更改&#xff0c;可以使用 git stash 将这些更改存储起来。 git stash强制拉取最新代码&#xff1a;使用 git re…

java第二十一课 —— 快捷键,包,访问修饰符

IDEA 快捷键 删除行&#xff1a;Ctrl Y复制行&#xff1a;Ctrl D补全代码&#xff1a;Alt /添加取消注释&#xff1a;Ctrl /导入该行需要的类&#xff1a;Alt Enter快速格式化代码&#xff1a;Ctrl Shift L快速运行程序&#xff1a;Ctrl Shift F10生成构造器&#xf…

鸿蒙OS初识

学习官网&#xff1a;https://www.harmonyos.com/cn/develop 准备 注册&#xff0c;安装软件&#xff08;node:12, DevEco Studio&#xff09;&#xff1a; https://developer.harmonyos.com/cn/docs/documentation/doc-guides/software_install-0000001053582415#ZH-CN_TOP…

【Rust】——面向对象设计模式的实现

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

算法题--华为od机试考试(围棋的气、用连续自然数之和来表达整数、亲子游戏)

目录 围棋的气 题目描述 输入描述 示例1 输入 输出 解析 答案 用连续自然数之和来表达整数 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入 输出 解析 答案 亲子游戏 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入…

04 uboot 编译与调试

新手不需要详细掌握 uboot,只需要知道它是一个什么东西即可,工作中也只是改一些参数而已。 1、uboot 是什么 Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段 bootloader 程序。这段 bootloader 程序会先初始化 DDR 等外设,然后将 Linux 内…

李飞飞解读创业方向:「空间智能」

在AI领域&#xff0c;李飞飞教授一直是一个举足轻重的存在。她的研究和见解不仅推动了计算机视觉的发展&#xff0c;更对人工智能的未来方向产生了深远的影响。在最近的一次演讲中&#xff0c;李飞飞详细解读了她对于「空间智能」的见解。本文将对她的演讲内容进行详细解读&…