【C++】-- STL之list模拟实现

目录

一、list模拟实现思路 

二、节点类的实现

三、list迭代器的实现

1._list_iterator类

2.构造函数

3.operator* 运算符重载

4.operator-> 运算符重载

5.operator!= 运算符重载

6.operator==运算符重载

7.前置++

8.后置++

9.前置--

10.后置--

四、list类的实现

1.list类 

2.构造函数

3.拷贝构造

4.赋值运算符重载

(1)传统的赋值运算符重载

(2)现代的赋值运算符重载

5.析构

6.迭代器

7.insert( ) 

8.erase( )

9.clear( )

10.push_front( )

11.push_back( )

12.pop_front( )

13.pop_back( )

14.empty( )

15 size( )

五、完整代码段


【C++】-- STL之list模拟实现_第1张图片

一、list模拟实现思路 

list链表的模拟实现,与前面的string和vector相比,略复杂一点:

(1)由于链表节点本身就是一个结构体,包含数据域、指针域。 将节点的相关操作放入节点类进行处理,逻辑更清晰

(2)string和vector的元素在空间上是连续分布的,迭代器++就能指向下一个元素,但list的迭代器不行,它的每个元素在空间上都不连续,要访问下一个节点必须找到当前节点的next,因此list的迭代器必须重写

【C++】-- STL之list模拟实现_第2张图片

链表主要模拟实现以下功能:

 【C++】-- STL之list模拟实现_第3张图片

为了和库里面的list区分开,使用命名空间delia将 list类和库里list隔离开

namespace delia
{
}

二、节点类的实现

节点类的成员变量有3个:

(1)节点值_val

(2)指向前一个节点的指针_prev

(3)指向后一个节点的指针_next 

节点无需拷贝构造、赋值运算符重载,由于没有额外申请空间,因此也不需要析构

	template
	struct _list_node
	{
        //3个成员变量
		T _val;
		_list_node* _next;
		_list_node* _prev;

        //构造函数
		_list_node(const T& val = T())
			:_val(val) 
			, _prev(nullptr)
			, _next(nullptr)
		{};	
	};

三、list迭代器的实现

由于节点的指针原生行为不满足迭代器定义,在这里,迭代器通过类来封装节点的指针重载运算符,如operator*、operator->、operator++等。用迭代器封装节点指针的思路不关心容器底层结构是数组、链表还是树等数据结构,都封装隐藏了底层细节,可以用一种统一的方式去访问、修改容器。

迭代器分为普通迭代器和const迭代器,对于_list_iterator类要实现两个版本,一个是普通的_list_iterator,另一个是const版本的_list_const_iterator,区别在于:对于两个类中的部分函数有普通函数和const函数之分(如begin( )和end( )),其他并无区别。因为这两个类的大部分代码相似,会造成代码冗余,如何解决呢?

对于T&,类模板实例化出两个类,一个是T&类,一个是const T&类,同理,T*也一样。使用 

template

类模板就会实例化出来两个类,一个是普通的不带const的T,T&, T*,另一个是带const的T,const T&, const T*,其中Ref是引用,Ptr是指针,该类模板实例化了以下这两个类模板:

template _list_iterator _iterator;
template _list_iterator const_iterator;

这就解决了需要写两个类的问题。 

1._list_iterator类

迭代链表的节点,只需要一个成员变量即节点 

    template
	struct _list_iterator//使用_list_iterator类来封装node*
	{
		typedef _list_node node;
		typedef _list_iterator self;

		node* _pnode;//成员变量
    }

2.构造函数

只需要初始化节点即可 

		//构造函数
		_list_iterator(node* pnode)
			:_pnode(pnode)
		{}

拷贝构造、赋值运算符重载、析构函数,编译器默认生成即可

3.operator* 运算符重载

		//解引用,返回左值,是拷贝,因此要用传引用返回
		Ref operator*()
		{
			return _pnode->_val;
		}

4.operator-> 运算符重载

		//结构体指针解引用,返回节点值的指针
		Ptr operator->()
		{
			return &_pnode->_val;
		}

5.operator!= 运算符重载

		//!=重载
		bool operator!=(const self& s) const
		{
			return _pnode != s._pnode;
		}

6.operator==运算符重载

		//==重载
		bool operator==(const self& s) const
		{
			return _pnode == s._pnode;
		}

7.前置++

让迭代器走到下一个节点 

		//前置++  it.operator(&it)
		self& operator++()
		{
			_pnode = _pnode->next;
			return *this;
		}

8.后置++

		//后置++ 返回++之前的值  it.operator(&it,0)
		self operator++(int)//需构造临时对象,临时对象不能用引用返回,所以self没有加&
		{
			self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}

9.前置--

让迭代器走到前一个节点 

		//前置--  it.operator(&it)
		self& operator--()
		{
			_pnode = _pnode->prev;
			return *this;
		}

10.后置--

		//后置-- 返回--之前的值  it.operator(&it,0)
		self operator--(int)//需构造临时对象,临时对象不能用引用返回,所以self没有加&
		{
			self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}

四、list类的实现

1.list类 

list的成员只需要一个头节点,通过迭代器访问其他节点元素

	template
	class list
	{
		typedef _list_node node;
	public:
		typedef _list_iterator iterator;//重命名迭代器
		typedef _list_iterator const_iterator;//重命名const迭代器
	private:
		node* _head;
	};

2.构造函数

		list()
		{
			_head = new node;//会调_list_node的构造函数
			_head->_next = _head;//整个链表只有头节点,先构造一个没有实际节点的链表
			_head->_prev = _head;//整个链表只有头节点,先构造一个没有实际节点的链表
		}

3.拷贝构造

创建一个空链表,再把参数链表节点的值一个一个尾插进去

		//拷贝构造 lt1(lt)
		list(const list& lt)
		{
			//1.先构造一个只有头节点的空list:创建头节点,头节点的前一个是自己,头节点的后一个是自己
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;

			//2.将lt的元素全部尾插到新链表
			for (const auto& e : lt)
			{
				push_back(e);
			}
		}

4.赋值运算符重载

(1)传统的赋值运算符重载

        //赋值运算符重载  lt1 = lt  传统写法
		list operator=(const list& lt)
		{
            //链表已存在,只需将节点尾插进去即可
			if(this != lt)
			{
				for (auto& e : lt)
				{
					push_back(e);
				}
			}
		}

(2)现代的赋值运算符重载

		list& operator=(list& lt)
		{
			swap(_head, lt->_head);
			return *this;
		}

其中,swap( )函数的执行过程如下:  

template  void swap ( T& a, T& b )
{
  T c(a); a=b; b=c;
}

在执行赋值运算符重载时,会调用拷贝构造函数执行深拷贝,然后再交换两个链表头节点的指针,把this要释放的资源交给临时对象,临时对象出了swap的函数作用域就被this要释放的资源也会被同时释放。

5.析构

(1)清除所有节点内容

(2)删除头节点,链表就销毁了 

		//析构
		~list()
		{
			clear();//先清除所有节点内容
			delete _head;//再删除头节点
			_head = nullptr;
		}

6.迭代器

(1)普通迭代器

		iterator begin()
		{
			return iterator(_head->_next);//头节点不存数据
		}

		iterator end()
		{
			return iterator(_head);//尾节点的下一个节点位置即头节点
		}

 (2)const迭代器

		const_iterator begin() const
		{
			return const_iterator(_head->_next);//头节点不存数据
		}

		const_iterator end() const
		{
			return const_iterator(_head);//尾节点的下一个节点位置即头节点
		}

7.insert( ) 

(1)构造节点

(2)双向带头循环链表插入节点【C++】-- STL之list模拟实现_第4张图片

        //插入节点		
        void insert(iterator pos, const T& x)
		{
			assert(pos._pnode);
			node* newnode = new node(x);//构造节点

			node* prev = pos._pnode->_prev;//为方便后续操作,给插入位置的前一个节点起了个名字,pos是迭代器对象
            
            //插入节点
			newnode->_next = pos._pnode;
			pos._pnode->_prev = newnode;
			prev->_next = newnode;
			newnode->_prev = prev;

		}

8.erase( )

(1)删除节点

(2)更新指针指向 

【C++】-- STL之list模拟实现_第5张图片

		//删除节点
        iterator erase(iterator pos)
		{
			assert(pos._pnode);//判断该位置节点是否存在
			assert(pos != end());//end()是最后一个节点的下一个节点位置,也就是头节点,头节点不能删,需要断言

			node* prev = pos._pnode->_prev;//pos位置节点的前一个节点
			node* next = pos._pnode->_next;//pos位置节点的后一个节点
            
            //删除节点
			delete pos._pnode;
			prev->_next = next;
			next->_prev = prev;

			return iterator(next);//删除之后pos失效,把下一个位置的迭代器给它
		}

9.clear( )

只清除所有节点内容,不能删除头节点,如果删除头节点那么链表就不存在了,是链表的析构函数需要完成的操作

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);//erase需要传参迭代器,即位置
			}
		}

10.push_front( )

 头插

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

11.push_back( )

尾插

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

12.pop_front( )

头删

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

13.pop_back( )

尾删

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

14.empty( )

判空 

		//判空
        bool empty()
		{
			return end() == begin();
		}

15 size( )

求节点个数

		//求节点个数
        size_t size()
		{
			iterator it = begin();
			size_t sz = 0;
			while (it != end())//时间复杂度O(N)
			{
				it++;
				sz++;
			}

			return sz;
		}

五、完整代码段

016-list.h 

#pragma once
#include
#include
using namespace std;

namespace delia
{
	template
	struct _list_node
	{
		T _val;
		_list_node* _prev;
		_list_node* _next;

		_list_node(const T& val = T())
			:_val(val) 
			, _prev(nullptr)
			, _next(nullptr)
		{};	
	};


	//节点的指针原生行为不满足迭代器定义,在这里,迭代器通过类来封装节点的指针重载运算符来控制

	//对于T&,类模板实例化出两个类,一个是T&类,一个是const T&类,同理,T*也一样。
	//使用template类模板就会实例化出来两个类,一个是T,T&, T*的,一个是T,const T&, const T*的	 
	//template _list_iterator _iterator;
	//template _list_iterator const_iterator;
	
	//这样就不需要再去定义一个如下const的迭代器类:
	//template
	//struct _list_const_iterartor
	//{
	//	typedef _list_node node;
	//	typedef _list_const_iterartor self;

	//	node* _pnode;
	//}
	// 
	
	template
	struct _list_iterator//使用_list_iterator类来封装node*
	{
		typedef _list_node node;
		typedef _list_iterator self;

		node* _pnode;

		//构造函数
		_list_iterator(node* pnode)
			:_pnode(pnode)
		{}

		//拷贝构造、赋值运算符重载、析构函数,编译器默认生成即可

		//解引用,返回左值,是拷贝,因此要用引用返回
		Ref operator*()
		{
			return _pnode->_val;
		}

		//结构体指针解引用,返回节点值的引用
		Ptr operator->()
		{
			return &_pnode->_val;
		}

		//!=重载
		bool operator!=(const self& s) const
		{
			return _pnode != s._pnode;
		}

		//==重载
		bool operator==(const self& s) const
		{
			return _pnode == s._pnode;
		}

		//前置++  it.operator(&it)
		self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}

		//后置++ 返回++之前的值  it.operator(&it,0)
		self operator++(int)
		{
			self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}

		//前置--  it.operator(&it)
		self& operator--()
		{
			_pnode = _pnode->prev;
			return *this;
		}

		//后置++ 返回++之前的值  it.operator(&it,0)
		self operator--(int)//临时对象不能用引用返回,所以self没有加&
		{
			self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}
	};

	template
	class list
	{
		typedef _list_node node;
	public:
		typedef _list_iterator iterator;//重命名迭代器
		typedef _list_iterator const_iterator;//重命名const迭代器

		//构造函数
		list()
		{
			_head = new node;//会调_list_node的构造函数
			_head->_next = _head;//整个链表只有头节点,先构造一个没有实际节点的链表
			_head->_prev = _head;//整个链表只有头节点,先构造一个没有实际节点的链表
		}

		//拷贝构造 lt1(lt)
		list(const list& lt)
		{
			//1.先构造一个只有头节点的空list:创建头节点,头节点的前一个是自己,头节点的后一个是自己
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;

			//2.将lt的元素全部尾插到新链表
			for (const auto& e : lt)
			{
				push_back(e);
			}
		}

		赋值运算符重载  lt1 = lt  传统写法
		//list& operator=(list& lt)
		//{
		//	if(this != lt)
		//	{
		//		for (auto& e : lt)
		//		{
		//			push_back(e);
		//		}
		//	}
		//}
		
		//赋值运算符重载  lt1 = lt  现代写法
		list& operator=(list& lt)
		{
			swap(_head, lt._head);
			return *this;
		}

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

		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);//尾节点的下一个节点位置即头节点
		}

		//插入节点
		void insert(iterator pos, const T& x)
		{
			assert(pos._pnode);
			node* newnode = new node(x);//构造节点

			node* prev = pos._pnode->_prev;//为方便后续操作,给插入位置的前一个节点起了个名字,pos是迭代器对象

			//插入节点
			newnode->_next = pos._pnode;
			pos._pnode->_prev = newnode;
			prev->_next = newnode;
			newnode->_prev = prev;

		}

		//删除节点
		iterator erase(iterator pos)
		{
			assert(pos._pnode);//判断该位置节点是否存在
			assert(pos != end());//end()是最后一个节点的下一个节点位置,也就是头节点,头节点不能删,需要断言

			node* prev = pos._pnode->_prev;//pos位置节点的前一个节点
			node* next = pos._pnode->_next;//pos位置节点的后一个节点

			//删除节点
			delete pos._pnode;
			prev->_next = next;
			next->_prev = prev;

			return iterator(next);//删除之后pos失效,把下一个位置的迭代器给它
		}

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

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

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

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

		//尾删
		void pop_back()
		{
			erase(--end());
		}
		
		//判空
		bool empty()
		{
			return _head->_next == _head;
		}
		
		//求节点个数
		size_t size()
		{
			iterator it = begin();
			size_t sz = 0;
			while (it != end())//时间复杂度O(N)
			{
				it++;
				sz++;
			}

			return sz;
		}
	private:
		node* _head;
	};

	void PrintList(const list& lt)
	{
		list::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << it._pnode->_val << " ";
			it++;
		}
		cout << endl;
	}

	void test_list1()
	{
		list l1;
		l1.push_back(5);
		l1.push_back(8);
		l1.push_back(20);
		l1.push_back(9);

		PrintList(l1);

		list l2;
		l2 = l1;
		PrintList(l2);

		cout << endl;
	}
}

016-test.cpp

#include "016-list.h"
#include
using namespace std;

int main()
{
	delia::test_list1();
	return 0;
}

你可能感兴趣的:(C++,c++,开发语言)