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

news2024/11/15 8:33:58

文章目录

    • 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函数(就近原则)。

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

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

相关文章

“KeyarchOS:国产Linux新星的崛起与创新之路“

简介 KOS&#xff0c;也就是KeyarchOS&#xff0c;是一款由国内团队开发的服务器操作系统。它因为几个特点而受到我的青睐和一些用户的关注。 首先&#xff0c;KOS注重安全性和稳定性。它有一些防护和隔离功能&#xff0c;来帮助系统稳定运行&#xff0c;而且是中文语言更接地…

从零开始的LINUX(三)

bc&#xff1a;进行浮点数运算 uname&#xff1a;查看当前的操作系统 ctrlc&#xff1a;中止当前正在执行的程序 ctrld&#xff1a;退出xshell shutdown&#xff1a;关机 reboot&#xff1a;重启 shell外壳&#xff1a; 作用&#xff1a;1、命令解释&#xff08;将输入的程序…

QSS 自定义QLineEdit

QSS 自定义QLineEdit Chapter1 QSS 自定义QLineEdit简述常用属性和伪状态效果图QSS源码参考 Chapter1 QSS 自定义QLineEdit 原文链接&#xff1a;https://blog.csdn.net/Staranywhere/article/details/107306276 简述 本文将通过简单示例介绍QLineEdit样式如何自定义。 常用…

Linux高性能服务器编程——ch9笔记

第9章 I/O复用 同时监听多个文件描述符&#xff0c;但本身是阻塞的。 9.1 select系统调用 在一段指定时间内&#xff0c;监听用户感兴趣的文件描述符上的可读、可写和异常等事件是否就绪。 :::tips int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* except…

掌握 JavaScript:从初学者到高级开发者的完整指南之JavaScript对象(二)

JavaScript基础知识 1. JavaScript对象1.1.1 基本对象1.1.1.1 Array对象语法格式特点属性和方法 1.1.1.2 String对象语法格式属性和方法 1.1.1.3 JSON对象自定义对象json对象 1. JavaScript对象 可以大体分页3大类&#xff1a; 第一类&#xff1a;基本对象,我们主要学习Array…

Python---小海龟会画画---利用turtle(海龟)模块

1、小海龟模块 在Python3版本中&#xff0c;新增加了一个模块叫做turtle&#xff08;海龟&#xff09;&#xff0c;专门用于绘制图形图像 2、模块如何使用 ① 导入模块 import turtle② 使用turtle模块中已经定义好的方法 turtle.forward(数值) # 从左向右&#xff0c;绘制一…

docker 部署 若依 Ruoyi springboot+vue分离版 dockerCompose

本篇从已有虚拟机/服务器 安装好dokcer为基础开始讲解 1.部署mysql 创建conf data init三个文件夹 conf目录存放在mysql配置文件 init目录存放着若依数据库sql文件&#xff08;从navicat导出的并非若依框架自带sql&#xff09; 创建一个属于本次若依部署的网段&#xff08;只…

python:使用Scikit-image对遥感影像进行梯度特征提取(gradient)

作者:CSDN @ _养乐多_ 在本博客中,我们将介绍如何使用Scikit-Image来进行梯度特征提取(gradient),并且提供一个示例代码,演示了如何在单波段遥感图像上应用这些方法。 梯度特征是指用于表示图像中亮度或颜色变化的特征。它包括两个关键成分:梯度幅值和梯度方向。梯度幅…

吴恩达《机器学习》1-3:监督学习

一、监督学习 例如房屋价格的数据集。在监督学习中&#xff0c;我们将已知的房价作为"正确答案"&#xff0c;并将这些价格与房屋的特征数据一起提供给学习算法。学习算法使用这些已知答案的数据来学习模式和关系&#xff0c;以便在未知情况下预测其他房屋的价格。这就…

底层驱动day8作业

代码&#xff1a; //驱动程序 #include<linux/init.h> #include<linux/module.h> #include<linux/of.h> #include<linux/of_gpio.h> #include<linux/gpio.h> #include<linux/timer.h>struct device_node *dnode; //unsigned int gpiono; …

内存管理:TLSF算法

动态内存分配DSA DSA&#xff1a;Dynamic Storage Allocation&#xff0c;用于动态管理程序运行时所需的内存。动态内存分配涉及在程序运行时根据需要分配和释放内存&#xff0c;以存储数据结构和数据。 内存管理方式&#xff1a;动态内存分配与静态内存分配相对应&#xff0…

json格式存储b64编码的rgb raw数据

1.rgb raw数据准备 利用python将jpg里面的rgb raw数据提取出来。 import cv2# 读取 JPG 图像 image_path 1.jpg image cv2.imread(image_path)#imread读出来的顺序是BGR print("image shape:",image.shape)# 将图像由BGR转换为 RGB 数据 rgb_data cv2.cvtColor(im…

什么是水坑攻击

水坑攻击 1. 水坑攻击的概念1. 水坑攻击的原理2. 水坑攻击的常用手段3. 典型水坑攻击事件 1. 水坑攻击的概念 水坑攻击&#xff08;Watering Hole Attack&#xff09;是一种网络攻击方法&#xff0c;其名称来源于自然界的捕食方式。 攻击者会通过前期的调查或各种社会工程手段…

2023年【河北省安全员B证】免费试题及河北省安全员B证作业考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 河北省安全员B证免费试题考前必练&#xff01;安全生产模拟考试一点通每个月更新河北省安全员B证作业考试题库题目及答案&#xff01;多做几遍&#xff0c;其实通过河北省安全员B证在线考试很简单。 1、【多选题】一般…

计算机中了faust勒索病毒怎么办,faust勒索病毒解密,数据恢复

近年来网络技术得到了飞速发展&#xff0c;为人们的企业生产生活提供了极大便利&#xff0c;但随之而来的网络安全威胁也不断增加&#xff0c;近期&#xff0c;云天数据恢复中心收到了很多企业的求助&#xff0c;企业的计算机服务器遭到了faust勒索病毒攻击&#xff0c;导致企业…

【java学习—九】内部类(7)

文章目录 1. 概念2. 内部类特性3. 内部类实现多重继承的应用 1. 概念 &#xff08;1&#xff09;在 Java 中&#xff0c;允许一个类的定义位于另一个类的内部&#xff0c;前者称为内部类&#xff0c;后者称为外部类。     &#xff08;2&#xff09;Inner class 一般用在定…

亚马逊哪个站点好做?亚马逊全球站点介绍!

前言 亚马逊全球有18个站点&#xff0c;其中七大站点分别为&#xff1a;北美站、欧洲站、日本站、澳洲站、印度站、中东站、新加坡站。按照国家和地区分为中国、美国、加拿大、墨西哥、英国、德国、法国、西班牙、意大利、澳大利亚、日本、印度、土耳其、中东和巴西。不同的站…

GoLong的学习之路(十三)语法之标准库 log(日志包)的使用

上回书说到&#xff0c;flag的问题。这回说到日志。无论是软件开发的调试阶段还是软件上线之后的运行阶段&#xff0c;日志一直都是非常重要的一个环节&#xff0c;我们也应该养成在程序中记录日志的好习惯。 文章目录 log配置logger配置日志前缀配置日志输出位置自定义logger …

vscode配置C/C++

首先下载 https://sourceforge.net/projects/mingw-w64/files/mingw-w64/ 解压到一个方便的文件夹中 在环境变量中添加 C:\mingw64\bin验证添加的环境变量&#xff0c;打开cmd&#xff0c;输入gcc --version 进入vscode进行配置 安装插件 进行配置 gcc和g的配置文件…