list的模拟实现

这篇具有很好参考价值的文章主要介绍了list的模拟实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

        listSTL中重要的容器,了解它的原理对于我们掌握它是有很多的帮助的,一般listvector都是一起来使用的,因为它们的优缺点不同,刚好可以互补。list的优点是任意位置的插入和删除都很快,它的缺点是不支持随机访问,而vector的优点是支持随机访问,进而就可以很好的支持排序算法,二分查找,堆算法等,它的缺点是扩容要付出一定的代价,而且除了尾上的插入和删除外其他位置的插入和删除都不快(因为要挪动数据)。下面让我们一起来实现一下list吧。

目录

list的实现代码 

1.构造函数和析构函数

        1.1构造函数

        2.2析构函数 

2.operator=的重载和拷贝构造函数

        2.2拷贝构造

        2.2operator=的重载 

3.迭代器的实现

        3.1普通迭代器

        3.2const类型的迭代器

4.增删查改

        4.1尾删尾插

        4.2任意位置的插入和删除

         4.3头插头删

 5.测试代码


list的实现代码 

#include<iostream>
using namespace std;
namespace qyy
{
template<class T>
struct __list_node
{
	__list_node(const T& data = T())//构造函数函数
		:_prev(nullptr)
		, _next(nullptr)
		, _data(data)
	{ }
	__list_node<T>* _prev;//前指针
	__list_node<T>* _next;//后指针
	T _data;//数据
};

template<class T, class Ref, class Ptr>
struct __list_iterator//对链表的迭代器进行封装
{
	typedef __list_node<T> Node;
	typedef __list_iterator< T,  Ref,  Ptr> iterator;
	__list_iterator(Node* node)//构造函数
		:_node(node)
	{ }
	//对指针行为的模仿
	Ref operator* ()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &(_node->_data);
	}
	iterator& operator++()//前置++
	{
		_node = _node->_next;
		return *this;
	}
	iterator& operator--()//前置--
	{
		_node = _node->_prev;
		return *this;
	}
	iterator operator++(int)//后置++
	{
		//Node tmp(_node);
		iterator tmp(*this);
		_node = _node->_next;
		return tmp;
	}
	iterator operator--(int)//后置--
	{
		//Node tmp(_node);
		iterator tmp(*this);

		_node = _node->_prev;
		return tmp;
	}
	bool operator==(const iterator& it)
	{
		return _node == it._node;
	}
	bool operator!=(const iterator& it)
	{
		return _node != it._node;
	}
	
public:
	Node* _node;//存放一个节点的指针
};

template<class T>
class list//带头双向循环链表
{
public:
	typedef __list_node<T> Node;//节点的重定义
	typedef __list_iterator<T,T&,T*> iterator;//迭代器重定义
	typedef __list_iterator<T, const T&, const T*> const_iterator;
	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);
	}
	list()//构造函数
		:_head(new Node)
	{
		_head->_next = _head;
		_head->_prev = _head;
	}
	list(const list<T>& l1)//拷贝构造
	{
		_head = new Node;//初始化头结点
		_head->_next = _head;
		_head-> _prev = _head;
		const_iterator it = l1.begin();
		while (it != l1.end())
		{
			push_back(it._node->_data);//将节点插入
			++it;
		}
	}

	list<T>& operator=(list<T> l1)//现代写法
	{
		if (this != &l1)
		{
			swap(_head, l1._head);
		}
		return *this;
	}
	list<T> operator=(const list<T> l1)const
	{
		clear();
		const_iterator it = l1.begin();
		while (it != l1.end())
		{
			push_back(it._node->_data);//将节点插入
			++it;
		}
		return *this;
	}
	~list()
	{
		clear();//删除头结点以外的其他节点
		delete _head;//删除头结点
	}
	void clear()//除了头结点剩下的节点全部删除
	{
		if (begin() != end())//判断不能删除头结点
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//删除当前节点之后迭代器就会 失效,所以要用it来接收下一个节点的迭代器
			}
		}
	}
	void push_back(const T& data)//尾插
	{
		//Node* tail = _head->_prev;

		//Node* newNode = new Node(data);//申请节点
		三个节点的相互链接
		//tail->_next = newNode;
		//newNode->_prev = tail;

		//_head->_prev = newNode;
		//newNode->_next = _head;
		insert(end(), data);//调用任意节点的插入来实现头删
	}
	void pop_back()//尾删
	{
		//assert(_head->_next != _head);//除了头结点还有其他节点——因为头结点是不可以被删除的
		//Node* tail = _head->_prev;//找到尾节点
		断开链接
		//Node* newtail = tail->_prev;//找新的尾节点
		进行头尾链接
		//newtail->_next = _head;
		//_head->_prev = newtail;
		//delete tail;//删除尾节点
		//调用erase进行尾删
		erase(--end());
	}
	void push_front(const T&data)//头插
	{
		//Node* newHead = new Node(data);//申请新的节点
		//Node* oldHead = _head->_next;
		进行链接
		//_head->_next = newHead;
		//newHead->_prev = _head;

		//newHead->_next = oldHead;
		//oldHead->_prev = newHead;
		//调用任意节点的插入和删除
		insert(begin(), data);
	}
	void pop_front()//头删
	{
		//assert(_head->_next != _head);//除了头结点还有其他节点——因为头结点是不可以被删除的
		//Node* head = _head->_next;//找到头节点
		断开链接
		//Node* newHead = head->_next;//找新的尾节点
		进行链接
		//newHead->_prev = _head;
		//_head->_next= newHead;
		//delete head;//删除尾节点
		//调用erase进行头删
		erase(begin());
	}
	
	void insert(iterator  pos, const T& data)//
	{
		//申请新节点
		Node* newNode = new Node(data);
		Node* prev = pos._node->_prev;//迭代器前一个节点
		Node* next =pos._node;//迭代器当前节点
		prev->_next = newNode;
		newNode->_prev = prev;
		next->_prev = newNode;
		newNode->_next = next;
	}
	iterator erase(iterator pos)//删除pos位置的值
	{
		assert(pos._node !=_head);//不能删除头结点
		Node* cur = pos._node;
		Node* prev = pos._node->_prev;
		Node* next = pos._node->_next;
		//断开当前迭代器所在节点的位置,将迭代器前后节点进行链接
		prev->_next = next;
		next->_prev = prev;
		//保存下一个位置的迭代器
		iterator  it1= ++pos;//记录下一个位置的迭代器
		//删除当前迭代器的节点

		delete cur;
		cur = nullptr;
		return it1;//返回下一个位置的迭代器
	}
private:
	Node* _head;//头结点
};
}

1.构造函数和析构函数

        1.1构造函数

        构造函数主要完成的是对头结点的初始化,如下:

template<class T>//先定义结点类
struct __list_node
{
	__list_node(const T& data = T())//构造函数函数
		:_prev(nullptr)
		, _next(nullptr)
		, _data(data)
	{ }
	__list_node<T>* _prev;//前指针
	__list_node<T>* _next;//后指针
	T _data;//数据
};
template<class T>//构造函数多头结点进行初始化
list()//构造函数
		:_head(new Node)
{
	_head->_next = _head;
	_head->_prev = _head;
}

        2.2析构函数 

        析构函数主要完成对资源的清理,这里通过调用clear对链表的节点进行删除。最后在删除头结点。 

~list()
{
	clear();//删除头结点以外的其他节点
	delete _head;//删除头结点
}

2.operator=的重载和拷贝构造函数

        2.2拷贝构造

        这里是通过对头结点进行初始化,之后调用push_back函数尾插数据,完成的拷贝构造。 

list(const list<T>& l1)//拷贝构造
{
	_head = new Node;//初始化头结点
	_head->_next = _head;
	_head-> _prev = _head;
	const_iterator it = l1.begin();
	while (it != l1.end())
	{
			push_back(it._node->_data);//将节点插入
			++it;
	}
}

        2.2operator=的重载 

        operator=赋值有两种写法,一种是传统写法另一种是现代写法,如下:

//传统写法--
list<T>& operator=(const list<T> l1)
{
	if (this != &l1)//防止对象自己给自己赋值
	{
		clear();//先清理之前链表中的内容,然后通过调用push_back将l1中的内容插入
		const_iterator it = l1.begin();
		while (it != l1.end())
		{
		    push_back(it._node->_data);//将节点插入
			++it;
		}
		return *this;
	}
}
//现代写法
list<T>& operator=(list<T> l1)//现代写法
{
	if (this != &l1)//先拷贝构造一个对象,然后调用STL中的swap函数交换this指向的头结点的指针和 
	{               //l1中头结点指针,出了作用域临时对象l1就会被析构,不要担心内存泄漏的问题
		swap(_head, l1._head);
	}
return *this;
}

3.迭代器的实现

        list的迭代器比较特殊,他不是原生的指针,而是将节点的指针进行封装,然后重载operator*,operator++,operator--等与指针相关的操作符来模拟指针的行为。 

        3.1普通迭代器

       

    template<class T>
	struct __list_node
	{
		__list_node(const T & data = T())//构造函数函数
			:_prev(nullptr)
			,_next(nullptr)
			,_data(data)
		{ }
		__list_node<T>* _prev;//前指针
		__list_node<T>* _next;//后指针
		T _data;//数据
	};
	template<class T>
	struct __list_iterator//对链表的节点进行封装
	{
		typedef __list_node<T> Node;
		__list_iterator(Node*node)//构造函数
			:_node(node)
		{ }
		//对指针行为的模仿
		T& operator* ()
		{
			return _node->_data;
		}
		__list_iterator<T>& operator++()//前置++
		{
			_node = _node->_next;
			return *this;
		}
		__list_iterator<T>& operator--()//前置--
		{
			_node = _node->_prev;
			return *this;
		}
		__list_iterator<T> operator++(int)//后置++
		{
			Node tmp(_node);
			_node = _node->_next;
			return tmp;
		}
		__list_iterator<T> operator--(int)//后置--
		{
			Node tmp(_node);
			_node = _node->_prev;
			return tmp;
		}
		bool operator==(const __list_iterator<T>&it)
		{
			return _node == it._node;
		}
		bool operator!=(const __list_iterator<T>& it)
		{
			return _node != it._node;
		}
		T* operator->()
		{
			return &(_node->_data);
		}
	public:
		Node  *_node;//存放一个节点的指针
	};
	

        注意:实现operator->的意义,如果有下面的这种情况,list存的不是一个内置类型而是一个自定义类型,这时想要通过迭代器去访问里面的成员,就会用来operator->,如下:

    class Date
	{
	public:
		Date()
			:_year(0)
			, _month(1)
			, _day(1)
		{ }
	public:
		int _year;
		int _month;
		int _day;
	};
    void TestList2()
	{
		list <Date> l1;
		Date d1, d2;
		l1.push_back(d1);
		l1.push_back(d2);
		list<Date>::iterator it = l1.begin();
		cout << it->_year << it->_month << it->_day;//访问list中存放的Date
	}

         其实 it->_yearit->_node->_year,只是编译器对其进行了简化。

        3.2const类型的迭代器

        const类型的对象只能使用const类型的迭代器,那么const类型的迭代器如何实现呢、需要再重新封装吗,像上面那样,可以,但是没有必要,因为那样代码的冗余度就会很高,我们只需要给模板增加两个参数就可以了。template<class T, class Ref, class Ptr>。实现如下:

template<class T, class Ref, class Ptr>
struct __list_iterator//对链表的迭代器进行封装
{
	typedef __list_node<T> Node;
	typedef __list_iterator< T,  Ref,  Ptr> iterator;
	__list_iterator(Node* node)//构造函数
		:_node(node)
	{ }
	//对指针行为的模仿
	Ref operator* ()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &(_node->_data);
	}
	iterator& operator++()//前置++
	{
		_node = _node->_next;
		return *this;
	}
	iterator& operator--()//前置--
	{
		_node = _node->_prev;
		return *this;
	}
	iterator operator++(int)//后置++
	{
		//Node tmp(_node);
		iterator tmp(*this);
		_node = _node->_next;
		return tmp;
	}
	iterator operator--(int)//后置--
	{
		//Node tmp(_node);
		iterator tmp(*this);

		_node = _node->_prev;
		return tmp;
	}
	bool operator==(const iterator& it)
	{
		return _node == it._node;
	}
	bool operator!=(const iterator& it)
	{
		return _node != it._node;
	}
	
public:
	Node* _node;//存放一个节点的指针
};

        这样看,貌似看不出来这两个参数有什么用,这两个参数一个代表T*,一个代表T&,这样就可以解决代码冗余的问题,在实例化的时候给模板不同的参数就可以实例化不同的内容list的模拟实现

4.增删查改

        4.1尾删尾插

	void push_back(const T& data)//尾插
	{
		Node* tail = _head->_prev;

		Node* newNode = new Node(data);//申请节点
		//三个节点的相互链接
		tail->_next = newNode;
		newNode->_prev = tail;

		_head->_prev = newNode;
		newNode->_next = _head;
		
	}
	void pop_back()//尾删
	{
		assert(_head->_next != _head);//除了头结点还有其他节点——因为头结点是不可以被删除的
		Node* tail = _head->_prev;//找到尾节点
		//断开链接
		Node* newtail = tail->_prev;//找新的尾节点
		//进行头尾链接
		newtail->_next = _head;
		_head->_prev = newtail;
		delete tail;//删除尾节点
	}

        4.2任意位置的插入和删除

    void insert(iterator  pos, const T& data)//
	{
		//申请新节点
		Node* newNode = new Node(data);
		Node* prev = pos._node->_prev;//迭代器前一个节点
		Node* next =pos._node;//迭代器当前节点
		prev->_next = newNode;
		newNode->_prev = prev;
		next->_prev = newNode;
		newNode->_next = next;
	}
	iterator erase(iterator pos)//删除pos位置的值
	{
		assert(pos._node !=_head);//不能删除头结点
		Node* cur = pos._node;
		Node* prev = pos._node->_prev;
		Node* next = pos._node->_next;
		//断开当前迭代器所在节点的位置,将迭代器前后节点进行链接
		prev->_next = next;
		next->_prev = prev;
		//保存下一个位置的迭代器
		iterator  it1= ++pos;//记录下一个位置的迭代器
		//删除当前迭代器的节点

		delete cur;
		cur = nullptr;
		return it1;//返回下一个位置的迭代器
	}

         4.3头插头删

        头插头删可以复用上面的insert和erase,如下:文章来源地址https://www.toymoban.com/news/detail-490978.html

	void push_front(const T&data)//头插
	{
		insert(begin(), data);
	}
	void pop_front()//头删
	{
		erase(begin());
	}

 5.测试代码

#include<assert.h>
#include"list.hpp"
 void Print(const qyy::list<int>& l1);
 //测试
void TestList()
{
	qyy::list<int>l1;
	//头删
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(5);
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	Print(l1);
	//尾删
	l1.pop_back();
	l1.pop_back();
	l1.pop_back();
	l1.pop_back();
	l1.pop_back();


}
void Print(const qyy::list<int>   &l3)
{
	qyy::list<int>::const_iterator it = l3.begin();
	while (it != l3.end())
	{
		//*it = 1;
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
void TestList2()
{
	qyy::list<int> l2;
	//头插
	l2.push_front(1);
	l2.push_front(2);
	l2.push_front(3);
	l2.push_front(4);
	l2.push_front(5);
	l2.push_front(6);
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	//头删
	l2.pop_front();
	l2.pop_front();
	l2.pop_front();
	l2.pop_front();
	l2.pop_front();
	l2.pop_front();
	//l2.pop_front();
	//l2.clear();
}
void TestList3()
{
	qyy::list<int> l3;
    //头插
	l3.push_back(1);
	l3.push_back(2);
	l3.push_back(3);
	l3.push_back(4);
	l3.push_back(5);
	for (auto& e : l3)
		cout << e << " ";
	qyy::list<int> l4(l3);
	l3 = l4;//测试operator=
}

到了这里,关于list的模拟实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【STL】“list“容器从使用到模拟实现

    🎉博客主页:小智_x0___0x_ 🎉欢迎关注:👍点赞🙌收藏✍️留言 🎉系列专栏:C++初阶 🎉代码仓库:小智的代码仓库 list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 list的底层是 双向链表结构 ,双向链表中每个元素存储在

    2024年02月16日
    浏览(87)
  • C++ stl容器list的底层模拟实现

    目录 前言: 1.创建节点 2.普通迭代器的封装 3.反向迭代器的封装 为什么要对正向迭代器进行封装? 4.const迭代器 5.构造函数 6.拷贝构造 7.赋值重载 8.insert 9.erase 10.析构 11.头插头删,尾插尾删 12.完整代码+简单测试 总结: 1.创建节点 注意给缺省值,这样全缺省就会被当做默认

    2024年04月23日
    浏览(45)
  • 【C++】STL之list容器的模拟实现

    个人主页:🍝在肯德基吃麻辣烫 分享一句喜欢的话:热烈的火焰,冰封在最沉默的火山深处。 本文章进入C++STL之list的模拟实现。 在STL标准库实现的list中,这个链表是一个== 双向带头循环链表==。 说明: list是一个类,成员变量为_head 节点类node,是每一个节点。 list的迭代

    2024年02月17日
    浏览(52)
  • STL容器 -- list的模拟实现(配详细注释)

    C++ STL(Standard Template Library,标准模板库)提供了一组通用的模板类和函数,用于实现常用的数据结构和算法。其中之一是 std::list,它实现了一个双向链表。 std::list 是一个容器,用于存储一系列的值。与数组和向量等连续存储的容器不同,std::list 使用链表作为底层数据结构

    2024年02月16日
    浏览(50)
  • 【C++】容器篇(二)——List的基本概述以及模拟实现

    前言: 在上期,我们学习了STL库中的第一个容器--vector ,今天我将给大家介绍的是 库中的另外一个容器--List。其实,有了之前学习 vector 的知识,对于List 的学习成本就很低了。 目录 (一)基本介绍 1、基本概念 2、list 与 forward_list 的比较 3、特点 (二)list的使用 1、list的

    2024年02月06日
    浏览(45)
  • 【C++】反向迭代器的模拟实现通用(可运用于vector,string,list等模拟容器)

    🌏博客主页: 主页 🔖系列专栏: C++ ❤️感谢大家点赞👍收藏⭐评论✍️ 😍期待与大家一起进步! 我们要写出一个通用的反向迭代器模拟而且在保证代码简介不繁琐的的情况下,一定程度上使用我们自己模拟的已经封装好的iterator迭代器可以简化许多步骤,首先我们要知

    2024年02月14日
    浏览(56)
  • 面试官:你了解axios的原理吗?有看过它的源码吗?

    关于 axios 的基本使用,上篇文章已经有所涉及,这里再稍微回顾一下: 发送请求 请求拦截器 响应拦截器 取消请求 构建一个 Axios 构造函数,核心代码为 request 方法: 导出 axios 实例: 上述就已经能够实现 axios({ }) 这种方式的请求。下面是来实现下 axios.method() 这种形式的请

    2024年02月07日
    浏览(47)
  • 深入解读 Axios 拦截器:全面了解它的工作原理和实际应用

     Axios提供了一种称为 “拦截器(interceptors)” 的功能,使我们能够在请求或响应被发送或处理之前对它们进行全局处理。拦截器为我们提供了一种简洁而强大的方式来转换请求和响应、进行错误处理、添加认证信息等操作。在本文中,我们将深入探讨如何使用 Axios 的拦截器

    2024年04月09日
    浏览(47)
  • List集合以及它的实现类和队列、栈

    Collection层次的结构接口中,一些允许有重复的元素,例如:List接口。而另外一些不允许有重复的元素,例如:Set接口。其中也分为了有序与无序的(存储顺序)。 在JDK中没有提供Collection接口的实现类,但是提供了更加具体的子接口(如Set、List和Queue接口)。 现在我们具体

    2023年04月13日
    浏览(47)
  • [C++] STL_priority_queue(优先级队列) 的使用及底层的模拟实现,容器适配器,deque的原理介绍

    priority_queue文档介绍 翻译: 1. 优先队列是一种 容器适配器 ,根据严格的弱排序标准, 它的第一个元素总是它所包含的元素中最大的。 2. 此上下文类似于 堆 , 在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。 3. 优先队列被实现为容器适配

    2024年02月04日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包