[STL]详解list模拟实现

[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.rhkb.cn/news/73708.html

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

相关文章

C语言指针详解

C语言指针详解 字符指针1.如何定义2.类型和指向的内容3.代码例子 指针数组1.如何定义2.类型和内容 数组指针1.如何定义2.类型和指向类型3.数组名vs&数组名数组指针运用 数组参数&指针参数一维数组传参二维数组传参一级指针传参二级指针传参 函数指针1.如何定义2.类型和…

【前端知识】React 基础巩固(三十九)——React-Router的基本使用

React 基础巩固(三十九)——React-Router的基本使用 一、Router的基本使用 Router中包含了对路径改变的监听&#xff0c;并且会将相应的路径传递给子组件。 Router包括两个API&#xff1a; BrowserRouter使用history模式 HashRouter使用hash模式&#xff08;路径后面带有#号…

APP自动化测试-Python+Appium+Pytest+Allure框架实战封装(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 pytest只是单独的…

无人驾驶实战-第五课(动态环境感知与3D检测算法)

激光雷达的分类&#xff1a; 机械式Lidar&#xff1a;TOF、N个独立激光单元、旋转产生360度视场 MEMS式Lidar&#xff1a;不旋转 激光雷达的输出是点云&#xff0c;点云数据特点&#xff1a; 简单&#xff1a;x y z i &#xff08;i为信号强度&#xff09; 稀疏&#xff1a;7%&…

【工具】自动搜索Research网站的学术会议排名

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] Research.com是一个可以搜索学术会议网站的影响因子的网站。 好用是好用&#xff0c;但有一个缺点&#xff1a;得手动选择类目。有这么多类目&#xff0c;一个个手动选也太累了。 所以做了一个自动搜索的小工具&a…

HTTP杂谈之Referer和Origin请求头再探

一 关于Referer和Origin的汇总 1) 知识是凌乱的,各位看官看个热闹即可2) 内容不断更新1、理解有盲区,需要及时纠正2、内容交叉有重复,需要适当删减3、扩展视野3) 以下内容都与Referer和Origin请求头有关联 nginx防盗链 HTTP杂谈之Referrer-Policy响应头 iframe标签referre…

新手入门Jenkins自动化部署入门详细教程

1. 背景 在实际开发中&#xff0c;我们经常要一边开发一边测试&#xff0c;当然这里说的测试并不是程序员对自己代码的单元测试&#xff0c;而是同组程序员将代码提交后&#xff0c;由测试人员测试&#xff1b; 或者前后端分离后&#xff0c;经常会修改接口&#xff0c;然后重新…

vue element el-upload附件上传、在线预览、下载当前预览文件

上传 在线预览&#xff08;iframe&#xff09;&#xff1a; payload&#xff1a; response&#xff1a; 全部代码&#xff1a; <template><div><el-table :data"tableData" border style"width: 100%"><el-table-column prop"d…

.Net6 Core Web API 配置 log4net + MySQL

目录 一、导入NuGet 包 二、添加配置文件 log4net.config 三、创建MySQL表格 四、Program全局配置 五、帮助类编写 六、效果展示 小编没有使用依赖注入的方式。 一、导入NuGet 包 ---- log4net 基础包 ---- Microsoft.Extensions.Logging.Log4Net…

天线辐射机制

电磁场如何从源中产生并最终脱离天线辐射到自由空间中去的呢&#xff1f;让我们首先来研究一下一些基本的辐射源。 1、单线Single Wire 导线是一种电荷运动产生电流特性的材料&#xff0c;假设用qv&#xff08;库仑/m3&#xff09;表示的一个电体积电荷密度均匀分布在一个横截…

ansible控制主机和受控主机之间免密及提权案例

目录 案例描述 环境准备 案例一--免密远程控制主机 效果展示&#xff1a; 解决方案 1.添加主机 2.通过ssh-key生成密钥对 3.生成ssh-copy-id 4.验证 案例二-----免密普通用户提权 效果展示 解决方案 1.使用普通用户&#xff0c;与案例一 一样&#xff0c;进行发送密钥…

pytest 自定义HOOK函数

除了系统提过的HOOK函数外&#xff0c;也可以通过自定义HOOK的方式实现想要的功能。 首先创建一个py文件&#xff0c;里面定义自己的HOOK函数&#xff0c;主要pytest里面的hook函数必须以pytest开头。 #myhook.pydef pytest_myhook(user):"""自定义HOOK函数&q…

【JavaEE初阶】Servlet(四) Cookie Session

文章目录 1. Cookie && Session1.1 Cookie && Session1.2 Servlet会话管理操作 1. Cookie && Session 1.1 Cookie && Session Cookie是什么? Cookie是浏览器提供的持久化存储数据的机制.Cookie从哪里来? Cookie从服务器返回给浏览器. 服务…

中小企业如何做好MES管理系统实施建设

中小企业在生产制造领域面临着诸多挑战&#xff0c;包括提升产品竞争力、规范生产制造等。为了应对这些挑战&#xff0c;越来越多的中小企业开始实施MES生产管理系统。然而&#xff0c;由于企业规模小、资源实力不足等原因&#xff0c;很多企业在实施MES管理系统时存在一定的困…

opencv rtsp 硬件解码

讨论使用opencv的reader 硬件解码的方案有太多种&#xff0c;如果使用ffmpeg硬件解码是最方便的&#xff0c;不方便的是把解码过后的GPU 拉到 CPU 上&#xff0c;再使用opencv的Mat 从cpu 上上载到gpu上&#xff0c;是不是多了两个过程&#xff0c;应该是直接从GPU mat 直接去…

腾讯测试大佬分享4个关于 Python 函数(方法)的冷知识

关于参数标识 不知道大家在工作中有没有遇到一种情况&#xff0c;你的同事 A 写了一个方法给你调用&#xff0c;然后你调用时不知道该传什么参数&#xff0c;然后这个同事 A 还很 cao dan 的居然不加班&#xff01;你一脸茫然的看着这个方法&#xff0c;当你尝试传进去一个 ab…

使用pg_prewarm缓存PostgreSQL数据库表

pg_prewarm pg_prewarm 直接利用系统缓存的代码,对操作系统发出异步prefetch请求&#xff0c;在应用中&#xff0c;尤其在OLAP的情况下&#xff0c;对于大表的分析等等是非常耗费查询的时间的&#xff0c;而即使我们使用select table的方式&#xff0c;这张表也并不可能将所有…

后端整理(MySql)

1 事务 1.1 事务ACID原则 原子性&#xff08;Atomicity&#xff09; 事务的原子性指的是事务的操作&#xff0c;要么全部成功&#xff0c;要么全部失败回滚 一致性&#xff08;Consistency&#xff09; 事务的一致性是指事务必须使数据库从一个一致状态转变成另一个一致性…

hugging face下载数据集

开始直接执行这个&#xff0c;下载下来的图片打不开 git clone https://huggingface.co/datasets/diffusers/dog-example 解决办法&#xff1a; 安装git lfs 1. curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash 2. sudo apt…

会议OA系统会议管理模块开发思路(layui搭建)

目录 一.为什么要进行开发 1.开发目的 2.项目流程 A.发起会议请求过程 1.首先实现我们的多选下拉框功能&#xff01; 2.时间组件功能&#xff0c;并且提交我们新增加的会议内容 3.在进行发起会议编码时遇到的问题&#xff0c;BUG 3.1.有点时候js访问不到路径 3.2在增加…