list的模拟实现

news2024/11/25 10:25:56
第一步:看源代码
类的框架:
成员函数:
 
基本可以确定list是一个带头双向循环链表,end()的迭代器指向头节点,begin()的迭代器指向头结点的下一个节
list的迭代器:(稍显复杂)
库中的迭代器成员函数只有一个链表节点指针。对链表节点指针直接解引用只能拿到节点空间,没办法拿到节点中存储的date,但是可以通过自定义类型封装间接实现拿到date。节点的指针++,也不会到达下一个地址。自定义类型的运算符重载可以控制行为。
下面是具体的实现:
list.h中
#pragma once
#include<iostream>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace bit
{
        template<class T>
        struct _list_node
        {
               _list_node(const T& x = T()) //这里的缺省不能直接给0,因为T的类型可能为string,vector
               {
                       _date = x;
                       _next = nullptr;
                       _prev = nullptr;
               }
               _list_node<T>* _next;
               _list_node<T>* _prev;
               T _date;
        };
         //list的迭代器是一个难点
        //template<class T>最开始的框架
        //typedef _list_iterator<T, T&, T*> iterator;
        //typedef _list_iterator<T, const T&, const T*> const_iterator;
        //当调用begin()的类型不同时匹配的begin()函数不同,决定了是返回const_iterator类型还是iterator类型,
        //由此决定ref具体是什么类型,ptr具体是什么类型。
        template<class T, class ref, class ptr>
         //源代码中模板参数有三个template<class T, class Ref, class Ptr>这个和const迭代器有关。
        struct _list_iterator
        {
               typedef _list_node<T> Node;
               typedef  _list_iterator<T,ref,ptr> Self;
               Node* _node; //迭代器器的本质是一个节点指针,和_head一样。
               _list_iterator(Node* it) //如果不想被隐式类型转换可以这样声明:explicit _list_iterator(Node* it)
                       :_node(it)
               {}
                //需要写析构函数吗?
               //不需要,_node指向的是节点空间,不能说使用一次迭代器就释放节点空间,所以使用默认生成的析构函数就可以
               //默认生成的析构函数不会对指针类型进行处理
               //需要写拷贝构造和赋值重载吗?
               //不需要,指针为内置类型,默认生成的浅拷贝就可以。
               ref operator*()
                //开始的框架:T& operator*()
               {
                       return _node->_date; //_date是节点中的数据,节点是new出来的,出作用域不会消失,可以引用返回。
               }
                 //开始的框架:T* operator->()
               ptr operator->() // ptr operator-> ()是干什么的?
                                        //当T的实参为类时会用到。在下面test.cpp中test2有示范
               {
                       return &(_node->_date);
               }
               bool operator==(const Self& pos)
               {
                       return _node == pos._node;
               }
               bool operator!=(const Self& pos) //pos是_list_iterator<T>类型
               {
                       return _node != pos._node; //这里必须用_node进行比较,如果用迭代器比较,会再进行!=重载,形成死循环。
                       //this是指向迭代器的指针,是_list_iterator<T>*类型
               }
               Self& operator++()
               {
                       _node = _node->_next;
                       return *this; //_node是_list_node<T>*类型,不能转换为_list_iterator<T>&类型
                       //如果没有&,就可以,因为支持单参数隐式类型转换。
               }
               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;
               }
        };
        template<class T>
        class list
        {
               typedef _list_node<T> Node; //重复typedef是因为上一个typedef只有在所在的{}内才有效
        public:
                //开始的框架:typedef _list_iterator<T> iterator;
               typedef _list_iterator<T,T&,T*> iterator;
               typedef _list_iterator<T, const T&, const T*> const_iterator; //类模板给不同的参数,会变成不同的类型
                //关于为什么const迭代器中T的前面不用加const:加上const,迭代器中的T就表示const T,_list_node<T>就表示
               //_list_node<const T>list存储的数据变成不可修改的了,而Node中的内容是可以修改的。类型不匹配,
               //在类模板中不同类型之间是不能赋值的(有const和没有const是不同类型),会报错。
               //迭代器封装以后,在使用时(不同容器的迭代器)看起来是一样的。如果没有被public修饰,就无法在list外使用。
               //T是存储的数据类型,T&/const T&是返回存储的数据,加引用是为了减少拷贝,
               //有无const取决于可不可以通过迭代器修改数据。
//关于迭代器使用的图示:
               list()
               {
                       _head = new Node;
                       _head->_next = _head;
                       _head->_prev = _head;
               }
                //传统写法:
               //list(const list<T>& it)//it2(it1)
               //{
               //      _head = new Node;
               //      _head->_next = _head;
               //      _head->_prev = _head;
               //      for (auto e : it)
               //      {
               //             push_back(e);
               //      }
               //}
               //现代写法:
               void empty_init() //库中也提供了这个函数
               {
                       _head = new Node;
                       _head->_next = _head;
                       _head->_prev = _head;
               }
               template <class InputIterator>
               list(InputIterator first, InputIterator last)
               {
                       empty_init();
                       while (first != last)
                       {
                              push_back(*first);
                              first++;
                       }
               }
               void swap(list<T>& it) //it的内容要修改,不能加const
               {
                       std::swap(_head, it._head);
                        //依据实现size()的方式,可能还有交换size,这里没有实现size(),所以没交换。
               }
               list(const list<T>& it)
               {
                       empty_init(); //不能用未初始化的list和tmp交换。
                       list<T> tmp(it.begin(), it.end());
                       swap(tmp);
               }
               list<T>& operator=(list<T> it) //it2 = it1
               {
                       swap(it); //传值拷贝,it中已经有需要的数据了
                       return *this;
               }
               ~list()
               {
                       clear();
                       delete _head;
                       _head = nullptr;
               }
               void clear()
               {
                       iterator it = begin();
                       while (it != end())
                       {
                              it = erase(it);
                       }
               }
               void push_back(const T& x)
               {
                        ///*Node* tmp = new Node(x);
                       //_head->_prev->_next = tmp;
                       //tmp->_prev = _head->_prev;
                       //tmp->_next = _head;
                       //_head->_prev = tmp;*/
                       //Node* tmp = new Node(x);
                       //Node* tail = _head->_prev;//保存尾节点的地址,就可以不用按顺序完成链表的连接了
                       //tail->_next = tmp;
                       //tmp->_prev = tail;
                       //tmp->_next = _head;
                       //_head->_prev = tmp;
                       insert(end(), x);
               }
               void push_front(const T& x)
               {
                       insert(begin(), x);
               }
               void pop_back()
               {
                       erase(--end()); //这里可以用--end(),原因不明
               }
               void pop_front()
               {
                       erase(begin());
               }
               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);
               }
               iterator insert(iterator pos, const T& val) //在pos位置之前插入数据
               {
                       Node* newnode = new Node(val);
                       Node* cur = pos._node;
                       cur->_prev->_next = newnode;
                       newnode->_prev = cur->_prev;
                       newnode->_next = cur;
                       cur->_prev = newnode;
                       return iterator(newnode); //insert的返回值很少用,和库里尽量保持一致还是加上了。
               }
               iterator erase(iterator pos)
               {
                       assert(pos != end());
                       Node* cur = pos._node;
                       Node* prev = cur->_prev;
                       Node* next = cur->_next;
                       prev->_next = next;
                       next->_prev = prev;
                       delete cur;
                       return iterator(next); //返回值解决迭代器失效的问题
               }
        private:
               Node* _head;
        };
}
test.cpp中
#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"
#include<list>
void test_list1()
{
        bit::list<int> l;
        l.push_back(1);
        l.push_back(2);
        l.push_back(3);
        l.push_back(4);
        l.push_back(5);
        bit::list<int>::iterator it = l.begin();
        while (it != l.end())
        {
               cout << *it << " ";
               ++it;
        }
}
struct AA
{
        AA(int a = 0, int b = 0)
        {
               a1 = a;
               a2 = b;
        }
        int a1;
        int a2;
};
cout没有AA的重载的解决方案一:加上重载
//ostream& operator<<(ostream& out, AA& aa)//函数中不能定义函数
//{
//      cout << aa.a1 << " " << aa.a2 << " ";
//      return out;
//}
void test_list2()
{
        bit::list<AA> l;
        l.push_back(AA(1, 1));
        l.push_back(AA(2, 2));
        l.push_back(AA(3, 3));
        l.push_back(AA(4, 4));
        l.push_back(AA(5, 5));
        bit::list<AA>::iterator it = l.begin();
        /*while (it != l.end())
        {
               cout << *it << " ";//这里的it是迭代器,解引用后拿到的是_date,_date的类型是T,即AA。cout没有AA的重载
               ++it;
        }*/
        while (it != l.end())
        {
                cout没有AA的重载的解决方案二:
               //cout << (*it).a1 << " " << (*it).a2 << " ";
               //it是迭代器,迭代器模仿的是指针,指针有通过->直接访问数据的功能,而it是一个类
               //cout << it->a1 << " " << it->a2 << " ";//原本应该这样使用的:it._node->_date.a1
               //库中支持迭代器直接访问数据,库中函数:T* operator->() const { return &(operator*()); }
               //it调用operator*(),*it会拿到其中的数据,即拿到_next->date,为operator*()的返回值;
               //it调用operator->()返回&(_node->date),即AA*,it->a1即AA* a1,实际上编译器为了可读性对这里进行简化。
               //it->a1实际上应该是it->->a1。it->即:it.operator->()返回AA*,AA*->a1,读取到数据
               cout << it->a1 << " " << it->a2 << " ";
               ++it;
        }
}
void print_list(const bit::list<int> tmp)
{
        bit::list<int>::const_iterator it = tmp.begin();
         //如果只是在begin()后加上const的修饰,只能编译通过,不能保证数据不可修改
        //要能做到不可修改,需要给 * 和 -> 的重载返回值加上const,但是加上const同时也意味着非const修饰的迭代器不可修改
        // 一般情况下,会选择再写一个_list_const_iterator类型的类,但是和_list_iterator类的逻辑高度重合,维护起来不方便
        // 下面跳转到list类部分,见证大佬为何是大佬。(每个注释有开始的框架的上下行都是大佬智慧的结晶)
        // 还不理解的翻到末尾,有图解说明
        //标准库中begin()可以自动识别迭代器类型,但是不能将const修饰的迭代器赋值给非const修饰的迭代器。
        //自定义类型被const修饰和不被const修饰是两个无关类型,
        //不能将被const修饰的类型赋值给不被const修饰的类型,标准库中也一样
        //比如说自定义类型的迭代器,不能说用const修饰了,就能支持const类型运算,还要有const类型对应运算的重载,
        //就算不被const修饰类型已经有对应的运算重载也不行。
        while (it != tmp.end())
        {
               cout << *it << " ";
               ++it;
        }
}
void test_list3()
{
        bit::list<int> l;
        l.push_back(1);
        l.push_back(2);
        l.push_back(3);
        l.push_back(4);
        l.push_back(5);
        print_list(l);
}
void test_list4()
{
        bit::list<int> l;
        l.push_back(1);
        l.push_back(2);
        l.push_back(3);
        l.push_back(4);
        l.push_back(5);
        l.insert(l.begin(), 10);
        l.push_front(20);
        l.push_front(30);
        l.push_front(40);
        l.erase(l.begin());
        l.pop_back();
        l.pop_front();
        bit::list<int>::iterator it = l.begin();
        while (it != l.end())
        {
               cout << *it << " ";
               ++it;
        }
}
void test_list5() //关于list的insert的迭代器失效问题
{
        bit::list<int> l;
        l.push_back(1);
        l.push_back(2);
        l.push_back(2);
        l.push_back(3);
        l.push_back(4);
        l.push_back(5);
        l.push_back(6);
        bit::list<int>::iterator it = l.begin();
        while (it != l.end())
        {
               if (*it % 2 == 0)
               {
                       l.insert(it, *it * 10);
               }
               it++;
        }
        for (auto it : l)
        {
               cout << it << " ";
        }
        cout << endl; //运行发现没什么问题
        //原因在于:list空间不连续,insert插入数据不需要挪动数据,所以不存在野指针问题,
        //insert插入数据,插入在指向的节点之前,意义也没有发生变化。
}
void test_list6() //关于list的erase的迭代器失效问题
{
        bit::list<int> l;
        l.push_back(1);
        l.push_back(2);
        l.push_back(2);
        l.push_back(3);
        l.push_back(4);
        l.push_back(5);
        l.push_back(6);
        bit::list<int>::iterator it = l.begin();
         /*while (it != l.end())
        {
               if (*it % 2 == 0)
               {
                       l.erase(it);//这样写是必然崩溃的,it都被释放了,it迭代器自然失效
               }
               it++;
        }*/
        while (it != l.end())
        {
               if (*it % 2 == 0)
               {
                       it = l.erase(it);
               }
               else
               {
                       it++;
               }
        }
        for (auto it : l)
        {
               cout << it << " ";
        }
        cout << endl;
        l.clear();
        l.push_back(1);
        l.push_back(2);
        l.push_back(3);
        for (auto it : l)
        {
               cout << it << " ";
        }
        cout << endl;
}
void test_list7()
{
        bit::list<int> l;
        l.push_back(1);
        l.push_back(2);
        l.push_back(3);
        l.push_back(4);
        l.push_back(5);
        l.push_back(6);
        bit::list<int> l2(l);
        for (auto it : l)
        {
               cout << it << " ";
        }
        cout << endl;
        l.pop_back();
        l2 = l;
        for (auto it : l2)
        {
               cout << it << " ";
        }
}
int main()
{
        test_list7();
        return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5节点系统潮流计算-牛拉法和PQ分解法(代码+报告)

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该部分资料是牛拉法和PQ分解法两种潮流计算方法的代码和对应的资料&#xff0c;程序针对5节点系统&#xff0c;也可以自行修改节点和线路参数改成其他节点系统&#xff0c;程序通用性较强&#xff0c;注释清晰…

电脑没有声音了怎么恢复?3个实用方法分享!

案例&#xff1a;我想在电脑上看电影、听音乐&#xff0c;但是我发现电脑没有声音&#xff0c;这种情况让我感到很困扰&#xff0c;有没有解决的方法&#xff1f; 【我的电脑没有声音&#xff0c;这非常影响我的使用。电脑没有声音是什么问题&#xff1f;有没有小伙伴知道解决…

Recurrent Neural Network(循环神经网络)

目录 Slot Filling with RNN Elman Network & Jordan Network Bidirectional RNN LSTM(Long Short-term Memory) Example Learning Target LSTM GRU (Gated Recurrent Unit) More Applications Many to One Many to Many Speech Recognition Sequence to Sequ…

一大波物联网毕业设计选题推荐(配套源码、文档、开发板)

以下项目整体综合性比较强&#xff0c;更贴近于产品化&#xff0c;并且基本都包含微信小程序与物联网云平台的联动&#xff0c;每个项目均配套详细的项目开发文档、程序源码&#xff0c;非常适合作为物联网毕业设计选题。项目文档及源码在文章末尾可免费下载。 另外&#xff0…

新手上路——怎样给我的网站备案

怎样办理网站备案&#xff1a; 由于备案是在主机接入商处办理&#xff0c;通常在哪里买的网站空间在哪里提交备案。例如在西部数码开通的虚拟主机、云服务器、独立主机等业务后&#xff0c;再通过其平台提交备案申请。 1.主机业务开通成功后&#xff0c;打开备案平台网址&…

【uniapp】app端压窗屏设计

一、前言 众所周知&#xff0c;在app端中&#xff0c;普通的组件是无法覆盖原生组件&#xff0c;即使是官方提供的cover-view也只是在实体内容中覆盖一些原生的如地图。但是无法覆盖底部的tabbar。 二、了解层级关系 实际上app端每点击一次的层级是这样的&#xff0c;我们可…

spring security oauth2.0-authorization code

Oauth2.0 spring security 估计很多人都在用,里面有几种安全模式值得大家去摸索. oauth2.0 是一种授权鉴权的机制,主要是用来颁发令牌,验证令牌,刷新令牌. OAuth2.0是OAuth协议的延续版本&#xff0c;但不向前兼容OAuth 1.0(即完全废止了OAuth1.0). 2012年10月&#xff0c…

泛微e-cology9 SQL注入漏洞复现(QVD-2023-5012)

1、产品简介 泛微协同管理应用平台e-cology是一套兼具企业信息门户、知识文档管理、工作流程管理、人力资源管理、客户关系管理、项目管理、财务管理、资产管理、供应链管理、数据中心功能的企业大型协同管理平台。 2、漏洞概述 泛微e-cology9中存在SQL注入漏洞&#xff0c;未…

《数据万象带你玩转视图场景》第三期:图片极智压缩

前言 在本系列的第一期我们介绍了图片 AVIF 压缩&#xff0c;作为最前沿的压缩技术&#xff0c;AVIF 确实有着无数的优点。但时代的进步是循序渐进的&#xff0c;在一些较老的终端或设备上&#xff0c;可能短时间内确实无法支持 AVIF 格式&#xff0c;那如何能让这部分业务享受…

【毕业季征文】你们的未来可期

还有大约一个月即将又有一批大学的学弟学妹们离开学校,走向社会,走上工作岗位。你们是否是既兴奋又迷茫呢?,我相信更多的是憧憬吧。回想我大学毕业那是10年前的事情了,现在的感受是那么遥远,仿佛自己从来都没上过大学。可能是在大学期间过的太过艰苦,以至于不想保留那一…

TDengine客户端连接

目录 TDengine-clinet 下载网址 运行软件 连接默认TD 连接指定TD TDengineGUI 下载地址 使用 连接&#xff1a;端口默认是6041&#xff1b; sql查询时需要制定 库名 TDengine-clinet 下载网址 连接器 | TDengine 文档 | 涛思数据 默认安装即可; 运行软件 连接默认TD…

机器学习期末复习 线性模型

1.线性回归&#xff0c;对数几率回归&#xff0c;线性判别分析是分类还是回归任务&#xff1f;是有监督的学习还是无监督的学习&#xff1f; 有监督学习和无监督学习 解释&#xff1a; 线性模型要做的有两类任务&#xff1a;分类任务、回归任务 分类的核心就是求出一条直线w…

JavaScript之BOM(上)

Window对象 1.BOM(浏览器对象模型) 2.定时器-延时函数 3.JS执行机制 4.location对象 5.navigator对象 6.history对象 一.BOM(浏览器对象模型) 1.BOM(Browser Object Model)是浏览器对象模型 2.window对象是一个全局对象&#xff0c;也可以说是JavaScript中的顶级对象 3.像do…

Facebook速推帖子和Facebook广告有什么区别

Facebook速推帖子和Facebook广告是在Facebook平台上推广内容的两种不同方式。虽然它们都可以帮助我们增加品牌曝光和吸引目标受众&#xff0c;但它们在运作方式和效果上有一些区别。让我们来详细了解一下它们之间的差异。 1.内容形式和展示方式&#xff1a; Facebook速推帖子&…

基于北斗+LoRa的落水报警定位方案一 -实现无人区,弱信号地区人员 位置安全监控

人员安全一直是企业管理的重中之重。无人区工地是一个安全事故多发的地方&#xff0c;在施工环境复杂且危险的情况下&#xff0c;工地人员位置监控成为管理工作中的一个难点&#xff0c;一直以来采用粗放的方式&#xff0c;现有的工地项目存在施工人员实时督查难等问题&#xf…

【问题记录】postgreSQL使用默认密码导致kdevtmpfsi挖矿病毒注入

起因 postgreSQL我做错了这几件事情 开启了全部IP登陆权限postgreSQL用的是默认用户名和密码用户postgres也没有设置密码&#xff0c;直接用su - postgres就能登陆 不知道是什么原理&#xff0c;反正服务器被侵入&#xff0c;并且注入了病毒文件 1. 基本信息排查 linux服务器…

大麦链接源码 大麦一键生成订单页面

8.4最新版源码 更新了大麦模版链接 更新了大麦订单页面一键生成

《Web安全基础》01. 基础知识

基础 1&#xff1a;概念名词1.1&#xff1a;域名1.2&#xff1a;DNS1.3&#xff1a;网站开发语言1.4&#xff1a;后门1.5&#xff1a;Web1.6&#xff1a;Web 相关安全漏洞 2&#xff1a;数据包2.1&#xff1a;HTTP2.2&#xff1a;HTTPS2.3&#xff1a;请求数据包2.3.1&#xff…

Linux Centos7 jar包方式安装Jenkins服务

1、本文是在VM虚拟机下安装的Centos7&#xff0c;这一步不赘述&#xff0c;有很多教程。 2、配置好静态IP&#xff0c;可参考Linux配置静态IP地址_cylemon的博客-CSDN博客 3、本文是通过XShell连接linux来操作linux系统的。配置JDK1.8及以上环境。先查看一下当前环境是否已存…

三、JavaWeb从入门到熟练

一、JavaWeb概述 Java Web&#xff0c;是用Java技术来解决相关web互联网领域的技术栈。web包括&#xff1a;web服务端和web客户端两部分。Java在客户端的应用有Java Applet&#xff0c;不过使用得很少&#xff0c;Java在服务器端的应用非常的丰富&#xff0c;比如Servlet&…