[STL]详解list模拟实现

news/2024/5/9 13:11:26

[STL]list模拟实现

文章目录

  • [STL]list模拟实现
    • 1. 整体结构总览
    • 2. 成员变量解析
    • 3. 默认成员函数
      • 构造函数1
      • 迭代器区间构造函数
      • 拷贝构造函数
      • 赋值运算符重载
      • 析构函数
    • 4. 迭代器及相关函数
      • 迭代器整体结构总览
      • 迭代器的模拟实现
      • begin函数和end函数
      • begin函数和end函数const版本
    • 5. 数据修改函数
      • push_back函数
      • insert函数
      • push_front函数
      • erase函数
      • pop_back函数
      • pop_front函数
      • clear函数
    • 6. 完整代码链接

1. 整体结构总览

template<class T>struct list_node //结点结构 --ListNode为类名,加上模板参数后为类型{list_node* _prev;list_node* _next;T _data;list_node(const T& val = T()) //结点的构造函数{_data = val;_prev = nullptr;_next = nullptr;}};template<class T, class Ref, class Ptr> //迭代器类实现struct __list_iterator{typedef list_node<T> node; //对结点重命名typedef __list_iterator<T, Ref, Ptr> self; //对迭代器重命名//...node* _node;};template<class T>class list{typedef list_node<T> node; //对结点重命名public:typedef __list_iterator<T, T&, T*> iterator; //普通迭代器typedef __list_iterator<T, const T&, const T*> const_iterator; //const迭代器public:void empty_init();list(); //默认构造函数template<class Iteartor>list(Iteartor begin, Iteartor end);void swap(list<T>& tmp);list(const list<T>& l);list<T>& operator=(list<T> l);~list();iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;void push_back(const T& x);void insert(iterator pos, const T x);void push_front(const T& x);iterator erase(iterator pos);void pop_back();void pop_front();void clear();private:node* _head;};

2. 成员变量解析

成员变量相关代码结构如下:

template<class T>
struct list_node //结点结构 --ListNode为类名,加上模板参数后为类型
{list_node* _prev; //指向上一个结点的指针list_node* _next; //指向下一个结点的指针T _val;	//结点存储的数据// ...
};
template<class T>
class list
{typedef list_node<T> node;// ...
private:node* _head; //指向哨兵位的头结点
};

image-20230723183036391

由于list是由双向循环链表实现的,因此只需要一个指向哨兵位的头结点的指针_head作成员变量,通过_head和指向上一个结点和下一个结点的指针能够很快的找到头结点和尾结点。

3. 默认成员函数

构造函数1

无参的默认构造函数,由于实现的双向循环链表,因此需要在创建链表时创建哨兵位的头结点,由于创建哨兵位的过程在后续实现中还需使用,因此将其封装成一个单独的empty_init函数。

void empty_init() //便于后续复用
{_head = new node;_head->_prev = _head;_head->_next = _head;
}list() //-const list也可以调用此构造函数,因为在初始化后才加的const属性
{empty_init();
}

迭代器区间构造函数

迭代器区间构造就是将传入的容器按其迭代器范围内的数据作为要构造的容器的数据进行构造,只需要将传入迭代器内的数据尾插即可。

template<class Iteartor>
list(Iteartor begin, Iteartor end)
{empty_init();while (begin != end){push_back(*begin);++begin;}
}

拷贝构造函数

拷贝构造函数的实现分为传统写法和现代写法。

传统写法是将要拷贝的list的数据依次进行尾插:

list(const list<T>& l)
{//传统写法empty_init();const_iterator it = l.begin();while (it != l.end()){push_back(*it);it++;}
}

现代写法是创建一个临时的list进行数据拷贝,然后将临时的list内的结点交换过来:

void swap(list<T>& tmp)
{std::swap(_head, tmp._head);
}list(const list<T>& l)
{//现代写法empty_init();  // -- 不申请结点会因为tmp变量析构野指针而报错list<T> tmp(l.begin().l.end());swap(tmp);
}

赋值运算符重载

赋值运算符重载的实现利用参数会拷贝构造的特性,然后交换参数的数据。

list<T>& operator=(list<T> l)
{swap(l);return *this;
}

析构函数

析构函数的实现可以复用能够将除了哨兵位结点外的所有结点删除的clear函数,然后删除哨兵位结点。(clear函数的实现在文末。)

qxm::list<T>::~list()
{clear();delete _head;_head = nullptr;
}

4. 迭代器及相关函数

迭代器整体结构总览

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* n){};//构造函数__list_iterator(const __list_iterator<T, T&, T*>& it){}; //普通迭代器构造const迭代器__list_iterator(const __list_iterator<T, const T&, const T*>& it){};//cosnt迭代器构造普通迭代器self& operator++(){};//前置++self operator++(int){};//后置++self& operator--(){};//前置--self operator--(int){};//后置--Ref operator*() {};//*运算符重载Ptr operator->() {};//->运算符重载bool operator!=(const self& s){};//!=运算符重载bool operator==(const self& s){};//==运算符重载};

迭代器的模拟实现

由于迭代器需要的功能很多,因此需要给迭代器单独封装一个类,成员变量是结点指针。迭代器成员变量有关的代码如下:

template<class T>struct __list_iterator{typedef list_node<T> node; //对结点重命名// ...node* _node; //指向结点的指针};

迭代器构造函数

迭代器只有一个指向结点的指针变量,迭代器构造函数只要将其初始化即可。

__list_iterator(node* n):_node(n){}

迭代器构造函数

为了实现普通迭代器和const迭代器的转换需要实现如下构造函数:

__list_iterator(const __list_iterator<T, T&, T*>& it) //普通迭代器构造const迭代器:_node(it._node){}__list_iterator(const __list_iterator<T, const T&, const T*>& it)//cosnt迭代器构造普通迭代器:_node(it._node){}

迭代器前置++运算符重载

迭代器实现++操作只需要将指针指向下一个结点即可。

template<class T>    
self& operator++()//前置++
{_node = _node->_next;return _node;
}

迭代器后置++运算符重载

实现后置++和前置++相比只需要将++前的迭代器保存并且返回即可。

self operator++(int)//后置++
{self tmp(_node);_node = _node->_next;return tmp;
}

迭代器前置–运算符重载

迭代器实现–操作只需要将指针指向上一个结点即可。

self& operator--()//前置--
{_node = _node->_prev;return *this;
}

迭代器后置–运算符重载

实现后置–和前置–相比只需要将–前的迭代器保存并且返回即可。

self operator--(int)//后置--
{self tmp(_node);_node = _node->_prev;return tmp;
}

迭代器*运算符重载

迭代器进行*操作是要获取迭代器指向的数据,因此只需要将结点指向的数据返回即可。

T& operator*()
{return _node->_val;
}

迭代器->运算符重载

迭代器进行->操作是因为list存储的是自定义数据类型,->运算符的重载只需要返回数据的地址即可。

T* operator->()
{return &_node->_data;
}

为了理解->运算符重载的实现,我们看下面的例子:

struct AA
{AA(int a1 = 1, int a2 = 2):_a1(a1),_a2(a2){}int _a1;int _a2;
};void test_list2()
{qxm::list<AA> l; //qxm作用域是模拟实现时设置的命名空间l.push_back(AA(1, 1));l.push_back(AA(2, 2));l.push_back(AA(3, 3));for (qxm::list<AA>::iterator it = l.begin(); it != l.end(); it++){cout << it->_a1 << ":" << it->_a2 << endl;}
}

在这个场景中如果调用迭代器的->会返回AA类型的指针,it->_a1相当于是&AA_a1将数据地址和变量写到一块,应该是访问不到数据的错误代码,但是编译器会自动做优化,此时一个->运算符当两个->使用,也就是说本来需要(it.operator->())->_a1访问数据的,但是编译器把其中一个->优化掉了,因此现在只要it->_a1就可以访问成功了。

迭代器!=运算符重载

迭代器的!=是指向的数据不同,因此只需要判断迭代器内的指针是否相同。

bool operator!=(const self& it)
{return this->_node != it->_node;
}

迭代器!=运算符重载

迭代器的==是指向的数据相同,因此只需要判断迭代器内的指针是否相同。

bool operator==(const self& s)
{return _node == s._node;
}

const迭代器实现

const迭代器和普通迭代器的实现只有在*运算符重载和->运算符的重载的返回值上有所不同。

const迭代器的*运算符重载函数返回的是const的数据,这样const迭代器就不能修改数据了。

const T& operator*() 
{return _node->_data;
}

const迭代器的->运算符重载函数返回的是const的指针,这样const迭代器就不能修改数据了。

const T* operator->()
{return &_node->_data;
}

迭代器模拟实现整体代码:

template<class T, class Ref, class Ptr> // -- Ref/Ptr控制是普通迭代器还是const迭代器struct __list_iterator{typedef list_node<T> node; //对结点重命名typedef __list_iterator<T, Ref, Ptr> self; //对迭代器重命名node* _node;__list_iterator(node* n):_node(n){}__list_iterator(const __list_iterator<T, T&, T*>& it):_node(it._node){}__list_iterator(const __list_iterator<T, const T&, const T*>& it):_node(it._node){}self& operator++()//前置++{_node = _node->_next;return *this;}self operator++(int)//后置++{self tmp(_node);_node = _node->_next;return tmp;}self& operator--()//前置--{_node = _node->_prev;return *this;}self operator--(int)//后置--{self tmp(_node);_node = _node->_prev;return tmp;}Ref operator*() //传引用返回可以修改结点内的数据{return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const self& s) //!=运算符重载{return _node != s._node;}bool operator==(const self& s) //==运算符重载{return _node == s._node;}};

注意: 模板参数Ref控制的是*运算符重载的返回值类型,模板参数Ptr控制的是->运算符重载的返回值类型,进而控制了迭代器是普通迭代器还是const迭代器。

begin函数和end函数

注: 文中此位置往下是迭代器相关函数实现,实现在list类内。

begin函数:

begin函数只需要返回拥有有效数据的头结点即可。

iterator begin()
{return iterator(_head->_next);
}

end函数:

end函数只需要返回哨兵位就可,因为哨兵位没有有效数据。

iterator end()
{return iterator(_head);
}

begin函数和end函数const版本

const版本begin函数:

begin函数只需要返回拥有有效数据的头结点即可。

const_iterator begin()const
{return const_iterator(_head->_next);
}

const版本end函数:

end函数只需要返回哨兵位就可,因为哨兵位没有有效数据。

const_iterator end()const
{return const_iterator(_head);
}

5. 数据修改函数

push_back函数

push_back函数为尾插函数,尾插示意图如下:

image-20230723165738795

实现尾插函数时创建一个临时变量tail,记录插入数据前的尾结点,方便进行指针指向的改动,避免因为指针指向改动而找不到正确的结点。

void push_back(const T& val)
{node* tail = _head->_prev; //记录插入数据前的尾部结点node* newnode = new node(x);tail->_next = newnode;//指针改变的顺序不影响结果newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

insert函数

insert函数的功能是通过迭代器,在迭代器指向的结点前插入结点,insert函数的实现只需要将传入的迭代器的前一个结点和迭代器指向的结点记录,然后将要插入的新节点进行链接。

insert函数插入结点示意图:

image-20230727131020076

void insert(iterator pos, const T x)
{node* cur = pos._node;node* prev = cur->_prev;node* newnode = new node(x);prev->_next = newnode;cur->_prev = newnode;newnode->_prev = prev;newnode->_next = cur;
}

说明: insert函数不会使迭代器失效,因为迭代器指向的结点不会随着插入而改变。

有了insert函数后,push_back函数可以改写为复用insert函数的版本:

void push_back(const T& x)
{insert(end(), x);
}

end函数返回的是有_head封装的迭代器,指向哨兵位,哨兵位的前一个结点就是尾结点,因此复用insert函数和end函数能实现尾插。

push_front函数

push_front函数的功能是头插结点,有了insert函数实现头插只需要在insert函数中传入头结点就可以实现。

void push_front(const T& x)
{insert(begin(), x);
}

erase函数

erase函数的功能是删除指定结点,并返回在删除之前删除结点的下一个结点,只需要将要删除的前一个结点和后一个结点记录,进行链接即可,但要注意不能删除哨兵位结点。

iterator erase(iterator pos)
{assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);
}

pop_back函数

pop_back函数的功能是尾删,只需要复用erase函数和end函数即可,end函数返回的是哨兵位的迭代器,–得到的是尾结点的迭代器。

void pop_back()
{erase(--end());
}

pop_front函数

pop_front函数的功能是尾删,只需要复用erase函数和begin函数即可,begin函数返回的头结点的迭代器。

void pop_front()
{erase(begin());
}

clear函数

clear函数的功能是将除了哨兵位结点外的所有结点都删除,只需要复用erase函数循环删除结点。(erase函数的实现需上翻本文)

void clear()
{iterator it = begin();while (it != end){it = erase(it); //--erase后需要重新对迭代器赋值,不然迭代器会失效。//erase(it++);  --后置++会返回++前的值,因此迭代器不会失效}
}

6. 完整代码链接

STL/List/List/List.h · 钱雪明/日常代码 - 码云 - 开源中国 (gitee.com)


http://www.mrgr.cn/p/28634465

相关文章

心法利器[93] | 谈校招:技术面

心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会&#xff0c;与大家一起成长。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。 2022年新一版的文章合集已经发布&#xff0c;累计已经60w字了&#xff0c;获取方式看这里&…

eda、gnm、anm究竟是个啥?

安装prody pip install prody -i https://pypi.tuna.tsinghua.edu.cn/simpleeda、anm、gnm eda(essential dynamics analysis) 另一个名字PCA(Principal Component Analysis) 或 NMA(Normal Mode Analysis)。 eda分析可以帮助人们理解生物大分子基本的运动模式和构象变化。…

【Linux】自动化构建工具-make/Makefile详解

前言 大家好吖&#xff0c;欢迎来到 YY 滴 Linux系列 &#xff0c;热烈欢迎&#xff01;本章主要内容面向接触过Linux的老铁&#xff0c;主要内容含 欢迎订阅 YY 滴Linux专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 订阅专栏阅读&#xff1a;YY的《…

Chat GPT是什么,初学者怎么使用Chat GPT,需要注意些什么

目录 Chat GPT是什么 初学者怎么使用Chat GPT 使用Chat GPT需要注意什么 一些简单的prompt示例 Chat GPT是什么 Chat GPT是由OpenAI开发的一种大型语言模型&#xff0c;它基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构。GPT是一种基于深度学习的…

模电基础知识学习笔记

文章目录&#xff1a; 一&#xff1a;基本元器件介绍 1.二极管 1.1 普通二极管特性测试 1.2 稳压二极管测试 1.3 整流二极管 1.4 开关二极管 2.电容 3.三极管(电流控制) 3.1 介绍 3.2 类型&#xff08;PNP、NPN&#xff09; 3.3 三种工作状态:放大状态、截止状态…

《JeecgBoot系列》JeecgBoot(ant-design-vue)实现筛选框:支持下拉搜索+下拉多选+表字典(支持条件查询)功能

JeecgBoot(ant-design-vue)实现筛选框&#xff1a;支持下拉搜索下拉多选表字典(支持条件查询)功能 JSearchMultiSelectTag.vue源文件 一、需求介绍 在使用JeectBoot(ant-design-vue)设计表单时&#xff0c;需要实现下拉搜索下拉多选表字典(支持条件查询)。 但是系统目前有两…

Vue3 Vite electron 开发桌面程序

Electron是一个跨平台的桌面应用程序开发框架&#xff0c;它允许开发人员使用Web技术&#xff08;如HTML、CSS和JavaScript&#xff09;构建桌面应用程序&#xff0c;这些应用程序可以在Windows、macOS和Linux等操作系统上运行。 Electron的核心是Chromium浏览器内核和Node.js…

机器学习实战:Python基于EM期望最大化进行参数估计(十五)

文章目录 1. 前言1.1 EM的介绍1.2 EM的应用场景 2. 高斯混合模型估计2.1 导入函数2.2 创建数据2.3 初始化2.4 Expectation Step2.5 Maximization step2.6 循环迭代可视化 3. 多维情况4. 讨论 1. 前言 1.1 EM的介绍 &#xff08;Expectation-Maximization&#xff0c;EM&#…

程序设计 算法基础

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

【雕爷学编程】MicroPython动手做(02)——尝试搭建K210开发板的IDE环境

知识点&#xff1a;简单了解K210芯片 2018年9月6日,嘉楠科技推出自主设计研发的全球首款基于RISC-V的量产商用边缘智能计算芯片勘智K210。该芯片依托于完全自主研发的AI神经网络加速器KPU,具备自主IP、视听兼具与可编程能力三大特点,能够充分适配多个业务场景的需求。作为嘉楠科…

QGIS3.28的二次开发一:编译工程

环境&#xff1a;VS2019OSGeo4WCMake_3.26Cygwin64QGIS_3.28 注意&#xff1a;一定要按照步骤顺序来&#xff01; 一、配置环境 &#xff08;一&#xff09;VS2019 VS2019下载链接https://my.visualstudio.com/Downloads?qvisual%20studio%202019&wt.mc_ido~msft~vsco…

WAIC2023:图像内容安全黑科技助力可信AI发展

目录 0 写在前面1 AI图像篡改检测2 生成式图像鉴别2.1 主干特征提取通道2.2 注意力模块2.3 纹理增强模块 3 OCR对抗攻击4 助力可信AI向善发展总结 0 写在前面 2023世界人工智能大会(WAIC)已圆满结束&#xff0c;恰逢全球大模型和生成式人工智能蓬勃兴起之时&#xff0c;今年参…

【沐风老师】归纳总结50个3dMax常用的方法和技巧

​在日常工作中&#xff0c;我们总能总结出一些方法和技巧&#xff0c;用以在今后的工作中提高工作效率。下面是50个3dMax最常见的方法和技巧&#xff0c;这些方法和技巧已经成为众多3dMax用户日常工作流程中不可或缺的一部分。 1.使用“重命名对象”工具可以同时重命名多个对象…

【Chat GPT】用 ChatGPT 运行 Python

前言 ChatGPT 是一个基于 GPT-2 模型的人工智能聊天机器人&#xff0c;它可以进行智能对话&#xff0c;同时还支持 Python 编程语言的运行&#xff0c;可以通过 API 接口进行调用。本文将介绍如何使用 ChatGPT 运行 Python 代码&#xff0c;并提供一个实际代码案例。 ChatGPT …

golang pprof

pprof是一个用于分析数据的可视化和分析工具&#xff0c;由谷歌公司的开发团队使用go语言编写成的。一般用于对golang资源占用进行分析。不是原创&#xff0c;参考&#xff1a;https://juejin.cn/post/7122473470424219656 1. 通过页面查看golang运行情况 访问 http://127.0.0…

PostgreSql 锁

一、概述 在 PostgreSQL 事务中提到&#xff0c;多个用户访问相同数据时可能出现脏读&#xff0c;不可重复度&#xff0c;幻读&#xff0c;更新丢失的问题&#xff0c;为解决这些问题&#xff0c;定义了不同的隔离级别&#xff0c;而隔离级别的具体实现&#xff0c;依靠的就是数…

Kubernetes 使用 helm 部署 NFS Provisioner

文章目录 1. 介绍2. 预备条件3. 部署 nfs4. 部署 NFS subdir external provisioner4.1 集群配置 containerd 代理4.2 配置代理堡垒机通过 kubeconfig 部署 部署 MinIO添加仓库修改可配置项 访问nodepotingress 1. 介绍 NFS subdir external provisioner 使用现有且已配置的NFS…

在外远程NAS群晖Drive - 群晖Drive挂载电脑磁盘同步备份【无需公网IP】

文章目录 前言1.群晖Synology Drive套件的安装1.1 安装Synology Drive套件1.2 设置Synology Drive套件1.3 局域网内电脑测试和使用 2.使用cpolar远程访问内网Synology Drive2.1 Cpolar云端设置2.2 Cpolar本地设置2.3 测试和使用 3. 结语 前言 群晖作为专业的数据存储中心&…

tinkerCAD案例:24. Ruler - Measuring Lengths 标尺 -量勺

tinkerCAD案例&#xff1a;24. Ruler - Measuring Lengths 标尺 - 测量长度 Project Overview: 项目概况&#xff1a; A machine shop, where any idea can become a reality, can cost millions and million of dollars. Still, the most important tool in the shop is the…