C++:list?自己模拟实现!
目录
1.list的模拟实现
1.1 成员变量和节点
1.2 迭代器实现
1.2.1 非const的迭代器
1.2.2 const的迭代器
1.2.3 一个模板实现 const 与 非const 迭代器
1.3 增删改查的实现
1.4 拷贝构造函数,析构函数与赋值运算符重载
1.5 泛型编程实现打印
2. list 反向迭代器的实现
3.list 和 vector 的对比
1.list的模拟实现
要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,通过上一篇的学习,这些内容已基本掌握,现在我们来模拟实现list。
1.1 成员变量和节点
首先,这里 list 设置成两个成员变量,_head 和 _size, _head用来指向头节点,_size用来记录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){}};template<class T>class list//list类{typedef list_node<T> Node;void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list()//构造函数{empty_init();}private:Node* _head;size_t _size;};
1.2 迭代器实现
在vector中,我们可以迭代器直接利用原生指针来实现,而在list中因为所使用的空间并不是连续的,我们需要对迭代器进行封装,内部存放一个Node* 类型的指针,运算符重载一些要使用的操作。
迭代器要使用的操作
- ++ / --
- * ->
- != / ==
1.2.1 非const的迭代器
template<class T>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node;__list_iterator(Node* node = nullptr):_node(node){}T& operator*(){return _node->_data;}T* operator->()//为数据存放的是自定义类型准备的。{return &_node->_data;}};
1.2.2 const的迭代器
const 迭代器要保证指向的内容不能被修改,而不是简单的在非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 = nullptr):_node(node){}//*it = 10;const T& operator*(){return _node->_data;}//it->a1 = 10;const T* operator->()//为数据存放的是自定义类型准备的。{return &_node->_data;}};
1.2.3 一个模板实现 const 与 非const 迭代器
可以发现上面两个迭代器只有 * -> 返回值不一样,写起来比较繁琐,我们可以用一个模板实现。
//const_iterator 和 iterator需要实现两个类,太繁琐// 可以利用模板实现//同一个类模板,模板参数不容类型不同//实例化的参数不容,类型完全不同,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 = nullptr):_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;}Ref operator*(){return _node->_data;}Ptr operator->()//为数据存放的是自定义类型准备的。{return &_node->_data;}bool operator!=(const self& it){return _node != it._node;}bool operator==(const self& it){return _node == it._node;}};
list类中使用迭代器:
public:/*typedef __list_iterator<T> iterator;typedef __list_const_iterator<T> const_iterator;*/typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->_next;//node指针,单参数构造函数,隐式类型转换}iterator end(){return _head;}//const迭代器 //const iterator 是修饰的是迭代器本身,迭代器就不能++了,//const迭代器的目的是让内容不能被修改//const_iterator 与iterator是两种完全不同的类型,没有什么关系//const_iterator 是重新定义的类型。本身可以修改,指向的内容不能修改const_iterator begin()const {return _head->_next;//node指针,单参数构造函数,隐式类型转换}const_iterator end()const{return _head;}
1.3 增删改查的实现
第一步我们来模拟实现insert 和 erase,实现这个接口后,我们其他插入删除操作可以通过嵌套这两个函数实现,这里使用到了链表的知识,不理解的可以画一下图。
iterator insert(iterator pos,const T& val){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(val);//prev -> newnode -> curnewnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;_size++;return iterator(newnode);//构造一个iterator的匿名对象返回,//也可以直接返回newnode,进行隐式类型转换}iterator erase(iterator pos)//迭代器会失效{if(_head->_next!=_head){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//prev -> nextprev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);}}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());}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}
1.4 拷贝构造函数,析构函数与赋值运算符重载
这里实现构造函数,析构函数,与赋值运算符重载。通过嵌套可以实现的更简洁。
list(const list<T>& l){empty_init();for (auto e : l){push_back(e);}}void swap(list<T>& l){std::swap(_head, l._head);std::swap(_size, l._size);}list<T>& operator=(list<T> l){//这里传参已经是实现拷贝构造,只需交换*this和l 就可以实现赋值赋值运算符重载swap(l);return *this;}~list(){clear();delete _head;}
1.5 泛型编程实现打印
我们可以使用一个模板函数来实现可以打印不同容器的内容。
void test4(){list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(5);l1.push_back(6);//print_list(l1);print_Container(l1);list<string> l2;l2.push_back("12345678");//这里list.push_back 不会涉及深拷贝的问题, //vector是因为开空间要拷贝原来数据l2.push_back("12345678");l2.push_back("12345678");l2.push_back("12345678"); //print_list(l2);print_Container(l2);vector<string> v;v.push_back("1111111111111");v.push_back("1111111111111");v.push_back("1111111111111");v.push_back("1111111111111");v.push_back("1111111111111");print_Container(v);}
1.实现只能打印vector <int> 的函数
void print_list(const list<int>& l){list<int>::const_iterator it = l.begin();//返回const迭代器while (it != l.end()){//*it = 1;//const迭代器不能修改cout << *it << " ";it++;}cout << endl;}
2.实现可以打印 vector<T> 的函数。
之前讲class 和 typename 的不同之处在这里可以体现。
template<typename T> void print_list(const list<T>& l){//这里要加 typename //list<T> 未实例化,编译器不能去他里面找//不能判断const_iterator 是一个静态成员变量还是一个内嵌类型//所以加一个typename 就是告诉编译器这里是一个类型,等它实例化了再去取,通过初步检查typename list<T>::const_iterator it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;}
3.实现可以打印所有容器的函数
template<typename Container>void print_Container(const Container& con){typename Container::const_iterator it = con.begin(); while (it != con.end()){cout << *it << " ";++it;}cout << endl;}//模板(泛型编程)本质,本来应该由我们做的事情交给编译器去做
2. list 反向迭代器的实现
通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。
template<class Iterator>
class ReverseListIterator
{// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态成员变量 // 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;public:// 构造ReverseListIterator(Iterator it) : _it(it){}// 具有指针类似行为Ref operator*(){Iterator temp(_it);--temp;return *temp;}Ptr operator->(){ return &(operator*());}//// 迭代器支持移动Self& operator++(){--_it;return *this;}Self operator++(int){Self temp(*this);--_it;return temp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self temp(*this);++_it;return temp;}// 迭代器支持比较bool operator!=(const Self& l)const{ return _it != l._it;}bool operator==(const Self& l)const{ return _it != l._it;}Iterator _it;
};
3.list 和 vector 的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
vector | list | |
底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随 机 访 问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素 效率O(N) |
插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1) |
空 间 利 用 率 | 底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高 | 底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低 |
迭 代 器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响 |
使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随 机访问 |
本篇结束!
相关文章:
C++:list?自己模拟实现!
目录 1.list的模拟实现 1.1 成员变量和节点 1.2 迭代器实现 1.2.1 非const的迭代器 1.2.2 const的迭代器 1.2.3 一个模板实现 const 与 非const 迭代器 1.3 增删改查的实现 1.4 拷贝构造函数,析构函数与赋值运算符重载 1.5 泛型编程实现打印 2. list 反…...

layui table合并相同的列
table.render({elem: #samples,url: /index/Develorderss/samplelists?od_idod_id //数据接口,page: { //支持传入 laypage 组件的所有参数(某些参数除外,如:jump/elem) - 详见文档layout: [prev, page, next, count,skip,limit]…...

【Spring】SpringBoot配置文件
SpringBoot配置文件 配置文件作用SpringBoot配置文件配置文件快速入手配置文件的格式properties配置文件说明基本语法读取配置文件properties缺点分析 yml配置文件说明yml基本语法yml使用进阶yml配置读取配置对象配置集合配置Mapyml优缺点 配置文件作用 计算机上有数以千计的配…...
python批量下载txt文件中链接的数据
python下载txt文件中链接的数据,以gimms ndvi3g下载为例 官方下载网址:https://www.ncei.noaa.gov/data/land-normalized-difference-vegetation-index/access/ 选择下载2020年数据为例: 网址:Index of /data/land-normalized-difference-vegetation-index/access/2020…...

stm32 Bootloader设计(YModem协议)
stm32 Bootloader设计(YModem协议) Chapter1 stm32 Bootloader设计(YModem协议)YModem协议:STM32 Bootloader软件设计STM32 Bootloader使用方法准备工作stm32 Bootloader修改:stm32目标板程序.bin偏移地址修改: Chapt…...

竞赛 题目: 基于深度学习的疲劳驾驶检测 深度学习
文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 🔥 优…...

ubuntu 16.04.5 安装 vivado 2019.1 完整编译AD9361的环境
一、前期安装 1、安装ncurses库(已经包含了,其他的os需要安装) sudo apt install libncurses5二、安装 sudo ./xsetup使用lic进行激活。 三、安装后 输入指令 sudo gedit ~/.bashrc 末尾添加 source /opt/Xilinx/Vivado/2019.1/setti…...

Zotero详细功能补充!熟练使用!【进阶版,持续更新】
Zotero安装请参见文章Zotero安装 1.改变条目文件夹 如果直接选择条目直接进行移动,能移动成功,但是原来文件夹和目标文件夹都会存在,实际是复制! 如果只想保留在一个文件夹里面,可以选中条目,右击-从分…...
【Windows】Windows系统常用命令大全
现实生活中,对电脑越熟悉懂得的快捷命令就越多。例如我们日常遇到的比较方便办公技巧,用快捷命令调用工具:调用计算器,Win键R,输入calc,回车,计算器界面弹出来; 调用记事本ÿ…...

大语言模型研究进展综述
1、历史 自20世纪50年代图灵测试被提出以来,研究人员一直在探索和开发能够理解并掌握语言的人工智能技术。 作为重要的研究方向之一,语言模型得到了学术界的广泛研究,从早期的统计语言模型和神经语言模型开始,发展到基于Transform…...

linux gdb 调试 常见调试命令介绍+总结
1.调试前准备 -g gcc arcg.c -g -oO -o app //必须添加-g 2.调试 gdb gdb app 3.常见调试命令 set args 1 2 3 4 5 6 //设置参数 show args //查看参数 3.1执行程序 1.start2. run gdb app set args 1 2 3 4 5 start //执行一行 c //继续执行 q…...

基于JavaWeb+SSM+Vue微信小程序校园兼职任务平台系统的设计和实现
基于JavaWebSSMVue微信小程序校园兼职任务平台系统的设计和实现 源码传送入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码传送入口 前言 随着社会的发展和全球疫情的冲击,大学生的就业形势越来越严峻。越…...
我的MQTT操作类(M2Mqtt.Net)
类本体 4.3.0 public class MQTTMain{public MqttClient mqttClient null ; public bool MQTTMainConnect(string MenZuNo, string ServerIP, int ServerPort, string UserName, string Pwd){try{string EMQX_CLIENT_ID $"称重端-{MenZuNo}-{OPCommon.PCControl.G…...

node插件MongoDB(四)—— 库mongoose 的个性话读取(字段筛选、数据排序、数据截取)(四)
文章目录 一、字段筛选二、数据排序三、数据截取1. skip 跳过2. limit 限定 一、字段筛选 字段筛选:只读取指定的数据,比如集合(表)中有…...

AI:73-结合语法知识的神经机器翻译研究
🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…...

[LeetCode]-225. 用队列实现栈
目录 225. 用队列实现栈 题目 思路 代码 225. 用队列实现栈 225. 用队列实现栈 - 力扣(LeetCode)https://leetcode.cn/problems/implement-stack-using-queues/description/ 题目 请你仅使用两个队列实现一个后入先出(LIFO࿰…...
Kafka Rebanlace次数过高问题
Kafka Rebanlace次数过高问题 环境: Kafka Server 2.6.x Kafka Client Java 2.8.2 缘起: 最近发现Kafka Rebalance次数着实有点多,一天达到了六十多次,感觉不太正常,于是查了下日志发现: Offset commit c…...

计算机是如何进行工作的+进程和线程
一)计算机是如何工作的? 指令是如何执行的?CPU基本工作过程? 假设上面有一些指令表,假设CPU上面有两个寄存器A的编号是00,B的编号是01 1)第一个指令0010 1010,这个指令的意思就是说把1010地址上面的数据给他读取到A寄存器里面 2…...

MySQL(11):数据处理之增删改
插入数据 方式1: 一条一条的添加数据 为表的所有字段按默认顺序插入数据 INSERT INTO 表名 VALUES (value1,value2,....);# 没有指明添加的字段 INSERT INTO emp1 VALUES (1,TOM,2023-11-06,3400);没有指明添加的字段,要按照声明顺序,进行…...

QT QDockWidget
QDockWidget是Qt中的一个容器类,用于在主窗口上创建可停靠的子窗口。 设置停靠窗口的一般流程如下: (1)创建一个QDockWidget 对象的停靠窗体。 (2)设置此停靠窗体的属性,通常调用setFeatures()及setAllowedAreas()两种方法。 (3)新建一个要插入停靠窗…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...