C++STL----list的模拟实现

文章目录

    • list模拟实现的大致框架
    • 节点类的模拟实现
    • 迭代器类的模拟实现
      • 迭代器类存在的意义
      • 迭代器类的模板参数说明
      • ++运算符的重载
      • --运算符的重载
      • !=与==运算符的重载
      • *运算符的重载
      • ->运算符的重载
    • list的模拟实现
      • 默认成员函数
      • 迭代器相关函数
      • 元素修改相关函数
        • front和back
        • insert
        • erase
        • push_back和pop_back
        • push_front和pop_front
      • 其他函数
        • size
        • resize
        • clear
        • swap

list模拟实现的大致框架

#include<iostream>

using namespace std;

namespace lhj
{
    template<class T>
	//单个节点
	//内部框架
	struct list_node
	{};
    
    //迭代器: 像指针一样的对象
	template<class T,class Ref,class Ptr>
	//使迭代器类型泛型化,Ref,Ptr是普通类型,那么迭代器就是普通类型
	//Ref,Ptr是const类型,迭代器就是const类型
	struct __list_iterator
    {};
    
	template<class T>
	class list
	{
	public:
        typedef __list_iterator<T,T&,T*> iterator;
		typedef __list_iterator<T,const T&,const T*>const_iterator;
        //......
    private:
		typedef list_node<T> Node;//将单个节点的类型 重命名为Node 便于书写
		Node* _head;
	};
}

在这里插入图片描述

节点类的模拟实现

template<class T>
//单个节点
//内部框架
struct list_node
{
	T _data;
	//指向节点的指针,类型为节点的类型
	list_node* _next;
	list_node* _prev;
	list_node(const T& x=T())//构造函数初始化
		:_data(x)
		,_next(nullptr)
		,_prev(nullptr)
	{}
};

注意: 若构造结点时未传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据。

迭代器类的模拟实现

迭代器类存在的意义

在这里插入图片描述

总结: list迭代器类,实际上就是对结点指针进行了封装,对其各种运算符进行了重载,使得结点指针的各种行为看起来和普通指针一样。(例如,对结点指针自增就能指向下一个结点)

迭代器类的模板参数说明

在list的模拟实现当中,typedef了两个迭代器类型,普通迭代器和const迭代器。

typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;

迭代器类中

template<class T,class Ref,class Ptr>
//使迭代器类型泛型化,Ref,Ptr是普通类型,那么迭代器就是普通类型
//Ref,Ptr是const类型,迭代器就是const类型

若该迭代器类不设计三个模板参数,那么就不能很好的区分普通迭代器和const迭代器。

迭代器类的模拟实现

template<class T,class Ref,class Ptr>
//使迭代器类型泛型化,Ref,Ptr是普通类型,那么迭代器就是普通类型
//Ref,Ptr是const类型,迭代器就是const类型
struct __list_iterator
{
	typedef list_node<T> Node;
	typedef __list_iterator<T,Ref,Ptr> iterator;
	Node* _node;//迭代器的本质就是指针,故需要定义一个节点的指针
	//构造函数,用一个节点的指针来初始化迭代器
	__list_iterator(Node* node)
		:_node(node)
	{}
	//迭代器不需要提供析构函数
	//_node为指针,属于内置类型
	//同时不需要写拷贝构造函数
	//默认的浅拷贝就是迭代器所需要的	
};

++运算符的重载

前置++原本的作用是将数据自增,然后返回自增后的数据。

先记录当前结点指针的指向,然后让结点指针指向后一个结点,最后返回“自增”前的结点指针

//前置++
iterator operator++(int)
{
	iterator tmp(*this);//记录当前结点指针的指向
	_node = _node->_next; //让结点指针指向后一个结点
	return tmp;//返回自增前的结点指针
}

后置++,则应该让结点指针指向后一个结点,然后再返回“自增”后的结点指针

iterator operator++()
{
	_node = _node->_next;//让结点指针指向后一个结点
	return *this;//返回自增后的结点指针
}

注意:iterator是当前迭代器类实例化出来的一个对象类型

–运算符的重载

iterator operator--()
{
	_node = _node->_prev;
	return *this;
}
//--it
iterator operator--(int)
{
	iterator tmp(*this);
	_node = _node->_prev;
	return tmp;
}

!=与==运算符的重载

bool operator!=(const iterator& it)const
{
	//迭代器之间的比较
	return _node != it._node;
}
bool operator==(const iterator& it)const
{
	//迭代器之间的比较
	return _node == it._node;
}

*运算符的重载

对指针变量进行解引用是为了得到该变量的数据内容。故直接返回当前结点指针所指结点的数据,但是这里需要使用引用返回,因为解引用后可能需要对数据进行修改。

Ref operator*()
{
	return _node->_data;//返回结点指针所指结点的数据
}

->运算符的重载

对于->运算符的重载,直接返回结点当中所存储数据的地址即可。

Ptr operator->()
{
	return &(operator*());//返回结点指针所指结点的数据的地址
    // &(operator*()) -> &(_pnode->_val)
}

在这里插入图片描述

list的模拟实现

默认成员函数

构造函数

构造一个某类型的空容器。

list<int> lt1; //构造int类型的空容器

使用迭代器拷贝构造某一段内容。

string s("hello world");
list<char> lt4(s.begin(),s.end()); //构造string对象某段区间的复制品

构造函数的模拟实现

void empty_init()
{
    //初始化
	//头结点的_next,_prev都是指向自己
	_head = new Node;
	_head->_next = _head;
	_haed->_prev = _head;

list()
{
    //构造函数,先新建一个节点作为头结点
	empty_init();//复用
}
//迭代器区间初始化
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
	empty_init();//先要将头结点初始化
	while (first!=last)
	{
		push_back(*first);
		++first;
	}
}

拷贝构造函数

拷贝构造某类型容器的复制品。

list<int> lt3(lt2); //拷贝构造int类型的lt2容器的复制品

拷贝构造函数的模拟实现

拷贝构造一个对象,即需要先构造出一个头结点,然后再用被拷贝对象的迭代器区间初始化一个中间对象,然后再与拷贝对象交换

也可以在初始化出一个头结点后,将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面

//方法一:
list(const list& lt)
{
	empty_init();//初始化函数
	list<T> tmp(lt.begin(), lt.end());
	swap(tmp);
}

//方法二:
list(const list<T>& lt)
{
	_head = new node; //申请一个头结点
	_head->_next = _head; //头结点的后继指针指向自己
	_head->_prev = _head; //头结点的前驱指针指向自己
	for (const auto& e : lt)
	{
		push_back(e); //将容器lt当中的数据一个个尾插到新构造的容器后面
	}
}

赋值运算符重载函数

//传统写法
list<T>& operator=(const list<T>& lt)
{
	if (this != &lt) //避免自己给自己赋值
	{
		clear(); //清空容器
		for (const auto& e : lt)
		{
			push_back(e); //将容器lt当中的数据一个个尾插到链表后面
		}
	}
	return *this; //支持连续赋值
}

//现代写法
list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
{
	swap(lt); //交换这两个对象
	return *this; //支持连续赋值
}

析构函数

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

迭代器相关函数

begin和end

list的底层为带头双向循环链表,begin()为头结点的下一个结点的地址构造出来的迭代器,end()为最后一个节点的下一个位置的迭代器(最后一个结点的下一个结点就是头结点

iterator begin()
{
	//返回头结点的下一个结点的地址构造出来的迭代器
	return iterator(_head->_next);
}
iterator end()
{
	//返回使用头结点的地址构造出来的普通迭代器
	return iterator(_head);
}
const_iterator begin() const
{
	//返回头结点的下一个结点的地址构造出来的const迭代器
	return const_iterator(_head->_next);
}
const_iterator end() const
{
	//返回使用头结点的地址构造出来的const迭代器
	return const_iterator(_head);
}

元素修改相关函数

front和back

front和back函数分别用于获取第一个有效数据和最后一个有效数据.

T& front()
{
	return *begin(); //返回第一个有效数据的引用
}
T& back()
{
	return *(--end()); //返回最后一个有效数据的引用
}
const T& front() const
{
	return *begin(); //返回第一个有效数据的const引用
}
T& back()
{
	return *(--end()); //返回最后一个有效数据的引用
}
const T& back() const
{
	return *(--end()); //返回最后一个有效数据的const引用
}
insert

list中的插入节点与数据结构中的插入节点一样。

iterator insert(iterator pos, const T& x)
{
    assert(pos._pnode); //检测pos的合法性
	Node* cur = pos._node;//迭代器pos处的结点指针
	Node* prev = cur->_prev;//迭代器pos前一个位置的结点指针
	Node* newnode = new Node(x)//根据所给数据x构造一个待插入结点
    //建立newnode与prev之间的双向关系    
	prev->_next = newnode;
	newnode->_prev = prev;
    //建立newnode与cur之间的双向关系    
	newnode->_next = cur;
	cur->_prev = newnode;

	return iterator(newnode);
}
erase
iterator erase(iterator pos)
{
	assert(pos != end());//检测pos的合法性
	assert(pos != end()); //删除的结点不能是头结点	
    
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
    
	//建立prev与next之间的双向关系
	prev->_next = next;
	next->_prev = prev;
	delete cur;//释放cur结点

	return iterator(next);//返回所给迭代器pos的下一个迭代器
}
push_back和pop_back

push_back和pop_back函数分别用于list的尾插和尾删,在已经实现了insert和erase函数的情况下,我们可以通过复用函数来实现push_back和pop_back函数。

//尾插
void push_back(const T& x)
{
	insert(end(), x); //在头结点前插入结点
}
//尾删
void pop_back()
{
	erase(--end()); //删除头结点的前一个结点
}
push_front和pop_front

头插和头删的push_front和pop_front函数也可以复用insert和erase函数来实现。

//头插
void push_front(const T& x)
{
	insert(begin(), x); //在第一个有效结点前插入结点
}
//头删
void pop_front()
{
	erase(begin()); //删除第一个有效结点
}

其他函数

size

size函数用于获取当前容器当中的有效数据个数,因为list是链表,所以只能通过遍历的方式逐个统计有效数据的个数。

size_t size() const
{
	size_t sz = 0; //统计有效数据个数
	const_iterator it = begin(); //获取第一个有效数据的迭代器
	while (it != end()) //通过遍历统计有效数据个数
	{
		sz++;
		it++;
	}
	return sz; //返回有效数据个数
}

扩展: 其实也可以给list多设置一个成员变量size,用于记录当前容器内的有效数据个数。

resize

实现resize的方法:设置一个变量len,用于记录当前所遍历的数据个数,然后开始遍历容器

在遍历过程中:

len大于或是等于n时遍历结束,此时说明该结点后的结点都应该被释放,将之后的结点释放即可。
当容器遍历完毕,说明容器当中的有效数据个数小于n,则需要尾插结点,直到容器当中的有效数据个数为n时停止尾插即可。

void resize(size_t n, const T& val = T())
{
	iterator i = begin(); //获取第一个有效数据的迭代器
	size_t len = 0; //记录当前所遍历的数据个数
	while (len < n&&i != end())
	{
		len++;
		i++;
	}
	if (len == n) //说明容器当中的有效数据个数大于或是等于n
	{
		while (i != end()) //只保留前n个有效数据
		{
			i = erase(i); //每次删除后接收下一个数据的迭代器
		}
	}
	else //说明容器当中的有效数据个数小于n
	{
		while (len < n) //尾插数据为val的结点,直到容器当中的有效数据个数为n
		{
			push_back(val);
			len++;
		}
	}
}
clear
void clear()
{
	//clear是清除头结点以外的所有节点
	//析构函数才是清除包括头结点的所有节点
	iterator it = begin();
	while (it!=end())
	{
		it = erase(it);//erase会返回下一个位置的迭代器
		//故不需要it++
	}
}
swap
void swap(list<T>& lt)
{
	::swap(_head, lt._head); //交换两个容器当中的头指针即可
}

注意: 在此处调用库当中的swap函数需要在swap之前加上“::”(作用域限定符),告诉编译器这里优先在全局范围寻找swap函数,否则编译器会认为你调用的就是你正在实现的swap函数(就近原则)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

edge浏览器的隐藏功能

1. edge://version 查看版本信息 2. edge://flags 特性界面 具体到某一特性&#xff1a;edge://flags/#overlay-scrollbars 3. edge://settings设置界面 详情可参考chrome: 4. edge://extensions 扩展程序页面 5. edge://net-internals 网络事件信息 6. edge://component…

【指针、数组参数】

void interchange(int * u,int * v) {int temp *u; //带*号指向该地址上的值*u *v;*v temp; }int main1(void) {int x 10;int y 5;printf("before: x %d y %d\n",x,y);interchange(&x,&y);printf("after: x %d y %d\n",x,y); }结果&…

Redis测试新手入门教程

在测试过程中&#xff0c;我们或多或少会接触到Redis&#xff0c;今天就把在小破站看到的三丰老师课程&#xff0c;把笔记整理了下&#xff0c;用来备忘&#xff0c;也希望能给大家带来亿点点收获。 主要分为两个部分&#xff1a; 一、缓存技术在后端架构中是如何应用的&#…

十八、模型构建器(ModelBuilder)快速提取城市建成区——批量掩膜提取夜光数据、夜光数据转面、面数据融合、要素转Excel(基于参考比较法)

一、前言 前文实现批量投影栅格、转为整型,接下来重点实现批量提取夜光数据,夜光数据转面、夜光数据面数据融合、要素转Excel。将相关结果转为Excel,接下来就是在Excel中进行阈值的确定,阈值确定无法通过批量操作,除非采用其他方式,但是那样的学习成本较高,对于参考比较…

Linux Centos7安装后,无法查询到IP地址,无ens0,只有lo和ens33的解决方案

文章目录 前言1 查看network-scripts目录2 创建并配置 ifcfg-ens33 文件3 禁用NetworkManager4 重新启动网络服务总结 前言 在VMware中&#xff0c;安装Linux centos7操作系统后&#xff0c;想查询本机的IP地址&#xff0c;执行ifconfig命令 ifconfig结果如下&#xff1a; 结…

基于深度学习的单图像人群计数研究:网络设计、损失函数和监控信号

摘要 https://arxiv.org/pdf/2012.15685v2.pdf 单图像人群计数是一个具有挑战性的计算机视觉问题,在公共安全、城市规划、交通管理等领域有着广泛的应用。近年来,随着深度学习技术的发展,人群计数引起了广泛的关注并取得了巨大的成功。通过系统地回顾和总结2015年以来基于深…

【Overload游戏引擎细节分析】PBR材质Shader---完结篇

PBR基于物理的渲染可以实现更加真实的效果&#xff0c;其Shader值得分析一下。但PBR需要较多的基础知识&#xff0c;不适合不会OpenGL的朋友。 一、PBR理论 PBR指基于物理的渲染&#xff0c;其理论较多&#xff0c;需要的基础知识也较多&#xff0c;我在这就不再写一遍了&…

【Vue】vant上传封装方法,van-uploader上传接口封装

项目场景&#xff1a; 问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 在移动端项目中&#xff0c;使用vant组件上传&#xff0c;但是vant没有上传方法&#xff0c;需要自己写。 html代码 <van-uploader v-model"fileList" :max-size"50…

SV-10A-4G IP网络报警非可视终端 (4G版)

SV-10A-4G IP网络报警非可视终端 &#xff08;4G版&#xff09; https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.621e3d0dpv5knb&ftt&id745728046948 产品简介&#xff1a; 通过局域网/广域网网组网的网络报警系统&#xff0c;改变传统局域网组网…

[Linux C] signal 的使用

前言&#xff1a; signal 是一种通信机制&#xff0c;可以跨进程发送&#xff0c;可以同进程跨线程发送&#xff0c;可以不同进程向指定线程发送。 信号的创建有两套api&#xff0c;一个是signal&#xff0c;一个是sigaction&#xff0c;signal缺陷很多&#xff0c;比如没有提…

【脚本笔记】AssetDatabase

AssetDatabase是编辑器下的处理资源操作的重要类&#xff0c;主要用于访问资源并针对资源执行操作的接口。 这里面所有的操作路径都是基于Unity项目的相对路径也就是Assets/xxx或者Assets/xxx.jpg这种。CacheServer 主要解决的是缩短大型团队导入资源的时间。当配置后&#xff…

论文阅读——InstructGPT

论文&#xff1a;Training_language_models_to_follow_instructions_with_human_feedback.pdf (openai.com) github&#xff1a;GitHub - openai/following-instructions-human-feedback 将语言模型做得更大并不能从本质上使它们更好地遵循用户的意图。例如&#xff0c;大型语…

命令模式——让程序舒畅执行

● 命令模式介绍 命令模式&#xff08;Command Pattern&#xff09;&#xff0c;是行为型设计模式之一。命令模式相对于其他的设计模式来说并没有那么多条条框框&#xff0c;其实并不是一个很“规矩”的模式&#xff0c;不过&#xff0c;就是基于一点&#xff0c;命令模式相对于…

搭载紫光展锐芯片平台W117,小米手表S3全新上市

近日&#xff0c;搭载紫光展锐W117芯片平台的全新小米手表S3正式上市。该款手表主打“独立通话&#xff0c;强劲续航”&#xff0c;设计延续了经典腕表精致外观&#xff0c;基础表盘质感全⾯提升。同时小米手表S3首创“百变表圈”&#xff0c;用户可以根据需求自行更换不同表圈…

【自然语言处理】【长文本处理】RMT:能处理长度超过一百万token的Transformer

相关博客 【自然语言处理】【长文本处理】RMT&#xff1a;能处理长度超过一百万token的Transformer 【自然语言处理】【大模型】MPT模型结构源码解析(单机版) 【自然语言处理】【大模型】ChatGLM-6B模型结构代码解析(单机版) 【自然语言处理】【大模型】BLOOM模型结构源码解析(…

2023年05月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 运行以下程序&#xff0c;如果通过键盘先后输入的数是1和3&#xff0c;输出的结果是&#xff1f;&#xff08; &#x…

学习视频剪辑:如何从指定时段快速抽出视频图片!高效技巧分享

随着数字媒体的普及&#xff0c;越来越多的人开始接触视频剪辑。在视频剪辑过程中&#xff0c;有时候我们需要从指定时段快速抽出视频图片。这不仅可以帮助我们提高剪辑效率&#xff0c;还可以让我们的视频更加丰富多彩。本文将分享一些高效技巧&#xff0c;帮助你轻松实现从指…

企业计算机电脑中了locked勒索病毒怎么办,勒索病毒解密,数据恢复

网络技术的不断发展&#xff0c;为我们的企业带来了很大的便利&#xff0c;大部分企业都会选择合适的办公软件系统&#xff0c;方便自身的生产与运营。近期&#xff0c;网络上的locked勒索病毒又开始攻击企业的计算机服务器了&#xff0c;经过10月份云天数据恢复中心对企业数据…

基于机器视觉的银行卡识别系统 - opencv python 计算机竞赛

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的银行卡识别算法设计 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng…

水果FL Studio21.2体验版下载安装教程(增加云服务功能)

FL Cloud 音效库包含开放版权的Loop和采样&#xff0c;以及来自 FL Studio 著名用户的艺术家独家内容。更新后&#xff0c;现在还可以使用人工智能辅助母带处理和数字发行功能来制作音轨。FL Studio 由最初的 "Fruity Loops" DAW 发展而来&#xff0c;25 年来&#x…