C++_STL——list模拟实现

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


前言

  • list作为一个容器组件,有着其重要的价值,其实在底层这是一个带头双向循环的链表数据结构,可以在任意位置进行插入和删除的序列式容器,并且该容器可以前后进行遍历。
  • 那么同样作为序列式容器,list和vector相比又有哪些优缺点呢?
  1. 优点:对于在任意位置进行插入、删除操作,list的效率相对于vector是高很多的(这是由于vector在进行这些操作时需要移动数据进行覆盖)
  2. 缺点:list不支持任意位置的随机访问,这是由于list内部数据的存储方式并不是连续的,随机访问所需的代价较高,并且对缓存的利用率较低

因此,同样作为序列式容器,vector和list是优势互补,不可替代的,并且list在实际中的使用频率也很高,所以,为了更好的使用list,我们需要进行模拟实现。

list使用文档

在进行模拟实现之前,我们首先需要直到STL的list的各种使用接口以及其功能,然后才能高效的使用和模拟,下面是stl_list的使用文档及其说明:
list使用文档


模拟实现

节点struct

首先,我们直到对于带头双向循环链表来说,每个数据是存在一个节点中的,这个节点还要包括指向前一个节点和后一个节点的指针,所以我们需要用一个结构体来封装这些信息。

	template<typename T>
	struct list_node
	{
		list_node* next;
		list_node* prev;
		T val;
	};

类成员

在将要模拟实现的类中,我们想要找到所有的数据,只需要保存头节点即可,并且为了更简单的获得类的成员数量,可以在加一个size_t的_size成员标式数量。

	template<typename T>
	class list
	{
	private:
	    //这里为了方便,仿照源代码使用了typedef
	    //并且使用了c++11的缺省参数语法
		typedef list_node<T> link_node;
		link_node* _head = nullptr;
		size_t _size = 0;
	}

默认构造函数

带头双向循环链表的构造函数要求创建一个头节点,并且让其头节点的nextprev都指向自身。

		list()
			:_head(new link_node)
		{
			_head->next = _head;
			_head->prev = _head;
			_head->val = T();
		}

list的迭代器实现

我们知道,迭代器实现了stl容器和算法的分离,对于vector来说,迭代器使用原生的指针进行typedef来即可,但这是由于vector内部数据在物理空间上是连续的,但是对于list来说其数据在物理空间上并不是连续的,所以对于list来说,他的迭代器就不能是指针,而是一个类,通过重载运算符的方式,做到其使用方法能够和指针相同(对迭代器++使得指针指向next,–使得指针指向prev)下面是实现:

template<class T>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		Node* _node;

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

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

		__list_iterator<T>& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		__list_iterator<T> operator++(int)
		{
			__list_iterator<T> tmp(*this);

			_node = _node->_next;

			return tmp;
		}

		bool operator!=(const __list_iterator<T>& it)
		{
			return _node != it._node;
		}

		bool operator==(const __list_iterator<T>& it)
		{
			return _node == it._node;
		}
	};

这里,有人可能会不知道operator->()函数有什么用,那么可以看下面的代码:

	lzz::list<pair<int, int>> lt;
	lt.push_back({ 1,1 });
	lt.push_back({ 2,2 });
	lt.push_back({ 3,3 });
	lt.push_back({ 4,4 });
	lt.push_back({ 5,5 });
	auto it = lt.begin();
	while (it != lt.end())
	{
		//这里其实应该是it->->first(it.operator->()->first)
		//c++为了代码观赏性,对其进行了优化
		cout << it->first << " " << it->second << endl;
		it++;
	}

对于结构体,为了使得使用方式和指针一样,所以重载了该运算符。并且由于list的数据结构进行随机访问是极为低效的**o(n)**的复杂度,所以list的迭代器并没有支持+功能。


那么又该如何设计const_iterator呢?通过分析我们会发现,对于const_iterator类,只有operator*()operator->()函数需要修改返回值,其他则不需要变,如果重新写一个类命名为const_iterator,就过于冗余了,那么有什么更好的解决方法呢?
这里参考了库里的巧妙的实现方式,通过巧用模板完美的解决了这个问题,看下面代码:

//这里传输三个引用是为了让const引用和const指针能够不用重复设计
	template<typename T, typename Ref, typename Ptr>
	//为了便于在list里面使用迭代器,用struct而不是类
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		/*迭代器成员*/
		Node* _node;
		//构造函数
		__list_iterator(Node* x = nullptr)
			:_node(x)
		{}
		//析构函数
		//不需要析构函数,根据需求,iterator消失并不代表数据消失
		Ref operator*() { return _node->val; }
		self& operator++()
		{
			_node = _node->next;
			//返回的都是iterator
			return *this;
		}
		//这里比较直接用const防止权限放大问题
		bool operator!=(const __list_iterator& it) { return _node != it._node; }
		Ptr operator->() { return &(_node->val); }
		self operator++(int)
		{
			self tmp(*this);
			_node = _node->next;
			return tmp;
		}
		bool operator==(const self& it) { return _node == it._node; }
		self& operator--()
		{
			_node = _node->prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			_node = _node->prev;
			return tmp;
		}
	};

上面代码多传入了两个模板参数,分别是Ref和Ptr,并且分别用其替代了operator*()和operator->()的返回值,然后通过控制模板参数就可以控制返回值的类型了!
在类中可以使用typedef来进行使用,如下:

class list
{
	typedef __list_iterator<T, T&, T*> iterator;
	//错误的设计,类比vector<const int>
	//typedef __list_iterator<const T> const_iterator;
	//如果使用const T类型,就表示了内部指针不能够进行改变,那如何修改他指向下一个或上一个指针?
	typedef __list_iterator<T, const T&, const T*> const_iterator;
}

需要啥传啥,就能够进行更好的控制了!

begin(),end()

**begin()😗*直接返回头节点的下一个指针(第一个位置指向的指针),利用隐式类型转换自动构造迭代器
**end()😗*直接返回头节点,利用隐式类型转换构造迭代器

iterator begin() { return _head->next; }
iterator end() { return _head; }
const_iterator begin() const { return const_iterator(_head->next); }
const_iterator end() const { return const_iterator(_head); }

list的增删查改等操作

insert,erase

对于链表来说,insert和erase操作都是在修改数据之间的链接关系罢了,大家可以画图理解,但是要注意链接前后关系

		iterator insert(const iterator& pos, const T& val)
		{
			link_node* tmp = new link_node;
			tmp->val = val;
			tmp->next = pos._node;
			tmp->prev = pos._node->prev;
			pos._node->prev->next = tmp;
			pos._node->prev = tmp;
			//记得维护_size
			++_size;
			return tmp;
		}
		iterator erase(iterator pos)
		{
			//不能删去头节点
			assert(pos != end());
			link_node* tmp = pos._node;
			tmp->prev->next = tmp->next;
			tmp->next->prev = tmp->prev;
			//别忘了释放空间
			++pos;
			delete tmp;
			--_size;
			return pos;
		}

头尾删插

对于这部分,可以直接复用insert和erase即可,这也是先实现insert和erase的原因

		void push_back(const T& val) { insert(end(), val); }
		void pop_back()
		{
			assert(!empty());
			erase(--end());
		}
		void push_front(const T& val) { insert(begin(), val); }
		void pop_front()
		{ 
			assert(!empty());
			erase(begin());
		}

同样由于list随机访问极为低效,所以该容器并不支持operator[]()函数功能

取头尾元素和元素个数

T& front() { return _head->next->val; }
const T& front() const { return _head->next->val; }
T& back() { return _head->prev->val; }
const T& back() const { return _head->prev->val; }
size_t size() { return _size; }

clear()清空数据以及析构函数

在通过遍历一个个delete节点时,要记得先保存下一个节点,否则析构之后再使用当前节点就出现了野指针问题。

		void clear()
		{
			//不删除头节点
			link_node* del = _head->next;
			while (del != _head)
			{
				link_node* ne = del->next;
				delete del;
				del = ne;
			}
			_size = 0;
		}

析构与clear()的主要区别就是析构需要释放头节点,而clear()函数不需要清空,所以析构可以先调用clear()函数,然后再释放头节点即可。

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

完整模拟实现代码

#pragma once
#include<assert.h>
namespace lzz
{
	template<typename T>
	struct list_node
	{
		list_node* next;
		list_node* prev;
		T val;
	};

	//这里传输三个引用是为了让const引用和const指针能够不用重复设计
	template<typename T, typename Ref, typename Ptr>
	//为了便于在list里面使用迭代器,用struct而不是类
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		/*迭代器成员*/
		Node* _node;
		//构造函数
		__list_iterator(Node* x = nullptr)
			:_node(x)
		{}
		//析构函数
		//不需要析构函数,根据需求,iterator消失并不代表数据消失
		Ref operator*() { return _node->val; }
		self& operator++()
		{
			_node = _node->next;
			//返回的都是iterator
			return *this;
		}
		//这里比较直接用const防止权限放大问题
		bool operator!=(const __list_iterator& it) { return _node != it._node; }
		Ptr operator->() { return &(_node->val); }
		self operator++(int)
		{
			self tmp(*this);
			_node = _node->next;
			return tmp;
		}
		bool operator==(const self& it) { return _node == it._node; }
		self& operator--()
		{
			_node = _node->prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			_node = _node->prev;
			return tmp;
		}
	};

	template<typename T>
	class list
	{
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		//错误的设计,类比vector<const int>
		//typedef __list_iterator<const T> const_iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef Reverse_iterator <const_iterator, const T&, const T*> constReverseIterator;
	private:
		typedef list_node<T> link_node;
		link_node* _head = nullptr;
		size_t _size = 0;
	public:
		//void empty_init()
		//带头双向循环链表初始化
		list()
			:_head(new link_node)
		{
			_head->next = _head;
			_head->prev = _head;
			_head->val = T();
		}
		//这里用int是为了防止和之后的迭代器区间初始化产生冲突
		list(int n, const T& val = T())
			:list()
		{
			for (; _size < n;)
				push_back(val);
		}
		//深拷贝
		list(const list<T>& lt)
			:list()
		{
			for (auto& e : lt)
				push_back(e);
		}
		template<typename InputIterator>
		list(InputIterator first, InputIterator last)
			:list()
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		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 push_back(const T& val)
		{
			link_node* tmp = new link_node;
			tmp->val = val;
			tmp->next = _head;
			_head->prev->next = tmp;
			tmp->prev = _head->prev;
			_head->prev = tmp;
		}*/
		bool empty() { return !_size; }
		//复用写法
		void push_back(const T& val) { insert(end(), val); }
		void pop_back()
		{
			assert(!empty());
			erase(--end());
		}
		void push_front(const T& val) { insert(begin(), val); }
		void pop_front()
		{ 
			assert(!empty());
			erase(begin());
		}

		iterator begin() { return _head->next; }
		iterator end() { return _head; }
		const_iterator begin() const { return const_iterator(_head->next); }
		const_iterator end() const { return const_iterator(_head); }
		iterator insert(const iterator& pos, const T& val)
		{
			link_node* tmp = new link_node;
			tmp->val = val;
			tmp->next = pos._node;
			tmp->prev = pos._node->prev;
			pos._node->prev->next = tmp;
			pos._node->prev = tmp;
			++_size;
			return tmp;
		}
		iterator erase(iterator pos)
		{
			//不能删去头节点
			assert(pos != end());
			link_node* tmp = pos._node;
			tmp->prev->next = tmp->next;
			tmp->next->prev = tmp->prev;
			//别忘了释放空间
			++pos;
			delete tmp;
			--_size;
			return pos;
		}
		size_t size() { return _size; }
		void clear()
		{
			//不删除头节点
			link_node* del = _head->next;
			while (del != _head)
			{
				link_node* ne = del->next;
				delete del;
				del = ne;
			}
			_size = 0;
		}
		T& front() { return _head->next->val; }
		const T& front() const { return _head->next->val; }
		T& back() { return _head->prev->val; }
		const T& back() const { return _head->prev->val; }
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
	};
}

总结

对于list底层和vector,string的底层模拟来说,最大的难点就时list的迭代器需要自己写一个类封装起来,而另两个由于数据存储空间连续直接使用原生指针即可,但是能使用指针的情况也就只有这两种,stl的其他容器的迭代器基本都是用类封装的,但这种设计思路大体时相同的,这也是stl设计的强大之处了!大家在以后学习其他stl容器时,会越来越体会到这种容器的巧妙的!
以上就是本篇博客模拟实现list的全部内容,如果还有不懂的或者作者写的有问题的地方,欢迎在评论区提出!文章来源地址https://www.toymoban.com/news/detail-604157.html

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

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

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

相关文章

  • 【STL】list的模拟实现

    目录 前言 结构解析 默认成员函数 构造函数 拷贝构造 赋值重载 析构函数 迭代器 const迭代器 数据修改 insert erase 尾插尾删头插头删 容量查询 源码  🍉list之所以摆脱了单链表尾插麻烦,只能单向访问等缺点,正是因为其在结构上升级成了带头双向循环链表。不仅如此,lis

    2024年02月06日
    浏览(42)
  • C++ STL->list模拟实现

    list list文档 list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。 list与forward_list非常相似:最主

    2024年02月20日
    浏览(44)
  • C++_STL——list模拟实现

    list作为一个容器组件,有着其重要的价值,其实在底层这是一个 带头双向循环的链表数据结构 ,可以在任意位置进行插入和删除的序列式容器,并且该容器可以前后进行遍历。 那么同样作为序列式容器,list和vector相比又有哪些优缺点呢? 优点 :对于在任意位置进行插入、

    2024年02月16日
    浏览(37)
  • C++STL的list模拟实现

    要实现STL的list, 首先我们还得看一下list的源码。 我们看到这么一个东西,我们知道C++兼容C,可以用struct来创建一个类。但是我们习惯用class。 那什么时候会用struct呢? 这个类所有成员都想开放出去,比如结点的指针,它一般开放出来。所以我们用struct.。 继续看源码比较重

    2024年02月04日
    浏览(43)
  • 【C++】STL---list的模拟实现

    上次模拟实现了一个vector容器,那么我们这次来实现一个list(链表)容器,链表在实际的开发中并不常见。但是也是一种很重要的数据结构,下面给大家介绍一下链表(list) 和 vector(顺序表)的区别。 list 和 vector 一样,是一个存储容器。不同的是vector在内存中是连续存储的,而

    2024年01月22日
    浏览(53)
  • C++ ——STL容器【list】模拟实现

    代码仓库: list模拟实现 list源码 数据结构——双向链表 源码的list是双向带头循环链表,所以我们定义两个节点,一个指向下一个,一个指向前一个 list类包含一个 _head 头节点,然后为了方便查出当前有多少个节点,还能多定义一个 _size 源码的迭代器设置了三个模板参数:

    2024年02月15日
    浏览(46)
  • [ C++ ] STL---list的模拟实现

    目录 结点类的模拟实现 迭代器类的模拟实现 构造函数 前置++与后置++ 前置- -与后置 - - == 与 !=运算符重载 * 运算符重载 - 运算符重载 普通迭代器总体实现代码 list类的实现 list类的成员变量 构造函数 迭代器 insert() erase() push_front/push_back/pop_front/pop_back front/back clear() empty()

    2024年04月09日
    浏览(60)
  • C++ [STL之list模拟实现]

    本文已收录至《C++语言》专栏! 作者:ARMCSKGT list的底层与vector和string不同,实现也有所差别,特别是在迭代器的设计上,本节将为大家介绍list简单实现,并揭开list迭代器的底层! 本文介绍list部分简单接口,以list迭代器的介绍为主! list底层是一个带头双向循环链表,在节

    2024年02月09日
    浏览(39)
  • 【STL】“list“容器从使用到模拟实现

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

    2024年02月16日
    浏览(86)
  • 【C++STL】list的使用及其模拟实现

    list和sting、vector一样,我们可以使用cplusplus文档进行查询:list的文档介绍 【总结】 1.list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代 2.list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通

    2023年04月19日
    浏览(72)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包