【C++初阶】list的模拟实现 附源码

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

一.list介绍

list底层是一个双向带头循环链表,这个我们以前用C语言模拟实现过,->双向带头循环链表

下面是list的文档介绍: list文档介绍

我们会根据 list 的文档来模拟实现 list 的增删查改及其它接口。

【C++初阶】list的模拟实现 附源码,C++初阶,c++,开发语言,list,数据结构


 文章来源地址https://www.toymoban.com/news/detail-581625.html

二.list模拟实现思路

既然是用C++模拟实现的,那么一定要封装在类里

为了适合各种类型的数据,会使用模板

节点 Node

了解双向循环带头链表的都知道,我们需要一个节点 (Node),之前用C语言实现的时候,我们写了一个叫做 BuynewNode 的函数来获取节点,而在C++里我们用类封装一个,注意这个用 struct 封装比较好,因为 struct 默认是公有的,这样方便我们访问,所以可以写一个类:

    struct  list_node

迭代器  iterator

我们知道,C++提供了一种统一的方式来访问容器,这就是迭代器,string 和 vector 的迭代器模拟实现很简单,因为 string 和 vector 底层是用数组实现的,数组是一段连续的物理空间,支持随机访问,所以它是天然的迭代器

但是链表不一样,它不是一段连续的物理空间,不支持随机访问,所以想让 list 的迭代器在表面上和 string,vector 的迭代器用起来没有区别,我们在底层上就需要用类封装迭代器,然后再迭代器类的内部,重载  ++  --  *  ->  !=  ==  这些迭代器会用到的运算符

所以创建一个迭代器类:

   struct  list_iterator

const 迭代器  const_iterator

实现的普通的迭代器,还有 const 迭代器,const 迭代器的意思是让指针指向的内容不变,而指针本身可以改变,例如指针++,指针-- 这种操作,所以 const 迭代器与普通迭代器的不同只有 重载 * 运算符的返回值不同,它是 const  T&  (T是模板参数),重写一个const 迭代器类又显得太冗余,代码的可读性就降低了;

前面在学习模板时,我们知道不同的模板参数,编译器会生成不同的函数,所以我们选择加一个模板参数 :Ref 。这样只要在显示实例化模板参数时:

              普通迭代器就传 T&

              const 迭代器就传 const T&

-> 运算符重载

看下面这段代码:

using namespace std;

struct A
{
	A(int a1,int a2)
		:_a1(a1)
		,_a2(a2)
	{}

	int _a1;
	int _a2;
};

void test_list()
{
	list<A> lt;   //实例化自定义类型
	lt.push_back(A(1, 1));
	lt.push_back(A(2, 2));
	lt.push_back(A(3, 3));
	lt.push_back(A(4, 4));

	list<A>::iterator it = lt.begin();

	while (it != lt.end())
	{
		cout << it->_a1 << " " << it->_a2 << endl;  //像指针一样访问自定义类型里的成员变量
		it++;
	}	
}

int main()
{
	test_list();

	return 0;
}

有时候,实例化的模板参数是自定义类型,我们想要像指针一样访问访问自定义类型力的成员变量,这样显得更通俗易懂,所以就要重载 -> 运算符,它的返回值是 T* ,但是正常来说这里应该是这样访问的: it -> -> _a1

因为迭代器指向的是 整个自定义类型,要想再访问其内部成员应该再使用一次 -> (这个->就不是重载的 -> ,就是普通的 -> ),但是上面的代码为什么就写了一个 -> ,这个是C++语法把这里特殊化了。

那该怎么在迭代器类里重载 -> 运算符呢?

和const 迭代器一样,只需要再加一个模板参数 :Ptr

显示实例化的时候传 T* 就行了。 

迭代器类 模拟实现源码: struct list_iterator

以上的都算 list 模拟实现的难点,其他的像 重载 ++ 什么的,对于学过数据结构的小伙伴们是非常简单的,就不赘述了,没学过的可以看看这篇文章:双向带头循环链表

template<class T,class Ref,class Ptr>   //三个模板参数
	struct list_iterator   //封装迭代器
	{
		typedef list_node<T> Node;  //重命名节点
		typedef list_iterator<T, Ref, Ptr> self;  //重命名迭代器类型
		Node* _node;

		list_iterator(Node*node)   //构造函数,单参数的构造函数支持隐式类型转换
			:_node(node)
		{}

		//重载 * ++ -- != == ->
		Ref operator*() const
		{
			return _node->_val;
		}

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

		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& lt) const
		{
			return _node != lt._node;
		}

		bool operator==(const self& lt) const
		{
			return _node == lt._node;
		}

	};

list

我们在用C语言实现双向带头循环链表时,会先初始化链表的头(head),即让它的 前驱指针(prev)和后继指针(next)都指向自己

在C++的模拟实现 list 中,我们会创建一个类 list  来管理链表的节点并实现增删查改及其它接口,所以 list  的构建函数就是初始化 头(head)节点


三.源码

list.h

【C++初阶】list的模拟实现 附源码,C++初阶,c++,开发语言,list,数据结构

【C++初阶】list的模拟实现 附源码,C++初阶,c++,开发语言,list,数据结构

【C++初阶】list的模拟实现 附源码,C++初阶,c++,开发语言,list,数据结构 

我们可以模拟实现以上接口,具体函数的逻辑可以查阅文档,实现起来都是很简单的。

namespace nagi   //把模拟实现list的类都放在一个命名空间里封装起来
{
	template<class T>
	struct list_node   //创建节点
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _val;

		list_node(const T& val = T())  //构造函数,初始化节点
			:_prev(nullptr)
			,_next(nullptr)
			,_val(val)
		{}

	};

	template<class T,class Ref,class Ptr>   //三个模板参数
	struct list_iterator   //封装迭代器
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> self;
		Node* _node;

		list_iterator(Node*node)   //构造函数,单参数的构造函数支持隐式类型转换
			:_node(node)
		{}

		//重载 * ++ -- != == ->
		Ref operator*() const
		{
			return _node->_val;
		}

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

		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& lt) const
		{
			return _node != lt._node;
		}

		bool operator==(const self& lt) const
		{
			return _node == lt._node;
		}

	};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		typedef list_iterator<T, T&, T*> iterator;  //重命名普通迭代器
		typedef list_iterator<T, const T&, const T*> const_iterator;  //重命名const迭代器

		void empty_init()  //因为构造函数和拷贝构造都会初始化头节点,所以就写成一个函数了
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
			_sz = 0;
		}

		list()  //构造函数
		{
			empty_init();
		}
		//普通迭代器
		iterator begin()
		{
			return _head->_next;
		}

		iterator end()
		{
			return _head;
		}
		//const迭代器
		const_iterator begin() const
		{
			return _head->_next;
		}

		const_iterator end() const
		{
			return _head;
		}

		iterator insert(iterator pos, const T& x)  //在pos之前插入
		{
			Node* newnode = new Node(x);
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			_sz++;

			return newnode;
		}

		iterator erase(iterator pos)   //删除pos位置,注意删除的时候不能把头节点也删了,所以要做pos检查
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;
			_sz--;

			return next;   //库里规定返回删除节点的下一个节点
		}

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

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

		void pop_back()  //尾删
		{
			erase(--end());
		}

		void pop_front()  //头删
		{
			erase(begin());
		}

		void clear()  //清楚除了头节点以外的所有节点
		{
			iterator it = begin();
			while (it != end())
			{
				it=erase(it);
				
			}
			_sz = 0;
		}

		~list()  //析构函数
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		list(const list<T>& lt)   //拷贝构造
		{
			empty_init();

			for (auto& e : lt)
			{
				push_back(e);
			}
			
		}

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

		list<T>& operator=(list<T> lt)  //赋值重载
		{
			swap(lt);

			return *this;
		}

	private:
		Node* _head;  //头节点
		size_t _sz;   //记录链表的长度
	};

}

🐬🤖本篇文章到此就结束了, 若有错误或是建议的话,欢迎小伙伴们指出;🕊️👻

😄😆希望小伙伴们能支持支持博主啊,你们的支持对我很重要哦;🥰🤩

😍😁谢谢你的阅读。😸😼

 

 

 

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

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

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

相关文章

  • C++初阶之一篇文章教会你list(模拟实现)

    C++初阶之一篇文章教会你list(模拟实现)

    成员类型表 这个表中列出了C++标准库中list容器的一些成员类型定义。这些类型定义是为了使list能够与C++标准库的其他组件协同工作,并提供一些通用的标准接口。每个成员类型的用处: value_type : 这个成员类型代表list容器中存储的数据类型,即模板参数T的类型。 allocator_

    2024年02月12日
    浏览(13)
  • 【C++初阶】容器适配器模拟实现栈和队列(附源码)

    【C++初阶】容器适配器模拟实现栈和队列(附源码)

    其实在使用模板时,我们 不仅可以使用类模板,还可以使用容器模板 ,这就是一个容器适配器,我们可任意给 模板实例化不同的容器,然后就可以使用容器里的接口 。 我们知道,栈可以用数组实现也可以用链表实现,以前在C语言那里,如果我们想要两个底层不同的栈,要

    2024年02月16日
    浏览(10)
  • 【C++初阶】仿函数和priority_queue的模拟实现(附源码)

    【C++初阶】仿函数和priority_queue的模拟实现(附源码)

    仿函数,顾名思义就是 模仿函数,它其实是一个类,类里面重载了运算符() ,在调用这个重载的运算符时,让我们感觉是调用函数一样,可以说相当于C语言里的函数指针一样,但是函数指针的可读性不好,不如仿函数。 1.仿函数即使定义相同,也可能有不同的类型; 2.仿

    2024年02月16日
    浏览(14)
  • 『初阶数据结构 • C语言』⑩ - 队列的概念与实现(附完整源码)

    『初阶数据结构 • C语言』⑩ - 队列的概念与实现(附完整源码)

        队列对于临时数据的处理也十分有趣,它跟栈一样都是有约束条件的数组(或者链表)。区别在于我们想要按什么顺序去处理数据,而这个顺序当然是要取决于具体的应用场景。 你可以将队列想象成是电影院排队。排在最前面的人会最先离队进入影院。套用到队列上,就

    2024年02月15日
    浏览(14)
  • 『初阶数据结构 • C语言』⑫ - 堆的概念&&实现(图文详解+完整源码)

    『初阶数据结构 • C语言』⑫ - 堆的概念&&实现(图文详解+完整源码)

    目录 0.写在前面 1.什么是堆? 2.堆的实现 2.1 堆的结构定义 2.2 函数声明 2.3 函数实现 2.3.1 AdjustUp(向上调整算法) 2.3.2 AdjustDown(向下调整算法) 2.3.3 HeapCreate(如何建堆) 2.3.4 建堆的时间复杂度 3. 完整源码 Heap.h文件 Heap.c文件  Test.c文件  上一章中介绍了树和二叉树的概念

    2024年02月16日
    浏览(12)
  • 『初阶数据结构 • C语言』⑩ - 栈的概念与实现(附完整源码)

    『初阶数据结构 • C语言』⑩ - 栈的概念与实现(附完整源码)

        栈存储数据的方式跟数组一样,都是将元素排成一行。只不过它还有以下 3 条约束。   ● 只能在末尾插入数据。   ● 只能读取末尾的数据。   ● 只能移除末尾的数据。 你可以将栈看成一叠碟子:你只能看到最顶端那只碟子的碟面,其他都看不到。另外,要加碟子只能

    2024年02月16日
    浏览(14)
  • C++入门之stl六大组件--List源码深度剖析及模拟实现

    C++入门之stl六大组件--List源码深度剖析及模拟实现

    文章目录 前言 一、List源码阅读 二、List常用接口模拟实现 1.定义一个list节点 2.实现一个迭代器 2.2const迭代器 3.定义一个链表,以及实现链表的常用接口 三、List和Vector 总结 本文中出现的模拟实现经过本地vs测试无误,文件已上传gitee,地址:list: 模仿实现stl的list - Gitee.com 首

    2024年02月13日
    浏览(16)
  • 『初阶数据结构 • C语言』⑬ - 堆排序详解【附完整源码】

    『初阶数据结构 • C语言』⑬ - 堆排序详解【附完整源码】

     = 目录 0.写在前面 1.什么是堆? 2. 堆排序 2.1 建堆 2.1.1 AdjustUp(向上调整算法) 2.1.2 AdjustDown(向下调整算法) 2.2 两种建堆算法的时间复杂度 2.2.1 AdjustUp建堆的时间复杂度 2.2.2 AdjustDown建堆的时间复杂度 2.3 排序 3.堆排序的时间复杂度 完整源码 你是否对堆排序早有耳闻?身

    2024年02月16日
    浏览(13)
  • 『初阶数据结构 • C语言』⑧ - 动态顺序表详解(附完整源码)

    『初阶数据结构 • C语言』⑧ - 动态顺序表详解(附完整源码)

    本章内容 写在前面 1.静态与动态是指什么? 2.动态顺序表结构的定义 3.动态顺序表的函数接口实现 4.动态顺序表的问题及思考 5.关于顺序表的OJ题 6.OJ答案及解析 1.移除元素 2.删除有序数组中的重复项 ​3.合并两个有序数组 7.动态顺序表完整源码 1.SeqList.h 2.SeqList.c     上一章

    2024年02月16日
    浏览(11)
  • 『初阶数据结构 • C语言』⑦ - 静态顺序表详解(附完整源码)

    『初阶数据结构 • C语言』⑦ - 静态顺序表详解(附完整源码)

    本章内容 1.什么是线性表 2.什么是顺序表  3.静态顺序表结构的定义 4.静态顺序表的函数接口实现 5.静态顺序表的问题及思考     线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、

    2024年02月15日
    浏览(15)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包