【C++】list的模拟实现

📢博客主页:https://blog.csdn.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述


📢一、前言

在 C++ 编程中,模拟实现标准模板库(STL)中的 list 具有重要意义和广泛的应用场景。list 作为一种常用的数据结构,其独特的特性使得在许多情况下能够提供高效和灵活的操作。

模拟实现 list 有助于深入理解其内部工作原理。通过亲手编写代码来模拟 list 的各种功能,如节点的创建、插入、删除、遍历等,可以清晰地掌握数据在链表中的存储和流动方式,这对于提升对数据结构的理解和编程能力至关重要。

在实际应用中,list 常用于需要频繁进行插入和删除操作的场景。例如,当处理动态变化的数据集合,且插入和删除操作的位置不固定时,list 的优势就凸显出来。相比其他线性数据结构如数组和向量,list 不需要移动大量元素来进行插入和删除,时间复杂度通常为常数级别。

此外list 还适用于需要高效合并和分割数据的情况。由于其节点之间的链接关系相对独立,合并和分割操作可以相对轻松地实现。

总之,无论是为了深入学习数据结构和算法,还是为了在实际编程中灵活运用 list 来解决复杂问题,模拟实现 list 都具有不可忽视的价值。


🏳️‍🌈二、准备工作

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。
在这里插入图片描述
因为list是一个个节点组成的,所以对于他的每一个节点也要进行定义,代码如下:

template<class T>
struct list_node
{
	T _data;
	list_node<T>* _next;
	list_node<T>* _prev;

	// 缺省构造函数
	list_node(const T& data = T())
		:_data(data) 
		, _next(nullptr)
		,_prev(nullptr)
	{}
};

🏳️‍🌈三、节点结构体的实现

在模拟实现 C++ 中的 list 时,节点结构体的设计是基础。节点结构体通常包含以下几个部分:
首先是数据域,用于存储实际的数据。其数据类型可以根据具体需求灵活设定,比如整数、字符串、自定义对象等。

其次是前后指针域,分别指向链表中的前一个节点和后一个节点。这两个指针使得链表能够实现双向遍历和操作。

为了方便节点的创建和初始化,还会为节点结构体提供构造函数。构造函数可以接受数据作为参数,用于初始化数据域,并将前后指针域初始化为空指针,以确保节点在创建时处于正确的初始状态。

以下是一个简单的节点结构体示例代码:

template<typename T>
struct ListNode {
    T data;
    ListNode* prev;
    ListNode* next;

    ListNode(const T& val) : data(val), prev(nullptr), next(nullptr) {}
};

这样的节点结构体设计为后续实现 list 的各种复杂功能提供了基本的单元支持。

🏳️‍🌈四、迭代器结构体的实现

此处,大家可暂时将迭代器理解成一个指针,该指针指向Iist中的某个节点
在这里插入图片描述
我们可以通过下图理解其中的联系
在这里插入图片描述

❤️4.1 迭代器的模板参数

在模拟实现 list 的迭代器结构体中,通常会有多个模板参数。比如 <class T, class Ref, class Ptr> ,其中 T 表示迭代器所指向的节点中存储的数据类型。Ref 用于定义解引用操作返回的引用类型,以便在需要修改节点数据时能够进行修改。Ptr 则用于指定指针类型,例如在返回节点数据的指针时使用。

🧡4.2 迭代器的成员变量

迭代器结构体的成员变量通常是一个指向节点的指针,比如 Node* _pnode 。这个指针用于在链表中定位当前迭代器所指向的节点,从而实现对链表的遍历和操作。

💛4.3 重载的运算符

4.3.1 解引用运算符
解引用运算符 operator* 用于获取迭代器所指向节点的数据。通常的实现方式是返回节点中的数据,例如 Ref operator*() { return _pnode->_val; } ,通过这种方式,可以像使用指针一样获取节点中的数据。

4.3.2 指针操作运算符
指针操作运算符 operator-> 用于获取节点数据的地址。常见的实现是 Ptr operator->() { return &(_pnode->_val); } ,使得可以通过迭代器像使用指针一样访问节点数据的成员。

4.3.3 不等与相等判断运算符
不等运算符 operator!= 用于比较两个迭代器是否指向不同的节点,通常通过比较它们的指针值来实现,如 bool operator!=(const self& it) const { return _pnode!= it._pnode; } 。相等运算符 operator== 则相反,用于判断两个迭代器是否指向相同的节点。

下面是整个迭代器部分的代码:

	// 迭代器  -  结构体
	// 利用迭代器实现STL数据库
	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*()
		{
			return _node->_data;
		}

		// 返回的是指针
		Ptr operator->()
		{
			return &_node->_data;
		}

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

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

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

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

🏳️‍🌈五、list 链表类的实现

首先针对链表类,我们需要定义一个头节点和一个用来计算长度的变量

	private:
		Node* _head;
		size_t _size; 

❤️5.1 成员变量说明头节点指针

在 list 链表类中,通常会有一个成员变量用于存储头节点的指针。这个头节点指针是整个链表的起始点,通过它可以访问到链表中的其他节点。

template<typename T>
class List {
    ListNode<T>* head;
};

🧡5.2 构造函数

  • 默认构造函数会初始化头节点指针为空,表明链表初始时为空。
// 构造函数
		void empty_init()
		{
			// 哨兵位
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

		}

  • 拷贝构造函数用于创建一个与现有链表完全相同的新链表。
		list(const list <T>& it)
		{
			empty_init();
			for (auto& e : it)
			{
				push_back(e);
			}
		}
  • 析构函数用于释放链表中所有节点所占用的内存资源,以防止内存泄漏。在遍历链表时,依次删除每个节点。
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
  • 头插函数创建一个新节点,并将其置于链表头部。
		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

			newnode->_next = cur;
			newnode->_prev = prev;

			prev->_next = newnode;
			cur->_prev = newnode;
		}
		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;

			//++_size;

			insert(end(), x);
		}
  • 头删函数删除链表头部的节点。
		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

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

			delete pos._node;

			return next;
		}

		void pop_back()
		{
			erase(end()--);
		}

  • 赋值将一个链表的内容赋给另一个链表。
		list<T>& operator=(list<T> it)
		{
			swap(it);
			return *this;
		}

🏳️‍🌈六、注意事项

  • 迭代器失效
    在模拟实现 list 的过程中,需要特别注意迭代器失效的问题。当进行插入、删除等操作时,可能会导致原本指向特定节点的迭代器失效。例如,在插入节点后,原本指向插入位置之后节点的迭代器可能不再有效。为了处理迭代器失效,可以在进行可能导致失效的操作后,重新获取迭代器或者对相关迭代器进行更新。

  • 构造函数细节
    在构造函数中,尤其是按数量和类型构造以及迭代器构造,要确保正确地分配和初始化节点。对于内存分配,要处理可能的异常情况,以保证程序的健壮性。同时,要注意初始化节点之间的指针关系,避免出现悬空指针或循环引用等错误。

  • 析构函数要点
    析构函数用于释放链表中所有节点占用的内存。在实现时,要确保从链表头部开始,依次正确地删除每个节点,并将相关的指针置为空,以防止内存泄漏和野指针的出现。同时,要处理链表为空的特殊情况,避免出现非法的内存访问操作。

🏳️‍🌈整体代码

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace bit
{
	// 单个节点
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		// 缺省构造函数
		list_node(const T& data = T())
			:_data(data) 
			, _next(nullptr)
			,_prev(nullptr)
		{}
	};

	// 迭代器  -  结构体
	// 利用迭代器实现STL数据库
	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*()
		{
			return _node->_data;
		}

		// 返回的是指针
		Ptr operator->()
		{
			return &_node->_data;
		}

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

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

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

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


	/*template<class T>
	struct list_const_iterator
	{
		typedef list_node<T> Node;
		typedef list_const_iterator<T> Self;
		Node* _node;

		 构造函数
		list_const_iterator(Node* node)
			:_node(node)
		{}

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

		 返回的是指针
		const T* operator->()
		{
			return &_node->_data;
		}

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

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

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

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}
	};*/


	// 链表
	template<class T>
	class list
	{
		// 重命名
		typedef list_node<T> Node;

	public:
		/*typedef list_iterator<T> iterator;
		typedef list_const_iterator<T> const_iterator;*/

		// 利用模板实现两个类(高度相似的两个类)
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

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

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

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

		void empty_init()
		{
			// 哨兵位
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

		}

		list()
		{
			empty_init();
		}

		// lt2(lt1) 拷贝构造
		list(const list <T>& it)
		{
			empty_init();
			for (auto& e : it)
			{
				push_back(e);
			}
		}
		
		// lt1 = lt3
		list<T>& operator=(list<T> it)
		{
			swap(it);
			return *this;
		}


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

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

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

			newnode->_next = cur;
			newnode->_prev = prev;

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

		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;

			//++_size;

			insert(end(), x);
		}

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

		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

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

			delete pos._node;

			return next;
		}

		void pop_back()
		{
			erase(end()--);
		}

		void pop_front()
		{
			earse(begin());
		}
		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size; 
	};

	struct AA
	{
		int _a1 = 1;
		int _a2 = 1;
	};

	template<class Container>
	void print_container(const Container& con)
	{
		// const iterator ->迭代器本身不能修改
		// const_iterator -> 指向内容不能修改

		
		// 如果没有typename,编译器可能会错误地将Container::const_iterator解释为一个静态成员变量或成员函数,而不是一个类型。
		typename Container::const_iterator it = con.begin();
		while (it != con.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;


		for (auto e : con)
		{
			cout << e << " ";;
		}
		cout << endl;
	}


	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;


		list<AA> lta;
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());
		list<AA>::iterator ita = lta.begin();
		while (ita != lta.end())
		{
			//cout << (*ita)._a1 << ":" << (*ita)._a2 << endl;
			
			// 特殊处理,本来应该是两个->才合理,为了可读性,省略了一个->
			cout << ita->_a1 << ":" << ita->_a2 << endl;
			cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;

			++ita;
		}
		cout << endl;
	}

	void test_list2()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator it = lt.begin(); 
		lt.insert(it, 10);
		*it += 100;
		
		print_container(lt);
		cout << endl;

		// 删除所有的偶数
		it = lt.begin();
		while (it != lt.end())
		{
			if (*it % 2 == 0)
			{
				it = lt.erase(it);
			}
			else
				++it;
		}
		print_container(lt);
	}

	void test_list3()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);

		list<int> lt2(lt1);
		print_container(lt1);
		print_container(lt2);

		list<int> lt3;
		lt3.push_back(9);
		lt3.push_back(9);
		lt3.push_back(9);
		lt3.push_back(9);

		lt1 = lt3;
		print_container(lt1);
		print_container(lt3);

	}
}

👥总结

模拟实现 list 是一项具有挑战性但富有价值的工作。要点包括:

  • 深入理解双向链表的结构和特性,确保节点结构体、迭代器结构体以及链表类的设计准确无误。
  • 精心实现各种构造函数,满足不同的初始化需求,同时注意内存管理和指针操作的正确性。
  • 准确编写基本操作函数,如插入、删除、遍历等,保证链表的功能完整且高效。
  • 时刻关注迭代器失效问题,在相关操作后进行合理的处理和更新。

本篇博文对 list的模拟实现 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值