【C++】简单模拟实现list

目录

一.前言

二.list构建

三.迭代器设置

1.迭代器模板

2.迭代器功能实现

①构造函数

②重载++、--

③重载==、!=

④重载*

⑤重载&

3.iterator和const_iterator的区别实现

四.模板函数功能实现

1.构造函数和析构函数

①构造函数

②析构函数

2.设置迭代器

3.operator=(重载赋值)

4.size函数(获取大小)

5.empty函数(判断空)

6.clear函数(清空)

8.insert函数(插入)和erase函数(删除)

7.push函数(头插、尾插)和pop函数(头删、尾删)

五.源码(含部分测试用例)


一.前言

        本文将通过模拟实现c++中的list模板中一些常用的函数功能,具体包含push_back,pop_back等常用函数的模拟,但并不考虑到所有的成员函数,仅通过模拟以达到学习的目的。

        本文共四部分,一部分用来构建list,了解其底层实现原理;第二部分是自己构建迭代器,也是本文的重点和难点,涉及到封装和模板类的问题;第三部分则是一切就绪后对具体功能的实现;最后一部分则为源码的分享(编译器环境为VS2019)。

二.list构建

        链表的构建和C语言的链表很相似,不过多了封装和类模板的概念,对于其构建,C++中设置的是双向带头链表。

        对此,我们需要一个链表,里面放一个头节点,用来指向链表的头和尾;另一部分则是对节点设置,其成员函数除了需要本身存储的数据外,还要指向下一个和上一个节点的指针。具体实现如下:

//节点
template<class T>
struct List_Node
{
	List_Node<T>* _next;
	List_Node<T>* _prev;
	T _val;

	// 使用new初始化节点
	List_Node(const T& val = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{}
};

//链表
template<class T>
class list
{
	typedef List_Node<T> Node;
    // 此为迭代器
    typedef __list_iterator<T> iterator;

private:
	Node* _head;
    // 链表的大小可以通过遍历链表获取
    // 此处为了方便直接设置
	size_t _sz;
};

三.迭代器设置

        重新对迭代器设置因为迭代器本身是指针,由于链表并不能像string和vector那样指向一段连续的数据块,不能直接地对迭代器++或--达到修改和遍历的目的,因此我们需要重新设置将其正确的指向每块不连续空间的节点。

1.迭代器模板

        迭代器作为指针要指向开辟的数据块,因此成员仅需要一个节点指针即可。(节点中有双向指针,可以找到下个或上个节点,达到双向遍历的目的)

//迭代器
template<class T>
struct __list_iterator
{
	typedef List_Node<T> Node;
	typedef __list_iterator<T> iterator;

	Node* _node;
};

2.迭代器功能实现

①构造函数

        迭代器构造函数的设置有两种,一个为默认构造函数(含初始化),另一个为拷贝构造函数,这两个是使用迭代器时常见的两种构造。构造后的迭代器就是给的节点的指针。

//初始化构造
__list_iterator(Node* node = nullptr)
	:_node(node)
{}

//拷贝构造
__list_iterator(const iterator& l)
{
	_node = l._node;
}

        你可能会疑惑为什么迭代器没有析构函数,因为迭代器仅仅相当于用于访问数据的工具,具体的销毁工作还是得靠链表本身来完成。如果设置了析构函数销毁节点,可不仅仅只会出现析构两次这样糟糕的情况了。

②重载++、--

        此处的重载有一点需要注意:返回值,注意到前置的返回值为迭代器的引用,而后置的迭代器返回的是值,因为如果后置中返回的值是临时变量,如果传引用返回该引用会在函数栈帧销毁时一起销毁。++与--的区别不过将成员指针指向下一个和上一个。

// 前置++
iterator& operator++()
{
	_node = _node->_next;
	return *this;
}

// 后置++
iterator operator++(int)
{
	iterator tmp = *this;
	_node = _node->_next;

	return tmp;
}

// 前置--
iterator& operator--()
{
	_node = _node->_prev;

	return *this;
}

//后置--
iterator operator--(int)
{
	iterator tmp = *this;
	_node = _node->_prev;

	return tmp;
}

③重载==、!=

bool operator==(const iterator& l) const
{
	return _node == l._node;
}

bool operator!=(const iterator& l) const
{
	return !(*this == l);
}

④重载*

        解指针取出保存的数据,再用.访问保存的类中的内容。

// 解指针
T& operator*()
{
    // 返回节点内容即可
	return _node->_val;
}

⑤重载->

        此处有一点非常值得注意,->重载后取出的数据为保存类的指针,如果要继续访问其中的内容,需要连续使用两个->,也就是->->。但并不是,我们平常使用指针时,也不过只使用了一次->,其实编译器为了运算符重载的可读性,对此就进行了特殊处理,省略了其中一个。

T* operator->()
{
    // 返回节点地址
	return &_node->_val;
}

3.iterator和const_iterator的区别实现

        const_iterator的实现与iterator大有区别,也许你会像如下设置const迭代器:

//迭代器
template<class T>
struct __list_iterator
{
	typedef List_Node<T> Node;
	typedef __list_iterator<T> iterator;
    typedef const __list_iterator<T> const_iterator;

	Node* _node;
};

        如果仍然像string或const那样设置const迭代器——在前面直接加const,就大错特错了。因为

迭代器需要有迭代的功能,如果给迭代器加上const,那么++和--都将出错,有两种解决办法:

        第一种:重新构建一个const模板类

template<class T>
struct __list_const_iterator
{
	typedef list_node<T> Node;

    //...各种模板函数    

	Node* _node;
};

        这样设计起来实在是过于冗余,完全没有必要,因此我们一般使用第二种方式设置const迭代器,其本质和上面一样,不过更简便。

        第二种:增加类模板参数

// Ref 为T&,或const T&
// Ptr 为T*,或const T*
template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef List_Node<T> Node;
	typedef __list_iterator<T, Ref, Ptr> iterator;

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

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

    //...其他不变

	Node* _node;
};

        在链表类中也应该这样定义:

//链表
template<class T>
class list
{
	typedef List_Node<T> Node;
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;


private:
	Node* _head;
	size_t _sz;
};

四.模板函数功能实现

1.构造函数和析构函数

①构造函数

        带头双向链表不论在何种情况下都需要构建头节点,我们可以先构建一个函数以达到初始化头节点的目的。

void init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;

	_sz = 0;
}

        再实现构造函数,共实现了以下四种构造函数,要点已在代码中提到,其中的尾插函数将在后面实现:

//默认构造函数
list()
{
	init();
}

//此处n的参数若为size_t型,则会在使用int时与使用迭代器的构造函数出现分歧
//list(size_t n, const T& value = T())
list(int n, const T& value = T())
{
	init();

	size_t i = 0;
	while (i < n)
	{
		push_back(value);
		i++;
	}
}

template <class Iterator>
list(Iterator first, Iterator last)
{
	init();

	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

//拷贝构造
list(const list<T>& l)
{
	init();

	for (auto& i : l)
	{
		push_back(i);
	}
}

②析构函数

        析构函数需要遍历链表将每个节点删除,最后删除头节点。

//析构函数
~list()
{
	iterator i = begin();
	while (i != end())
	{

		//使用erase需要传递新的迭代器防止迭代器失效
		i = erase(i);
		i++;
	}

    _sz = 0;

    // 以上代码和clear函数一致,可直接调用clear函数
	delete _head;
}

2.设置迭代器

        头节点的下一个就是第一个数据块,自己本身则为最后一个节点。其中返回_head也不会出错,系统会自动调用构造函数返回参数为_head的迭代器。

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

iterator end()
{
	return _head;
}

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

const_iterator end() const
{
	return _head;
}

3.operator=(重载赋值)

//赋值
list<T>& operator=(list<T> l) //此处不使用常量引用方便拷贝构造后直接交换
//list<T>& operator=(const list<T>& l) 
{
	swap(l);

	return *this;
}

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

4.size函数(获取大小)

size_t size() const
{
	return _sz;
}

5.empty函数(判断空)

bool empty() const
{
	return _sz == 0;
}

6.clear函数(清空)

        使用迭代器遍历清空,不删除头节点。

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

	_sz = 0;
}

8.insert函数(插入)和erase函数(删除)

        插入或删除指定迭代器位置的数据,和带头双向链表一样,先创建一个新节点,再将前一个和当前节点的内容修改即可。需要注意的是二者都会导致迭代器失效,如果要继续使用迭代器,请使用返回更新后的迭代器。

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

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

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

	_sz++;

	return newnode;
}

iterator erase(iterator pos)
{
	assert(pos != end());

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

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

	delete cur;

	--_sz;

	return next;
}

7.push函数(头插、尾插)和pop函数(头删、尾删)

        可以按照往常一样,手搓出来各种功能,不过我们前面实现了insert和erase,为什么不直接调用呢。(懒)

void push_back(const T& x)
{
	/*Node* newnode = new Node(x);

	newnode->_next = _head;
	newnode->_prev = _head->_prev;

	_head->_prev->_next = newnode;
	_head->_prev = newnode;*/

	insert(end(), x);
}

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

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

void pop_front()
{
	erase(begin());
}

五.源码(含部分测试用例)

        使用了命名空间封装了自己模拟的list类。

#pragma once

#include <iostream>
#include <assert.h>

namespace List
{
	//节点
	template<class T>
	struct List_Node
	{
		List_Node<T>* _next;
		List_Node<T>* _prev;
		T _val;

		//使用new初始化节点
		List_Node(const T& val = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}
	};

	//迭代器
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef List_Node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;

		//初始化构造
		__list_iterator(Node* node = nullptr)
			:_node(node)
		{}

		//拷贝构造
		__list_iterator(const iterator& l)
		{
			_node = l._node;
		}

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

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

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

		iterator operator++(int)
		{
			iterator tmp = *this;
			_node = _node->_next;

			return tmp;
		}

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

			return *this;
		}

		iterator operator--(int)
		{
			iterator tmp = *this;
			_node = _node->_prev;

			return tmp;
		}

		bool operator==(const iterator& l) const
		{
			return _node == l._node;
		}

		bool operator!=(const iterator& l) const
		{
			return !(*this == l);
		}

		Node* _node;
	};

	//链表
	template<class T>
	class list
	{
		typedef List_Node<T> Node;
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

	public:
		//构造函数
		list()
		{
			init();
		}

		//此处n的参数若为size_t型,则会在使用int时与使用迭代器的构造函数出现分歧
		//list(size_t n, const T& value = T())
		list(int n, const T& value = T())	
		{
			init();

			size_t i = 0;
			while (i < n)
			{
				push_back(value);
				i++;
			}
		}

		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			init();

			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

		//拷贝构造
		list(const list<T>& l)
		{
			init();

			for (auto& i : l)
			{
				push_back(i);
			}
		}

		void init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			_sz = 0;
		}

		//析构函数
		~list()
		{
			iterator i = begin();
			while (i != end())
			{

				//使用erase需要传递新的迭代器防止迭代器失效
				i = erase(i);
				i++;
			}

			delete _head;
			_sz = 0;
		}

		//赋值
		list<T>& operator=(list<T> l) //此处不使用常量引用方便拷贝构造后直接交换
		//list<T>& operator=(const list<T>& l) 
		{
			swap(l);

			return *this;
		}

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

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

		iterator end()
		{
			return _head;
		}

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

		const_iterator end() const
		{
			return _head;
		}

		void push_back(const T& x)
		{
			/*Node* newnode = new Node(x);
			
			newnode->_next = _head;
			newnode->_prev = _head->_prev;

			_head->_prev->_next = newnode;
			_head->_prev = newnode;*/

			insert(end(), x);
		}

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

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

		void pop_front()
		{
			erase(begin());
		}

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

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

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

			_sz++;

			return newnode;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

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

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

			delete cur;

			--_sz;

			return next;
		}

		size_t size() const
		{
			return _sz;
		}

		bool empty() const
		{
			return _sz == 0;
		}

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

			_sz = 0;
		}

		void test_list1()
		{
			list<T> lt;
			lt.push_back(1);
			lt.push_back(2);
			lt.push_back(3);
			lt.push_back(4);
			for (auto& i : lt)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;

			list<T> New(lt);
			for (auto& i : New)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;

			lt.pop_back();
			lt.pop_back();
			for (auto& i : lt)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;
		}

		void test_list2()
		{
			list<T> lt;
			for (size_t i = 0; i < 5; i++)
				lt.push_front(i);
			for (auto& i : lt)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;

			for (size_t i = 0; i < 2; i++)
				lt.pop_front();
			lt.pop_back();
			for (auto& i : lt)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;
		}

		void test_list3()
		{
			list<T> lt(10, 5);
			std::cout << lt.size() << std::endl;
			for (auto& i : lt)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;

			list<T> New1(lt);
			New1 = *this;
			for (auto& i : New1)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;

			list<T> New2(lt.begin(), lt.end());
			for (auto& i : New2)
			{
				std::cout << i << ' ';
			}
			std::cout << std::endl;
		}

	private:
		Node* _head;
		size_t _sz;
	};
};

猜你喜欢

转载自blog.csdn.net/qq_74641564/article/details/131815049