【STL详解】list的模拟实现

目录

list的模拟实现总览:

1.节点类的模拟实现

构造函数

2.迭代器类的模拟实现

迭代器类的模板参数说明

构造函数

++运算符的重载

--运算符的重载

==运算符的重载

!=运算符的重载

*运算符的重载

 ->运算符的重载

list的模拟实现

默认成员函数

构造函数

 拷贝构造函数(迭代器版本)

拷贝构造函数(现代写法)

赋值运算符重载函数(现代写法)

 其他构造函数

析构函数

迭代器相关函数

 访问容器相关函数

插入、删除函数

insert(前插)

erase

push_back和pop_back

push_front和pop_front


list的模拟实现总览:

#include<iostream>
using namespace std;

namespace lxy
{
	//模拟实现list当中的结点类
	template<class T>
	struct ListNode
	{
		//构造函数
		ListNode(const T& data = T());
		//成员变量
		T _data;			//数据域
		ListNode<T>* _next;	//后继指针
		ListNode<T>* _prev;	//前驱指针
	};

	//模拟实现list迭代器
	template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;

		//构造函数
		__list_iterator(Node* x);

		//各种运算符重载函数
		self& operator++();
		self& operator--();
		self operator++(int);
		self operator--(int);
		bool operator==(const self& it) const;
		bool operator!=(const self& it) const;
		Ref operator*();
		Ptr operator->();

		//成员变量
		Node* _node;
	};


	//模拟实现list
	template<class T>
	class list
	{
	public:
		typedef ListNode<T> Node;
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const  T*> const_iterator;
		
		//默认成员函数
		list();
		list(const list<T>& lt);
		list<T>& operator=(const list<T>& lt);
		~list();

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//访问容器相关函数
		T& front();
		T& back();
		const T& front() const;
		const T& back() const;

		//插入、删除函数
		iterator insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void push_back(const T& x);
		void pop_back();
		void push_front(const T& x);
		void pop_front();

		//其他函数
		size_t size() const;
		void resize(size_t n, const T& val = T());
		void clear();
		bool empty() const;
		void swap(list<T>& lt);

	private:
		//指向链表头结点的指针
		Node* _head;
	};
}

1.节点类的模拟实现

	template<class T>
	struct ListNode
	{
		//构造函数
		ListNode(const T& data = T());
		//成员变量
		T _data;			//数据域
		ListNode<T>* _next;	//后继指针
		ListNode<T>* _prev;	//前驱指针
	};

list是一个带头双向循环链表,故先实现一个结点类。

构造函数

		ListNode(const T& data = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{ }

 注意: 若构造结点时未传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据。

2.迭代器类的模拟实现

	//模拟实现list迭代器
	template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;

		//构造函数
		__list_iterator(Node* x);

		//各种运算符重载函数
		self& operator++();
		self& operator--();
		self operator++(int);
		self operator--(int);
		bool operator==(const self& it) const;
		bool operator!=(const self& it) const;
		Ref operator*();
		Ptr operator->();

		//成员变量
		Node* _node;
	};

对于list来说,其各个结点在内存当中的位置是随机的,并不是连续的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作。迭代器让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。

 list迭代器类,实际上就是对结点指针进行了封装,对其各种运算符进行了重载,使得结点指针的各种行为看起来和普通指针一样。

迭代器类的模板参数说明

	template<class T,class Ref,class Ptr>

在list的模拟实现当中,typedef了两个迭代器类型,普通迭代器和const迭代器。

		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const  T*> const_iterator;

可知:Ref代表引用类型,Ptr代表指针类型。

若该迭代器类不设计三个模板参数,那么就不能很好的区分普通迭代器和const迭代器。

构造函数

对结点指针进行了封装,封装成迭代器。

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

++运算符的重载

前置++,后置++

迭代器++,访问下一个节点

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

		// it++
		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

说明: self是当前迭代器对象的类型:

		typedef __list_iterator<T, Ref, Ptr> self;

--运算符的重载

前置--,后置--

迭代器--,访问上一个节点

		// --it
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		// it--
		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

==运算符的重载

判断两个迭代器当中的结点指针的指向是否相同,注意加上const

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

!=运算符的重载

判断两个迭代器当中的结点指针的指向是否不同,注意加上const

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

*运算符的重载

*运算符得到该位置的数据内容,并且返回引用

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

 ->运算符的重载

某些情景下,我们使用迭代器的时候可能会用到->运算符。

当list容器当中的每个结点存储的不是内置类型,而是自定义类型,例如日期类,那么当我们拿到一个位置的迭代器时,我们可能会使用->运算符访问Date的成员:

	void test()
	{
		list<Date> lt;
		lt.push_back(Date(2022, 3, 12));
		lt.push_back(Date(2022, 3, 13));
		lt.push_back(Date(2022, 3, 14));

		list<Date>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
			cout << it->_year << "/" << it->_month << "/" << it->_day << endl;

			++it;
		}
		cout << endl;
	}

 注意: 使用pos->_year这种访问方式时,需要将日期类的成员变量设置为公有。

对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可。

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

这里本来是应该有两个->的,第一个箭头是pos ->去调用重载的operator->返回Date* 的指针,第二个箭头是Date* 的指针去访问对象当中的成员变量_year。

但是一个地方出现两个箭头,程序的可读性太差了,所以编译器做了特殊识别处理,为了增加程序的可读性,省略了一个箭头。

list的模拟实现

默认成员函数

构造函数

list是一个带头双向循环链表,在构造一个list对象时,申请一个头结点,并让其前驱指针和后继指针都指向自己即可。

		list()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}

 拷贝构造函数(迭代器版本)

先申请一个头节点,再遍历一个范围,push进去

说明:push_back函数稍后讲解。

		//list lst2(lst1.begin(),lst1.end());
        template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

拷贝构造函数(现代写法)

先用迭代器版本构造函数构造tmp,再交换头指针指向位置

		// lt2(lt1)
		list(const list<T>& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;

			list<T> tmp(lt.begin(), lt.end());
			std::swap(_head, tmp._head);
		}

赋值运算符重载函数(现代写法)

利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换即可。

		// lt2 = lt1
		list<T>& operator=(list<T> lt)
		{
			std::swap(_head, lt._head);
			return *this;
		}

 其他构造函数

使用案例:

构造一个list:1,1,1,1,1

构造一个list含5个Date类型。

注意:

 class InputIterator应该为迭代器类型,但是如果

list<int> lt2(5, 1);

则会识别为int类型,所以写出带int的构造函数,编译器会优先选择。 

		// list<Date> lt1(5, Date(2022, 3, 15));
		// list<int> lt2(5, 1);
		list(int n, const T& val = T())
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		list(size_t n, const T& val = T())
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

析构函数

首先说明:clear 函数(2钟写法),erase函数稍后解析。

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		iterator del = it++;    //迭代器前进
		delete del._node;       //删除当前位置数据
	}

	_head->_next = _head;
	_head->_prev = _head;
}

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

析构函数:首先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空即可。

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

迭代器相关函数

begin和end:begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。即begin指向首元素,end指向尾后。

对于list这个带头双向循环链表来说:

说明:

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

并且迭代器就是对节点指针的封装。


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);
}

 访问容器相关函数

front和back函数分别用于获取第一个有效数据和最后一个有效数据,并且返回引用。

T& front()
{
	return *begin(); //返回第一个有效数据的引用
}
T& back()
{
	return *(--end()); //返回最后一个有效数据的引用
}

const版本不再赘述。

插入、删除函数

核心函数:insert函数  erase函数,其他函数复用insert和erase得到

insert(前插)

插入后返回指向newNode的迭代器。

		// 这里insert以后,pos不失效
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

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

			return iterator(newnode);
		}

erase

		// 这里erase以后,pos失效
		iterator erase(iterator pos)
		{
			assert(pos != end());

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

			return iterator(next);
		}

删除后返回下一个位置的迭代器

push_back和pop_back

复用insert、erase

//尾插
void push_back(const T& x)
{
	insert(end(), x); //在头结点前插入结点
}
//尾删
void pop_back()
{
	erase(--end()); //删除头结点的前一个结点
}

push_front和pop_front

复用insert、erase

//头插
void push_front(const T& x)
{
	insert(begin(), x); //在第一个有效结点前插入结点
}
//头删
void pop_front()
{
	erase(begin()); //删除第一个有效结点
}

其他函数

size

size_t size() const
{
	size_t sz = 0; //统计有效数据个数
	const_iterator it = begin(); //获取第一个有效数据的迭代器
	while (it != end()) //通过遍历统计有效数据个数
	{
		sz++;
		it++;
	}
	return sz; //返回有效数据个数
}

猜你喜欢

转载自blog.csdn.net/m0_51866180/article/details/123770238
今日推荐