C++STL容器List的模拟实现

发布于:2025-07-25 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、引言

        list的实现,还是比较简单的,大家只要想着土家楼的形状,画出图来就好了,不需要过多担心。本次的博客会发出一个完整的实现List的List.hpp,以后也会这样,主要是分段发被说孩子分段生。

二、模拟List

        由于list中的结构需要特定的类型和特定的指定地址的,所以我们先要实现list中的结点和迭代器。正所谓"工欲善其事,必先利其器"。

        2.1        List内的结点制造

                 可以理解为一个包裹,用箱子包裹着的网购物品,list相当于分配的快递线。list结点要有前后指针,肯定也要有存储数据的类型。所以list_node中设计三个数据。

// 模版的使用。
// 可以套用任何类型,让编译器自己去推。
template< class T >
// 用struct是因为struct默认public访问(也就是允许访问任何资源)。
struct List_Node
{
    // 缺省参数,未传参时,可以默认初始化_val。
    // _next和_prev都是nullptr,初始化好,方便list类盖。
	List_Node(T val = T())
		:_val(val)
		, _next(nullptr)
		, _prev(nullptr)
	{
		;
	}

    // C++11的万能引用,等些到万能引用会粘贴好的。
	template < class X >
	List_Node(X&& val)
		:_val(val)
		, _next(nullptr)
		, _prev(nullptr)
	{
		;
	}
    
	T _val;
	List_Node* _next;
	List_Node* _prev;
};

        2.2      Iterator的模拟实现

                iterator迭代器是用来代替指针指向list中的地址的,所以肯定要有list结点的指针。在iterator迭代器中我们需要重载他们的运算符,重载运算符是C++的特性(operator 要重载的运算符,值得一提的是,重载的运算符不能违背运算符本身之外的操作,不然很容易报错)。我们根据迭代器类比指针就可以知道要重载什么运算符,由于list迭代器是双向迭代器,不支持+、-某一位置的单位距离,所以我们不能支持Self operator + ()和Self operator - (),不仅如此还要删除掉他们。使用delete关键字,令这两个函数赋值delete,两个函数就会停止生成。

template< class T , class Ptr , class Ref >
struct Iterator
{
    // 设置list中的List_Node,一个具有标识性的名字。
    // 封装好一点。
	typedef List_Node < T > Node;
    // 可变性
    // 为了实现在list中的普通类型和const类型的迭代器。
    // 不用实现两份代码。
    // 在接下来的list中会讲。
    // 最主要的原因是list的迭代器不能像vector中的迭代器直接加const。
    // 如果只加const,说明迭代器指向了另一个结点类型,根本无法适用。
	typedef Iterator < T , Ptr , Ref > Self;
    // 而且这样不用写一大串类型名。
    
    // 初始化对象。
	Iterator(Node* node)
		:_node(node)
	{
		;
	}
    
	Ref operator * ()
	{
		return _node->_val;
	}

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

	Self& operator ++ ()
	{
		_node = _node->_next;

		return (*this);
	}

	Self& operator -- ()
	{
		_node = _node->_prev;

		return _node;
	}

	Self operator ++ (int)
	{
		Self tmp(_node);
		_node = _node->_next;

		return tmp;
	}

	Self operator -- (int)
	{
		Self tmp(_node);
		_node = _node->_next;

		return tmp;
	}

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

        return (*this);
    }

	// 减少拷贝,怕临时值捣乱,无法左值引用。
	bool operator == (const Self& node)
	{
		return _node == node._node;
	}

	bool operator != (const Self& node)
	{
		return _node != node._node;
	}
    
		// 删除这两个函数,不让这两个函数被错误调用。
		Self& operator + (size_t i) = delete;
		Self& operator - (size_t i) = delete;

	Node* _node;
};

        2.3        List中的基本结构和初始化函数

                List中一定包含着List_Node类型结点,就像快递线上有着的包裹,回转寿司有着寿司,土家族楼有着房间一样。上一章我们说过如果设计一个环形的结构,不知道以哪个结点为开头,所以我们新增一个带头链接点为开头,避免群龙无首的事情发生。另外插入过程中,我们无法通过结点直接的运算得到插入结点总数量为多少,所以我们添加一个变量,记录插入结点的总数量。

// 无参构造
List()
{
	_lt = new Node();

	_lt->_next = _lt;
	_lt->_prev = _lt;
}

// 初始化列表构造,就把它当做数组就行了。
// 另外initializer_list是C++11的内容,使用时记得调好编译器选项。
List(initializer_list<T> lt)
{
	_lt = new Node();

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

Node* _lt;
size_t _n;

        2.4        size()函数和empty()函数

                List不像Vector和String一样有capacity()有着空间大小,只要有_n就行了。根据_n我们可以判断List中是否存在数据以及插入数据有多少个。

size_t size()
{
	return _n;
}

bool empty()
{
    return _n == 0;
}

        2.5        Insert()和Erase()函数

                Insert()函数和Erase()函数都是在指定位置插入删除。

                

                我们先从插入开始讲起。插入只需要将pos位置上的结点(我们将这个结点设为结点A)和要pos位置之前(_prev)的结点(我们将这个结点设为结点B)和该位置的结点的_prev、_next指针做修改就可以了。由图可知让结点A(pos)的_prev指针指向新结点的地址,结点B(--pos上的结点)的_next指针指向新结点的地址,再让新结点的_prev指向结点B,_next指针指向结点A。

                讲完插入,就要将删除了。删除就更简单了,将要删除结点pos之前的位置和要删除结点之后的位置直接相连。再将pos上的结点delete掉,返回现在处于pos位置上的结点,也就是pos位置上的下一个结点。

        另外为什么需要返回插入、删除位置上新结点?

        是为了更新迭代器显示的pos位置上的结点避免访问出现错误,那为什么不直接在函数内修改pos呢?

        这个主要是出于有些迭代器不需要去修改,这个只能让用户去进行决定,比如说insert(begin()),begin()需要修改内部的指针吗?况且有时候这是一份吃力不讨好的工作,毕竟拷贝也是有效率牺牲的。其前面也提到过C++追求极致的效率,是不可能损害效率的。

iterator Insert(iterator pos , const T& val)
{
	// 前节点
	Node* prev = ((pos._node)->_prev);
	// 后节点
	Node* next = pos._node;
	// 新插入节点。
	Node* tmp = new Node (val);

	// 新节点插入
	tmp->_next = next;
	tmp->_prev = prev;

	// 前节点更新next(记录下一个节点的地址。)
	prev->_next = tmp;

	// 后节点节点更新prev(记录上一个节点的地址。)
	next->_prev = tmp;

	// 更新结点个数。
	++_n;

	return iterator(tmp);
}

// 按照STL的思想,应该是返回后一个位置,因为返回删除后该位置的指针,后一个位置已经顶替了该位置的指针。
iterator Erase(iterator pos)
{
    // 判断是否存在数据。
	assert(empty());
    // 删除位置不能为确定位置的头结点。
	assert(pos != End());

	// 前节点
	Node* prev = ((pos._node)->_prev);
	// 后节点
	Node* next = (pos._node)->_next;

	// 删除该节点。
	next->_prev = prev;
	prev->_next = next;

	pos._node->_next = pos._node->_prev = nullptr;
	delete pos._node;

	// 更新结点个数。
	--_n;
    
    // 构建匿名对象(方便返回),相当于调用一个Iterator的初始化函数。
	return iterator(next);
}

        2.6        begin()函数和end()函数

                begin()指明List中的起始位置(当List中没有结点时,会等于end()),所以begin()应返回的是定位的头结点。end()可以设为定位头结点本身。

		iterator begin()
		{
			return iterator(_lt->_next);
		}

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

        2.7        push_back()函数和pop_back()函数        

               至于这个就更简单了,代码应该讲究能复用时就复用,这样能提高代码的健壮性 ,毕竟改代码只用改一个地方还是改几个地方还是很便捷的,最重要的是使代码变得简洁,以后用的时候也好用。所以我们直接复用insert和erase。

void push_back(T val)
{
	Insert(end(),val);
}

void pop_back()
{
	Erase(end());
}

        2.8        emplace()函数和empalce_back()函数        

                这个我保证C++11的时候绝对会讲。到时候放个链接在这里。

三、全部代码

#include <iostream>
#include <initializer_list>
#include <cassert>
using namespace std;

namespace Link_List
{
	template< class T >
	struct List_Node
	{
		List_Node(T val = T())
			:_val(val)
			, _next(nullptr)
			, _prev(nullptr)
		{
			;
		}

		template < class X >
		List_Node(X&& val)
			:_val(val)
			, _next(nullptr)
			, _prev(nullptr)
		{
			;
		}

		T _val;
		List_Node* _next;
		List_Node* _prev;
	};

	template< class T , class Ptr , class Ref >
	struct Iterator
	{
		typedef List_Node < T > Node;
		typedef Iterator < T , Ptr , Ref > Self;

		Iterator(Node* node)
			:_node(node)
		{
			;
		}

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

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

		Self& operator ++ ()
		{
			_node = _node->_next;

			return (*this);
		}

		Self& operator -- ()
		{
			_node = _node->_prev;

			return _node;
		}

		Self operator ++ (int)
		{
			Self tmp(_node);
			_node = _node->_next;

			return tmp;
		}

		Self operator -- (int)
		{
			Self tmp(_node);
			_node = _node->_next;

			return tmp;
		}

		// 减少拷贝,怕临时值捣乱,无法左值引用。
		bool operator == (const Self& node)
		{
			return _node == node._node;
		}

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

		// 删除这两个函数,不让这两个函数被错误调用。
		Self& operator + (size_t i) = delete;
		Self& operator - (size_t i) = delete;

		Node* _node;
	};

	template < class T > 
	class List
	{
	public:
		typedef List_Node < T > Node;

		typedef Iterator < T  , T* , T& > iterator;
		typedef Iterator < T, const T*, const T& > Const_Iterator;

		List()
		{
			_lt = new Node();

			_lt->_next = _lt;
			_lt->_prev = _lt;
		}

		List(initializer_list<T> lt)
		{
			// 设计一个带头结点。
			_lt = new Node();

			_lt->_next = _lt;
			_lt->_prev = _lt;

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

		~List()
		{
			size_t n = _n;
			for(int i = 0;i < n;++i)
			{
				Erase(begin());
			}
			//cout << endl;
			//cout << _n << ":" << n << endl;
			//iterator it = Begin();
			//while (it != End())
			//{
			//	cout << *it << endl;
			//	++it;
			//}

			delete _lt;
			_lt = nullptr;
		}

		iterator begin()
		{
			return iterator(_lt->_next);
		}

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

		iterator Insert(iterator pos , const T& val)
		{
			// 前节点
			Node* prev = ((pos._node)->_prev);
			// 后节点
			Node* next = pos._node;
			// 新插入节点。
			Node* tmp = new Node (val);

			// 新节点插入
			tmp->_next = next;
			tmp->_prev = prev;

			// 前节点更新next(记录下一个节点的地址。)
			prev->_next = tmp;

			// 后节点节点更新prev(记录上一个节点的地址。)
			next->_prev = tmp;

			// 更新结点个数。
			++_n;

			return iterator(tmp);
		}

		// 按照STL的思想,应该是返回后一个位置,因为返回删除后该位置的指针,后一个位置已经顶替了该位置的指针。
		iterator Erase(iterator pos)
		{
			// 判断是否存在数据。
			// assert()不满足则触发。
            // 本质是一种检查。
            // 例如assert(false);常常被拿来做防御式编程
			assert(size());
			// 删除位置不能为确定位置的头结点。
			assert(pos != end());

			// 前节点
			Node* prev = ((pos._node)->_prev);
			// 后节点
			Node* next = (pos._node)->_next;

			// 删除该节点。
			next->_prev = prev;
			prev->_next = next;

			pos._node->_next = pos._node->_prev = nullptr;
			delete pos._node;

			// 更新结点个数。
			--_n;

			// 构建匿名对象(方便返回),相当于调用一个Iterator的初始化函数。
			return iterator(next);
		}

		size_t size()
		{
			return _n;
		}

		bool empty()
		{
			return _n == 0;
		}

		void push_back(T val)
		{
			Insert(end(),val);
		}

		void pop_back()
		{
			Erase(end());
		}

		iterator add(iterator it)
		{
			return it;
		}

		// 接收参数包的类型,参数包不是单纯相同类型的集成。
		template < class X , class... ARGS >
		// 参数包后面...是展开的意思。
		// 前面则类似声明的意思。
		iterator add(iterator pos, X&& val, ARGS&&... args)
		{
			iterator it = Insert(pos , val);
			return add(it, args...);
		}
		
		template < class... ARGS >
		iterator Emplace(iterator cit, ARGS&&... args)
		{
			iterator it (cit);
			return add(it,args...);
		}

		template < class... ARGS >
		void Emplace_back(ARGS&&... args)
		{
			Emplace(end(),args...);
		}

	private:
		Node* _lt = nullptr;
		size_t _n;
	};

	// 测试Insert函数
	void test01()
	{
		List < int > lt;

		for (int i = 0; i < 13; ++i)
		{
			lt.push_back(i);
		}

		//lt.Erase(lt.begin());
		//lt.Erase(lt.end());
	}

	void test02()
	{
		List < int > lt = { 1,2,3,4,5,6,7 };

		for (auto& e : lt)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}

	void test03()
	{
		//List < int > lt = { 11121,131,0321 ,1311,33,113,1321 };
		List < int > lt;

		int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

		for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
		{
			lt.push_back(a[i]);
		}

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


int main()
{
	Link_List::test02();

	return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到