十四、模拟实现 list 类

Ⅰ . list 基本框架的实现

01 结点的建立

为了实现链表,我们首先要做的应该是建立结点

为了和真正的 list 进行区分,我们仍然在自己的命名空间内实现

代码实现:

cpp 复制代码
namespace yxt
{
	// 建立结点
	template<class T>
	struct ListNode
	{
		T _data;
		ListNode<T>* _next;		// 指向后继节点
		ListNode<T>* _prev;		// 指向前驱节点
	};
}

这里为什么 ListNode 要加 <T> 呢?

因为类模板不支持自动推类型,结构体模板或类模板在定义时可以不加 <T>,但使用时必须加 <T>

准备好 _data,放置好前驱 _next 和后继结点 _prev 后,我们的结点就有了 "结构"

我们知道,结构体 struct 在 C++ 中升级成了类,因此它也有调用构造函数的权利。

也就是说,在创建结构体对象的时会调用构造函数。

既然如此,结点的初始化工作,我们可以考虑写一个构造函数去初始化

02 结点初始化

其实结点初始化就是创建新结点,我们先不考虑开空间的事,完成初始化主要有两个方面:

① 将数据给 _data

② 将 _next 和 _prev 都置成空

这些任务我们可以写到构造函数中,还可以设计成全缺省,给一个匿名对象,这样一来,如果没有指定初始值,它就会按模板类型给对应的初始值

代码实现:

cpp 复制代码
namespace yxt
{
	// 建立结点
	template<class T>
	struct ListNode
	{
		T _data;
		ListNode<T>* _next;		// 指向后继节点
		ListNode<T>* _prev;		// 指向前驱节点
		
		// 结点初始化
		ListNode(const T& data = T())
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};
}

这样,结点就写好了

03 结点的连接

设计好结点后,我们就可以开始实现 list 类了

因为是带头双向循环链表,我们首先要把头节点设计出来

代码实现:

cpp 复制代码
	/* list */
	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		/* 构造函数 */
		list()
		{
			_pHead = new Node();
			_pHead->_next = _pHead;		// 默认指向头结点
			_pHead->_prev = _pHead;		// 默认指向头结点    
		}
	private:
		Node* _pHead;
	};

04 push_back

我们先实现一下 push_back,好让 list 先跑起来

步骤一:找到尾结点并创建新结点

虽然我们没有定义 _pTail,但对于双向带头循环链表来说,找到尾结点很容易,尾结点就是头结点的前驱指针,这里创建新结点也很容易,我们直接 new 一个新结点,自动调用我们刚才写的建立结点

步骤二:连接新结点与原链表

这里连接我们主要分两步:一是把新结点与尾结点连接起来 二是把新结点和头结点连接起来

我们直接来看代码

代码实现:

cpp 复制代码
		/* push_back */
		void push_back(const T& x)
		{
			// 找尾结点并建立新结点
			Node* _pTail = _pHead->_prev;
			Node* new_node = new Node(x);

			// 新结点和尾结点相连
			_pTail->_next = new_node;
			new_node->_prev = _pTail;

			// 新结点和头结点相连
			new_node->_next = _pHead;
			_pHead->_prev = new_node;
		}

尾插写好之后,我们调试一下:

cpp 复制代码
	void list_test1()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);
	}

调试结果如下:

Ⅱ . list 迭代器的实现

01 引入

list 的重点是迭代器,因为这里迭代器的实现和我们之前讲的实现方式都不同

我们之前实现的 string 和 vector 的迭代器都是一个原生指针,实现很简单

但 list 是一个链表,空间上不连续,又如何实现呢?

在链表中,想要找到下一个结点,通常需要解引用和 ++

而自带的解引用和 ++ ,并不能满足我们的要求,但我们可以对其进行重载

02 迭代器的构造

代码实现:

cpp 复制代码
	/* 定义迭代器 */
	template<class T>
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		Node* _node;

		/* 迭代器的构造 */
		_list_iterator(Node* x)
			:_node(x)
		{}
	};

03 operator ++

加加分前置和后置,我们先实现前置

代码实现:

cpp 复制代码
		/* ++it */
		_list_iterator<T>& operator++()
		{
			_node = _node->_next;
			return *this;
		}

因为前置直接改变主体,我们直接 return *this 即可

因为出了作用域还在,所以可以返回引用

对应的,后置++ 我们可以拷贝构造出一个 tmp 来保存原来的值,这样虽然改变了主体

但返回的还是之前的值,这样就实现了后置++

因为前置++后置++都是 operator++,区分方式是后置++用占位符 (int) 占位

代码实现:

cpp 复制代码
		/* it++ */
		_list_iterator<T>& operator++(int)
		{
			_list_iterator<T> tmp(*this);
			_node = _node->_next;
			return tmp;
		}

04 operator *

解引用就是取结点里的数据

并且operator* 和指针一样,不仅能读数据,还能写数据

为了使其支持修改的操作,我们这里用引用返回

代码实现:

cpp 复制代码
		/* 解引用 */
		T& operator*()
		{
			return _node->_data;
		}

05 测试 实现 begin() 和 end()

有了 operator++ 和 operator* ,我们就可以来测试一下我们的迭代器了

begin 是第一个存有效数据的结点,即 _pHead 的下一个位置的结点。

而 end 返回的是最后一个数据的下一个位置,即 _pHead

代码实现:

cpp 复制代码
		typedef _list_iterator<T> iterator;

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

		iterator end()
		{
			return _pHead;
		}

因为迭代器要用到 != ,所以我们要实现操作符的重载

06 operator!=

如何判断是否相等呢?

如果两个迭代器结点的指针指向的是同一个结点,那就说明是相等的迭代器。

代码实现:

cpp 复制代码
		/* != */
		bool operator!=(const _list_iterator<T>& it)
		{
			return _node != it._node;
		}

测试:

cpp 复制代码
	void list_test1()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);

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

运行结果如下:

07 迭代器的拷贝构造、赋值和析构

拷贝构造和赋值重载是否需要自己实现?析构呢?

list 的拷贝构造和赋值不需要自己实现,默认生成的即可

cpp 复制代码
it2(it1)
it2 = it1 浅拷贝

当前迭代器赋值给另一个迭代器使不需要深拷贝的,浅拷贝即可

我们再谈谈析构为什么不需要自己实现

cpp 复制代码
template<class T> 
struct __list_iterator 
{
    typedef ListNode<T> Node; 
    Node* _node;
    ...
}

迭代器这里虽然有一个结点的指针,但它并不是迭代器管的,是 list 管的

list 的析构函数会把这个结点给释放掉

所以它的释放和迭代器没什么关系

**总结:**迭代器是借助结点的指针访问修改链表的,结点属于链表,不属于迭代器,所以不用管它的释放问题,因此,拷贝构造、赋值重载和析构,这些都不需要自己实现,默认生成的即可

08 打印链表

我们刚才实现好了迭代器:

cpp 复制代码
		list<int>::iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

不用范围 for 的前提下,用迭代器似乎挺麻烦,我们可以放到一个函数里

这里为了减少拷贝,使用引用返回,我们没有实现 const 迭代器,会导致没法遍历:

测试:

cpp 复制代码
	void print_list(const list<int>& l)
	{
		list<int>::iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void list_test1()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);

		print_list(l);
	}

运行结果:报错

const 迭代器和普通迭代器的区别是什么?

普通迭代器访问普通对象,可读可写;const 迭代器访问 const 对象,可读不可写。

所以我们自然是要实现 const 迭代器

09 const 迭代器的实现

传统方法是把 list_iterator 直接 CV,然后改成 const 的

代码实现:

cpp 复制代码
		/* 定义const迭代器 */
		template<class T>
		struct _const_list_iterator
		{
			typedef ListNode<T> Node;
			Node* _node;

			/* 迭代器的构造 */
			_const_list_iterator(Node* x)
				:_node(x)
			{}

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

			/* it++ */
			_const_list_iterator<T>& operator++(int)
			{
				_const_list_iterator<T> tmp(*this);
				_node = _node->_next;
				return tmp;
			}

			/* 解引用 */
			const T& operator*()
			{
				return _node->_data;
			}

			/* != */
			bool operator!=(const _const_list_iterator<T>& it)
			{
				return _node != it._node;
			}

这里我们把 __list_iterator 都修改成 __const_list_iterator,

并且对于解引用 operator* 的重载,我们将其改成 const 引用返回,这样就只能读不能写了。

代码:们这里再在 list 中 typedef 一下 const 迭代器

cpp 复制代码
	/* list */
	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		/* 迭代器 */
		typedef _list_iterator<T> iterator;
		typedef _const_list_iterator<T> const_iterator;

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

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

const 迭代器和普通迭代器不是一个类型

不是迭代器是 const ,而是对象是 const,所以要调用 const 的 begin 和 end才行

所以还要写 __const_list_iterator 类型的 begin 和 end,我们用 const 去修饰,限制它写的权限

代码实现:

cpp 复制代码
		const_iterator begin() const
		{
			return const_iterator(_pHead->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_pHead);
		}

测试:

cpp 复制代码
	void print_list(const list<int>& l)
	{
		list<int>::const_iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void list_test1()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);

		print_list(l);
	}

运行结果如下:

10 使用模板实现 const 迭代器

通过加一个额外的模板参数去控制 operator 的返回值,你能想到吗?

在定义 template 模板的时增加一个参数 Ref :

这样的话,我们 operator* 的返回值我们不要用 T&了,我们改成 Ref:

也就是说,让 operator* 的返回值变成 Ref 这个模板参数!

代码:之后我们在 list 中 typedef 的时候就可以传 T& 或 const T&

cpp 复制代码
	public:
		/* 迭代器 */
		typedef _list_iterator<T, T&> iterator;
		typedef _const_list_iterator<T, const T&> const_iterator;

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

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

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

		const_iterator end() const
		{
			return const_iterator(_pHead);
		}

一:Ref 是 T& ,可读可写

这里 test_list1 是一个普通对象,调用的自然是普通的 begin。 begin 返回的是普通迭代器 __list_iterator<T, T&>,第二个模板参数是 T&,Ref 就是 T& 了。operator* 的返回值 Ref 是 T& 了,这样就做到了可读可写了。

二:Ref 是 const T& ,可读不可写

比如这里的 print_list 就是一个 const 对象,它调用的就是 const 的 begin。const begin 返回的是 const 迭代器 __list_iterator<T, const T&>,第一个值传的都是 T,第二个值 const T& 传给 Ref。那么 operator* 的返回值 Ref 就是 const T& 了,这样就做到了可读但不可写的。

模板重命名

时去编译,是编译不通过的。

因为我们多定义了一个 Ref,所以 __list_iterator 中的所有类模板都得加上它,比如:

这样加来加去是不是太不方便了?我们来看看设计STL的大佬是怎么做的:

把这些都 typedef 一下,这样我们就可以把 __list_iterator<T, Ref> 写成 Self 了:

代码实现:

cpp 复制代码
	/* 定义迭代器 */
	template<class T, class Ref>
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		typedef _list_iterator<T, Ref> Self;
		Node* _node;

		/* 迭代器的构造 */
		_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;
		}

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

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

		/* 解引用 */
		Ref operator*()
		{
			return _node->_data;
		}

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

箭头操作符

迭代器是像指针一样的东西,所以要重载两个解引用

为什么呢?指针如果指向的类型是原生的普通类型,要取对象是可以用解引用的

但如果指向的是一个结构,并且我们又要取它的每一个成员变量,比如这样:

代码:比如是一个日期类,我们没有实现流提取

cpp 复制代码
	struct Date 
    {
		int _year;
		int _month;
		int _day;
 
		Date(int year = 1, int month = 1, int day = 1) 
			: _year(year)
			, _month(month)
			, _day(day) 
		{}
	};
 
	void test_list3() 
    {
		list<Date> L;
		L.push_back(Date(2022, 5, 1));
		L.push_back(Date(2022, 5, 2));
		L.push_back(Date(2022, 5, 3));
 
		list<Date>::iterator it = L.begin();
		while (it != L.end()) 
        {
			// cout << *it << " ";  假设我们没有实现流提取,我们自己访问
			cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
			it++;
		}
		cout << endl;
	}

运行结果如下:

我们发现,在没有实现流提取的前提下,想遍历链表L

我们就需要用 *(it)._xxx 去访问,大多数主流习惯是用 -> 去访问的

所以我们这里去实现一下 ->:

cpp 复制代码
		/* 解引用 */
		Ref operator*()
		{
			return _node->_data;
		}

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

总结:所以类型重载 operator-> 时都会省略一个箭头

这里还面临一个问题------const 迭代器

如果是一个 const 迭代器用箭头也是可以去修改数据的,基于这样一个原因

我们还需要增加一个模板参数:Ptr

此时刚才 typedef 的 Self 就体现出价值了

我们只需要在 Self 中加个 Ptr:

我们直接把 operator-> 的返回值修改成 Ptr 就行了

到时候我们传一个 T* 或 const T* 给 Ptr 就做到适配普通迭代器和 const 迭代器的 operator-> 了。

代码:

cpp 复制代码
	/* 定义迭代器 */
	template<class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		typedef _list_iterator<T, Ref, Ptr> Self;
		Node* _node;

        /* 解引用 */
        Ref operator*()
        {
	        return _node->_data;
        }

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

    /* 链表 */
    template<class T>
    class list
    {
	    typedef ListNode<T> Node;
    public:
	    /* 迭代器 */
	    typedef _list_iterator<T, T&, T*> iterator;
	    typedef _list_iterator<T, const T&, const T*> const_iterator;
    };

这里我们把传递的值增加一个 T* 和 const T* ,如此一来就做到了完美的适配

11 反向迭代器的实现

我们来看一下源代码是如何实现的:

反向迭代器其实就是对正向迭代器的一种封装------适配器模式

代码:

cpp 复制代码
namespace yxt
{
	// Iterator是哪个容器的迭代器,reverse_iterator<Iterator>就可以
	// 适配出哪个容器的反向迭代器。复用的体现
	template<class Iterator, class Ref, class Ptr>
	class reverse_iterator
	{
		typedef reverse_iterator<Iterator, Ref, Ptr> Self;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}

		Ref operator*()
		{
			Iterator prev = _it;
			return *--prev;
		}

		Ptr operator->()
		{
			return &operator*();
		}

		Self& operator++()
		{
			--_it;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp(*this);
			--_it;
			return tmp;
		}

		Self& operator--()
		{
			++_it;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			++_it;
			return tmp;
		}

		bool operator!=(const Self& l) const
		{
			return _it != l._it;
		}

	private:
		Iterator _it;
	};
}

Ⅲ . list 增删查改

01 在 pos 位置前插入 - insert

在 pos 位置插入,我们通过 pos 找到前驱 prev,之后创建新结点,再连接起来

代码实现:

cpp 复制代码
		/* 在pos位置前插入 */
		void insert(iterator pos, const T& x)
		{
			// 找到pos位置的结点
			Node* cur = pos._node;
			Node* cur_prev = cur->_prev;
			// 创建新结点
			Node* new_node = new Node(x);
			// 连接
			cur_prev->_next = new_node;
			new_node->_prev = cur_prev;
			new_node->_next = cur;
			cur->_prev = new_node;
		}

insert 以后,pos是否失效?不会

优化:

cpp 复制代码
		/* 在pos位置前插入 */
		iterator insert(iterator pos, const T& x)
		{
			// 找到pos位置的结点
			Node* cur = pos._node;
			Node* cur_prev = cur->_prev;
			// 创建新结点
			Node* new_node = new Node(x);
			// 连接
			cur_prev->_next = new_node;
			new_node->_prev = cur_prev;
			new_node->_next = cur;
			cur->_prev = new_node;

			return iterator(new_node);
		}

有了 insert 之后,我们可以直接复用实现 push_back

代码实现:

cpp 复制代码
		/* push_back */
		void push_back(const T& x)
		{
			 找尾结点并建立新结点
			//Node* _pTail = _pHead->_prev;
			//Node* new_node = new Node(x);

			 新结点和尾结点相连
			//_pTail->_next = new_node;
			//new_node->_prev = _pTail;

			 新结点和头结点相连
			//new_node->_next = _pHead;
			//_pHead->_prev = new_node;

			insert(end(), x);
		}

push_back 复用 insert,pos 我们给 end() 。因为 end() 是头结点*_pHead*,

在头结点前面插入,insert 的 cur_prev 就会代表尾结点,会在 cur_prev 后面插入 new_node

并完成连接,这就做到了尾插

02 push_front

代码实现:

cpp 复制代码
		/* push_front */
		void push_front(const T& x)
		{
			insert(begin(), x);
		}

03 删除 pos 位置的结点 erase

步骤如下:

① 找到 pos 的前驱和后继

② 释放 pos 位置的结点

③ 将已经删除的 pos 结点的前驱和后继连接

注意: 我们还要防止哨兵位头结点*_pHead*被删的情况,头不小心卸了就没法玩了。

这里我还是习惯用暴力的方式去解决,用 assert 断言处理。

代码实现:

cpp 复制代码
		/* 任意位置删除 */
		void erase(iterator pos)
		{
			// 防止头结点被删
			assert(pos != end());
			// 找到pos位置的结点
			Node* cur = pos._node;
			Node* cur_prev = cur->_prev;
			Node* cur_next = cur->_next;
			// 删除pos位置的结点
			delete[] cur;
			cur = nullptr;
			// 连接
			cur_prev->_next = cur_next;
			cur_next->_prev = cur_prev;
		}

erase 以后,pos是否失效?

一定会失效!因为结点的指针指向的结点被干掉了,这当然会失效

我们可以学着文档里的处理方式 ------ 返回刚刚被删除的元素的下一个元素

cpp 复制代码
		/* 任意位置删除 */
		iterator erase(iterator pos)
		{
			// 防止头结点被删
			assert(pos != end());
			// 找到pos位置的结点
			Node* cur = pos._node;
			Node* cur_prev = cur->_prev;
			Node* cur_next = cur->_next;
			// 删除pos位置的结点
			delete[] cur;
			cur = nullptr;
			// 连接
			cur_prev->_next = cur_next;
			cur_next->_prev = cur_prev;

			return iterator(cur_next);
		}

04 pop_back

代码实现:

cpp 复制代码
		/* 尾删 */
		void pop_back()
		{
			erase(_pHead->_prev);
		}

当然你也可以这么写:

cpp 复制代码
/* 尾删 */
void pop_back() 
{
	erase(--end());  // 删除最后一个元素,即尾结点
}

05 pop_front

代码实现:

cpp 复制代码
		/* 头删 */
		void pop_front()
		{
			erase(begin());
		}

Ⅳ . 拷贝构造和赋值重载

01 list 同样涉及深浅拷贝问题

这里的拷贝构造是深拷贝还是浅拷贝?

cpp 复制代码
	void list_test2()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);

		list<int> l2(l1);
		for (auto e : l2)
			cout << e << " ";
		cout << endl;
	}

运行结果如下:

这里默认生成的拷贝构造是浅拷贝

浅拷贝导致 l1 和 l2 指向同一块地址,析构的时候导致同一块空间被释放两次

02 clear 清空链表中的所有数据

代码实现:

cpp 复制代码
		/* 清空链表所有数据 */
		void clear()
		{
			iterator cur = begin();
			while (cur != end())
			{
				iterator del = cur++;
				delete del._node;
			}

			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;
		}

测试:

cpp 复制代码
	void list_test3()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		cout << "删除前:";
		print_list(l1);

		cout << "删除后:";
		l1.clear();
		print_list(l1);
	}

运行结果如下:

当然,这里的删除结点我们也可以用 erase 去完成。

用 erase 可以省去我们将其恢复 _pHead的前驱和后继指向自己的操作。

简化:

cpp 复制代码
		/* 清空链表所有数据 */
		void clear()
		{
			iterator cur = begin();
			while (cur != end())
			{
				erase(cur++);
			}
		}

03 析构

代码实现:

cpp 复制代码
		~list()
		{
			clear();
			delete _pHead;
			_pHead = nullptr;
		}

实现好了析构函数,我们再回过头来测刚才 "逃过一劫" 的浅拷贝导致的两个结点指向同一地址的问题,现在问题就变得严重起来了,因为它会被析构两次:

cpp 复制代码
	void list_test2()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);

		list<int> l2(l1);
		for (auto e : l2)
			cout << e << " ";
		cout << endl;
	}

运行结果:崩溃

自动生成的拷贝构造是浅拷贝,为了解决这个问题,我们需要手动实现一个深拷贝的拷贝构造!

04 拷贝构造

代码实现:传统写法

cpp 复制代码
		/* 拷贝构造 */
		list(const list<T>& l)
		{
			_pHead = new Node();
			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;

			for (auto e : l)
				push_back(e);
		}

代码实现:现代写法

cpp 复制代码
		list(const list<T>& l)
		{
			_pHead = new Node();
			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;

			list<T> tmp(l.begin(), l.end());
			swap(_pHead, tmp._pHead);
		}

05 赋值重载

代码实现:传统写法

cpp 复制代码
		/* 赋值(传统写法) */
		list<T>& operator=(list<T> l)
		{
			if (this != &l)
			{
				clear();
				for (auto e : l)
					push_back(e);
			}
			return *this;
		}

代码实现:现代写法

cpp 复制代码
		/* 赋值(现代写法) */
		list<T>& operator=(list<T> l)
		{
			swap(_pHead, l._pHead);
			return *this;
		}

Ⅴ . 完整代码

list.h

cpp 复制代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace yxt
{
	/* 建立结点 */
	template<class T>
	struct ListNode
	{
		T _data;
		ListNode<T>* _next;		// 指向后继节点
		ListNode<T>* _prev;		// 指向前驱节点

		/* 结点初始化 */
		ListNode(const T& data = T())
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};

	/* 定义迭代器 */
	template<class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		typedef _list_iterator<T, Ref, Ptr> Self;
		Node* _node;

		/* 迭代器的构造 */
		_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;
		}

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

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

		/* 解引用 */
		Ref operator*()
		{
			return _node->_data;
		}

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

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

	///* 定义const迭代器 */
	//template<class T>
	//struct _const_list_iterator
	//{
	//	typedef ListNode<T> Node;
	//	Node* _node;

	//	/* 迭代器的构造 */
	//	_const_list_iterator(Node* x)
	//		:_node(x)
	//	{}

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

	//	/* it++ */
	//	_const_list_iterator<T>& operator++(int)
	//	{
	//		_const_list_iterator<T> tmp(*this);
	//		_node = _node->_next;
	//		return tmp;
	//	}

	//	/* 解引用 */
	//	const T& operator*()
	//	{
	//		return _node->_data;
	//	}

	//	/* != */
	//	bool operator!=(const _const_list_iterator<T>& it)
	//	{
	//		return _node != it._node;
	//	}

	//};

	/* 链表 */
	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		/* 迭代器 */
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;

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

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

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

		const_iterator end() const
		{
			return const_iterator(_pHead);
		}

		/* 构造函数 */
		list()
		{
			_pHead = new Node();
			_pHead->_next = _pHead;		// 默认指向头结点
			_pHead->_prev = _pHead;		// 默认指向头结点    
		}

		/* push_back */
		void push_back(const T& x)
		{
			 找尾结点并建立新结点
			//Node* _pTail = _pHead->_prev;
			//Node* new_node = new Node(x);

			 新结点和尾结点相连
			//_pTail->_next = new_node;
			//new_node->_prev = _pTail;

			 新结点和头结点相连
			//new_node->_next = _pHead;
			//_pHead->_prev = new_node;

			insert(end(), x);
		}

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

		/* 在pos位置前插入 */
		iterator insert(iterator pos, const T& x)
		{
			// 找到pos位置的结点
			Node* cur = pos._node;
			Node* cur_prev = cur->_prev;
			// 创建新结点
			Node* new_node = new Node(x);
			// 连接
			cur_prev->_next = new_node;
			new_node->_prev = cur_prev;
			new_node->_next = cur;
			cur->_prev = new_node;

			return iterator(new_node);
		}

		/* 任意位置删除 */
		iterator erase(iterator pos)
		{
			// 防止头结点被删
			assert(pos != end());
			// 找到pos位置的结点
			Node* cur = pos._node;
			Node* cur_prev = cur->_prev;
			Node* cur_next = cur->_next;
			// 删除pos位置的结点
			delete[] cur;
			cur = nullptr;
			// 连接
			cur_prev->_next = cur_next;
			cur_next->_prev = cur_prev;

			return iterator(cur_next);
		}

		/* 尾删 */
		void pop_back()
		{
			erase(_pHead->_prev);
		}

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

		/* 清空链表所有数据 */
		void clear()
		{
			iterator cur = begin();
			while (cur != end())
			{
				erase(cur++);
			}
		}

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

		template<class InputInterator>
		list(InputInterator first, InputInterator last)
		{
			_pHead = new Node();
			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;

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

		/* 拷贝构造(现代写法) */
		list(const list<T>& l)
		{
			_pHead = new Node();
			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;

			list<T> tmp(l.begin(), l.end());
			swap(_pHead, tmp._pHead);
		}

		/* 拷贝构造(传统写法) */
		list(const list<T>& l)
		{
			_pHead = new Node();
			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;

			for (auto e : l)
				push_back(e);
		}

		/* 赋值(传统写法) */
		list<T>& operator=(list<T> l)
		{
			if (this != &l)
			{
				clear();
				for (auto e : l)
					push_back(e);
			}
			return *this;
		}

		/* 赋值(现代写法) */
		list<T>& operator=(list<T> l)
		{
			swap(_pHead, l._pHead);
			return *this;
		}
	private:
		Node* _pHead;
	};
}

test.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"

namespace yxt
{
	void print_list(const list<int>& l)
	{
		list<int>::const_iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void list_test1()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);
		l.push_back(4);

		print_list(l);
	}

	void list_test2()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);

		list<int> l2(l1);
		for (auto e : l2)
			cout << e << " ";
		cout << endl;
	}

	void list_test3()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		cout << "删除前:";
		print_list(l1);

		cout << "删除后:";
		l1.clear();
		print_list(l1);
	}
}

int main()
{
	//yxt::list_test1();
	yxt::list_test2();
	//yxt::list_test3();
	return 0;
}

reverse_list.h

cpp 复制代码
#pragma once
#include"list.h"

namespace yxt
{
	// Iterator是哪个容器的迭代器,reverse_iterator<Iterator>就可以
	// 适配出哪个容器的反向迭代器。复用的体现
	template<class Iterator, class Ref, class Ptr>
	class reverse_iterator
	{
		typedef reverse_iterator<Iterator, Ref, Ptr> Self;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}

		Ref operator*()
		{
			Iterator prev = _it;
			return *--prev;
		}

		Ptr operator->()
		{
			return &operator*();
		}

		Self& operator++()
		{
			--_it;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp(*this);
			--_it;
			return tmp;
		}

		Self& operator--()
		{
			++_it;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			++_it;
			return tmp;
		}

		bool operator!=(const Self& l) const
		{
			return _it != l._it;
		}

	private:
		Iterator _it;
	};
}
相关推荐
我是谁??29 分钟前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
发霉的闲鱼1 小时前
MFC 重写了listControl类(类名为A),并把双击事件的处理函数定义在A中,主窗口如何接收表格是否被双击
c++·mfc
小c君tt1 小时前
MFC中Excel的导入以及使用步骤
c++·excel·mfc
xiaoxiao涛1 小时前
协程6 --- HOOK
c++·协程
羊小猪~~3 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德4 小时前
多项式加法——C语言
数据结构·c++·算法
legend_jz4 小时前
STL--哈希
c++·算法·哈希算法
CSUC4 小时前
【C++】父类参数有默认值时子类构造函数列表中可以省略该参数
c++
Vanranrr4 小时前
C++ QT
java·c++·qt
鸿儒5174 小时前
C++ lambda 匿名函数
开发语言·c++