list简单模拟实现

news2025/5/16 16:08:47

  • 成员变量
  • 迭代器(重点)
    • ListIterator
    • 运算符重载
    • begin、end
  • 插入、删除
    • insert
    • erase
    • 头插、尾插、头删、尾删
  • operator->
  • const_iterator
  • 拷贝构造
  • operator=
  • 析构函数
  • 完整代码

由于前面已经模拟实现了vector,所以这里关于一些函数实现就不会讲的过于细致。

成员变量

template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;
	ListNode(const T& x = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
	{}
};

template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	list()
	{
		_head = new Node;
		_head->_next = _head->_prev = _head;
		_size = 0;
	}
	size_t size()const
	{
		return _size;
	}
private:
	Node* _head;
	size_t _size;
};

迭代器(重点)

我们想要迭代器能实现以下的功能:

void test_1()
{
	list<int>lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << ' ';
		++it;
	}
	cout << endl;
}

实际上迭代器模拟的是指针的功能,然而链表的指针++并不会得到下一个元素的指针。但我们却希望迭代器++能得到下一个元素的迭代器。

那么我们是不是需要对++进行一个修改,也就是运算符重载。

再仔细想想,重载后的运算符是对这一个类生效的,但我们只需要对迭代器生效。那是不是意味着我们的迭代器需要是一个独立的类:

ListIterator

template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;
	Node* _node;//内置类型指针
	ListIterator(Node* node)
	:_node(node)
	{}
}

注意不需要析构函数,实际上我们的迭代器就是原数组结点指针的一个类,怎么可以用过一次后就析构掉呢?

运算符重载

前置++:

Self& operator++()//前置++
{
	_node = _node->_next;
	return *this;
}

后置++:

Self operator++(int)//后置++
{
	Self tmp(*this);//浅拷贝
	_node = _node->_next;
	return tmp; 
}

值得注意的是,前后++的重载函数名都是operator++,那么如何区分他们呢?

C++语法规定:后置++的函数参数里面需要有一个int

此外,我这的tmp是一个浅拷贝,这样做有没有问题呢?
我们需要的是指针,那么对于指针自然应该采用浅拷贝。
事实上,我们一般有一个规律:如果类需要重载析构函数,那么一般就要深拷贝,反之一般就需要浅拷贝。

剩余运算符:

Self& operator*()
{
	return _node->_data;
}

Self& operator--()//前置--
{
	_node = _node->_prev;
	return *this;
}

Self operator--(int)//后置--
{
	Self tmp(*this);//浅拷贝
	_node = _node->_prev;
	return tmp;
}

bool operator!=(const Self& it)
{
	return _node != it._node;
}

bool operator==(const Self& it)
{
	return _node == it._node;
}

begin、end

注意到我们的begin是返回首元素迭代器,而end是返回最后一个元素的下一个位置的迭代器:

typedef ListIterator<T> iterator;
iterator begin() 
{
	return _head->_next;
}

iterator end()
{
	return iterator(_head);
}

注意到,begin这里我返回的是_head->_next,因为单参数会隐式类型转换:自动调用单参数的构造函数,也就是等价于iterator(_head->_next).

插入、删除

insert

void insert(iterator pos, const T& val)
{
	Node* cur = pos._node;
	Node* newnode = new Node(val);
	Node* prev = cur->_prev;

	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	_size++;
}

erase

iterator erase(iterator pos)//返回iterator防止迭代器失效
{
	Node* cur = pos->_node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

	prev->_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()--);//没有重载-1,所以要用--
}

void pop_front()
{
	erase(begin());
}

尾删这里需要注意一定要是end()–,而不能是end()-1.理由很简单,因为我们没有重载-。

operator->

对于非内置类型如下:

struct A
{
	int _a1;
	int _a2;

	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{}
};

void test_2()
{
	list<A>lt;
	A aa1(1, 1);
	lt.push_back(aa1);
	lt.push_back(aa1);
	lt.push_back(A(2, 2));
	lt.push_back({ 3,3 });
	lt.push_back({ 4,4 });
	list<A>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << ' ';//不支持流插入
		++it;
	}

}

不难发现cout << *it << ’ ';这里会报错,因为自定义类型没有重载<<。
因此我们需要改写为:

cout << (*it)._a1 << ' ' << (*it)._a2 << endl;

不难发现这有些许繁琐,正常来说对于类指针功能我们更希望用->这个操作符,也就是:

cout << it->_a1 << ' ' << it->_a2 << endl;

那就需要重载->:

Self* operator->()
{
	return &_node->_data;
}

这时候就有人发出疑问了,怎么返回值是一个指针,那要调用_a1,不应该是it->->_a1吗?
没错,这是原生写法,但是我们C++语法给他优化掉了,现在it->_a1等价于it.operator->()->_a1

const_iterator

const_iterator为什么不是const iterator?
这个问题其实前面学习类的时候已经解答过了,事实上我们的const_iterator是不能改变迭代器呢,还是不能改变迭代器指向的元素的值呢?
事实上const_iterator也是可以++的,否则怎么遍历数组元素。
那也就是说const_iterator只是不能改变指向的元素的值,而不是不能改变迭代器本身。

经过上述分析我们得知,我们只需要对*和->重载的返回值作出修改即可:

const T& operator*()
{
	return _node->_data;
}

const T* operator->()
{
	return &_node->_data;
}

但是,只有返回值不同是无法重载函数的!!
那么我们是不是需要再多写一个const_iterator类呢?
可以,但有更好的方法。
注意到我们只有这两个函数不同,并且只有返回值不同,那是不是可以写一个模板将返回值不同分成不同的类呢?

template<class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;
			
	Ref operator*()
	{
		return _node->_data;
	}
	
	Ptr operator->()
	{
		return &_node->_data;
	}
	
}
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;

const_iterator begin() const
{
	return _head->_next;
}

const_iterator end() const
{
	return _head;
}

这样我们就省去了CV再写一个类的功夫。

拷贝构造

一样是复用push_back

void empty_init()
{
	_head = new Node;
	_head->_next = _head->_prev = _head;
	_size = 0;
}
list(const T& lt)// 复用push_back来深拷贝
{
	empty_init();
	for (auto& e : lt)
	{
		push_back(e);
	}
}

operator=

一样是复用拷贝构造:

void swap(list<T>& lt)
{
	std::swap(_head, lt._head);
	std::swap(_size, lt._size);
}

list<T>& operator=(list<T> lt)//传值传参
{
	swap(lt);
	return *this;
}

析构函数

同样复用erase

void clear()//没清除头结点
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}

}

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

}

完整代码

template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;
	ListNode(const T& x = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
	{}
};

template<class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;
	
	Node* _node;//内置类型指针

	ListIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	Self& operator++()//前置++
	{
		_node = _node->_next;
		return *this;
	}

	Self operator++(int)//后置++
	{
		Self tmp(*this);//浅拷贝
		_node = _node->_next;
		return tmp; 
	}

	Self& operator--()//前置--
	{
		_node = _node->_prev;
		return *this;
	}

	Self operator--(int)//后置--
	{
		Self tmp(*this);//浅拷贝
		_node = _node->_prev;
		return tmp;
	}

	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}

	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

};

template<class T>
class list
{
	typedef ListNode<T> Node;
public:

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

	iterator begin() //const
	{
		return iterator(_head->_next);
	}

	iterator end()
	{
		return iterator(_head);
	}

	//const_iterator而不是const iterator,因为是指向内容不能被修改,而非迭代器本身不能被修改。如果是const iterator就不能it++
	const_iterator begin() const
	{
		return _head->_next;
	}

	const_iterator end() const
	{
		return _head;
	}

	void empty_init()
	{
		_head = new Node;
		_head->_next = _head->_prev = _head;
		_size = 0;
	}

	list()
	{
		empty_init();
	}

	list(const T& lt)// 复用push_back来深拷贝
	{
		empty_init();
		for (auto& e : lt)
		{
			push_back(e);
		}
	}

	void swap(list<T>& lt)
	{
		std::swap(_head, lt._head);
		std::swap(_size, lt._size);
	}

	list<T>& operator=(list<T> lt)//传值传参
	{
		swap(lt);
		return *this;
	}

	void clear()//没清除头结点
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);
		}

	}

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

	}

	//void push_back(const T& x)
	//{
	//	Node* newnode = new Node(x);
	//	Node* tail = _head->_prev;

	//	tail->_next = newnode;
	//	newnode->_prev = tail;
	//	newnode->_next = _head;
	//	_head->_prev = newnode;
	//}

	void push_back(const T& x)
	{
		insert(end(), x);
	}

	void push_front(const T& x)
	{
		insert(begin(), x);
	}

	void pop_back()
	{
		erase(end()--);//没有重载-1,所以要用--
	}

	void pop_front()
	{
		erase(begin());
	}

	void insert(iterator pos, const T& val)
	{
		Node* cur = pos._node;
		Node* newnode = new Node(val);
		Node* prev = cur->_prev;

		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;
		_size++;
	}

	iterator erase(iterator pos)//返回iterator防止迭代器失效
	{
		Node* cur = pos->_node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

		prev->_next = next;
		next->_prev = prev;
		delete cur;
		_size--;

		return iterator(next);
	}

	size_t size()const
	{
		return _size;
	}

	bool empty()const
	{
		return _size == 0;
	}

private:
	Node* _head;
	size_t _size;
};

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

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

相关文章

QT6 源(101)阅读与注释 QPlainTextEdit,其继承于QAbstractScrollArea,属性学习与测试

&#xff08;1&#xff09; &#xff08;2&#xff09; &#xff08;3&#xff09;属性学习与测试 &#xff1a; &#xff08;4&#xff09; &#xff08;5&#xff09; 谢谢

Coze 实战教程 | 10 分钟打造你的AI 助手

> 文章中的 xxx 自行替换&#xff0c;文章被屏蔽了。 &#x1f4f1; 想让你的xxx具备 AI 对话能力&#xff1f;本篇将手把手教你&#xff0c;如何用 Coze 平台快速构建一个能与用户自然交流、自动回复提问的 xxx助手&#xff0c;零代码、超高效&#xff01; &#x1f4cc;…

牛客网 NC22167: 多组数据a+b

牛客网 NC22167: 多组数据ab 题目分析 这道题目来自牛客网&#xff08;题号&#xff1a;NC22167&#xff09;&#xff0c;要求我们计算两个整数a和b的和。乍看简单&#xff0c;但有以下特殊点需要注意&#xff1a; 输入包含多组测试数据每组输入两个整数当两个整数都为0时表示…

K8S Ingress、IngressController 快速开始

假设有如下三个节点的 K8S 集群&#xff1a; ​ k8s31master 是控制节点 k8s31node1、k8s31node2 是工作节点 容器运行时是 containerd 一、理论介绍 1&#xff09;什么是 Ingress 定义&#xff1a;Ingress 是 Kubernetes 中的一种资源对象&#xff0c;它定义了外部访问集群内…

快消零售AI转型:R²AIN SUITE如何破解效率困局

引言 快消零售行业正经历从“规模扩张”到“精益运营”的转型阵痛&#xff0c;消费者需求迭代加速、供应链复杂度攀升、人力成本持续走高&#xff0c;倒逼企业通过技术升级实现业务重塑[1]。RAIN SUITE以AI应用中台为核心&#xff0c;针对快消零售场景打造全链路提效方案&…

电路中零极点的含义

模拟电路中的零极点设计非常重要&#xff0c;涉及到系统的稳定。零点是开环传输函数分子为0时对应的频率。极点就是开环传递函数分子为0时对应的频率。 零点表征电路中能量输出路径的抵消效应&#xff0c;当不同支路的信号大小相等、方向相反时&#xff0c;导致特定频率下响应…

解读RTOS 第八篇 · 内核源码解读:以 FreeRTOS 为例

1. 引言 FreeRTOS 作为最流行的嵌入式实时操作系统之一,其内核源码简洁且功能完善。通过剖析其关键模块(任务管理、调度器、队列、内存管理和移植层),不仅能够更深入地理解 RTOS 的运行机制,还能掌握根据项目需求进行内核定制与优化的能力。本章将带你以 FreeRTOS 10.x 版…

2025年长三角+山东省赛+ 认证杯二阶段资料助攻说明

长三角高校数模B题 完整论文代码已经在售后群 网盘链接 发布 长三角更新时间轴 5.15 23:00 B站发布 完整论文讲解视频 5.16 18:00 j降重说明 5.17 22:00 无水印版本可视化无水印代码 其余时间 写手老师 售后群在线答疑 山东省助攻C道 认证杯二阶段助攻C题 山东省认证杯…

鸿蒙电脑:五年铸剑开新篇,国产操作系统新引擎

出品 | 何玺 排版 | 叶媛 前不久&#xff0c;玺哥发布的《鸿蒙电脑&#xff0c;刺向垄断的利刃&#xff0c;将重塑全球PC市场格局》发布后&#xff0c;获得了读者朋友的积极反馈&#xff0c;不少都期望鸿蒙电脑早日发布。 如今&#xff0c;它真来了&#xff01; 5月8日&…

SQLMesh信号机制详解:如何精准控制模型评估时机

SQLMesh的信号机制为数据工程师提供了更精细的模型评估控制能力。本文深入解析信号机制的工作原理&#xff0c;通过简单和高级示例展示如何自定义信号&#xff0c;并提供实用的使用技巧和测试方法&#xff0c;帮助读者优化数据管道的调度效率。 一、为什么需要信号机制&#xf…

通义千问-langchain使用构建(二)

目录 序言xinference应用构建构建过程简单概述成效 chatchat应用构建过程成效 总结 序言 在昨天的使用langchain的基础上。又尝试了构建智能问答应用。 使用langchain chatchat这个开源包&#xff0c;构建了一下智能问答系统。 前置项&#xff0c;是使用了一下xinference框架&…

[IMX] 02.GPIO 寄存器

目录 手册对应章节 1.GPIO 复用&#xff08;引脚功能选择&#xff09;- IOMUXC_SW_MUX_CTL_PAD_xxx 2.GPIO 电气特性 - IOMUXC_SW_PAD_CTL_PAD_xxx 3.GPIO 数据与控制寄存器 3.1.数据 - DR 3.2.输入/输出选择 - GDIR 3.3.状态 - PSR 3.4.中断触发控制 - ICR 3.5.中断使…

【电子通识】热敏纸的静态发色性能和动态发色性能测试方法

静态发色性能的测定 测定治具 测定静态发色曲线需要使用三个仪器,包括静态发色仪、秒表(分辨力为0.01 s)、反射光密度计(符合 GB/T23649)。 静态发色曲线使用的测试仪为静态发色仪。其结构如下图所示:包括了保湿压板、金属加热板、温度显示器、控制面板。温度能在50℃到…

AIbase推出全球MCP Server集合平台 收录超12万个MCP服务器客户端

2025年&#xff0c;AI领域迎来了一项重要的技术进展——MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;的广泛应用。全球MCP Server集合平台AIbase(https://mcp.aibase.cn/)应运而生&#xff0c;为AI开发者提供了一站式的MCP服务器和客户端整合…

使用CMake中的configure_file命令自动生成项目版本信息

1 背景 随着实际项目的完善&#xff0c;可维护变的更加重要。在日志中保存项目的版本或是构建信息是一个非常有用的方法。 CMake提供了configure_file()命令&#xff0c;可以帮助开发者在构建项目时&#xff0c;自动生成版本或是构建信息&#xff0c;便于开发者在代码中直接引…

Linux的进程管理和用户管理

gcc与g的区别 比如有两个文件&#xff1a;main.c mainc.cpp&#xff08;分别是用C语言和C语言写的&#xff09;如果要用gcc编译&#xff1a; gcc -o mainc main.c gcc -o mainc mainc.cpp -lstdc表明使用C标准库&#xff1b; 区别一&#xff1a; gcc默认只链接C库&#x…

【springcloud学习(dalston.sr1)】Eureka服务端集群的搭建(含源代码)(二)

该系列项目整体介绍及源代码请参照前面写的一篇文章【springcloud学习(dalston.sr1)】项目整体介绍&#xff08;含源代码&#xff09;&#xff08;一&#xff09; 这篇文章主要介绍多个eureka服务端的集群环境是如何搭建的。 &#xff08;一&#xff09;eureka的简要说明 Eu…

崩坏星穹铁道 3.3 版本前瞻活动攻略:在黎明升起时坠落

《崩坏星穹铁道》3.3 版本 “在黎明升起时坠落” 将于 5 月 21 日正式上线。本次版本更新内容丰富&#xff0c;新角色、新地图、新活动和新周本 BOSS 等精彩内容&#xff0c;等待开拓者们前去体验。下面就为大家带来 3.3 版本的前瞻活动攻略。 一、新角色与卡池 1.上半卡池&am…

OneNote内容太多插入标记卡死的解决办法

OneNote内容太多插入标记卡死的解决办法 针对平板电脑的OneNote用户适合此类情况&#xff1a; 当向电脑导入几百页pdf可以正常使用&#xff0c;唯独插入标记的时候OneNote直接罢工&#xff0c;只能关闭。关闭时还可能会出现0x000000fxxxxx的错误。 注&#xff1a;仅对于平板…

fpga系列 HDL : Microchip FPGA开发软件 Libero Soc 安装 license申请

启动 注册账号&#xff1a;https://login.microchip.com/申请免费许可&#xff1a;https://www.microchipdirect.com/fpga-software-products C:\Windows\System32>vol驱动器 C 中的卷是 Windows卷的序列号是 ****-****为“D:\Microsemi\License.dat”创建环境变量“LM_LICE…