21. C++STL 7(8000字详解list及其迭代器的模拟实现)

⭐本篇重点:STL中的list及其迭代器的模拟实现和测试

⭐本篇代码:c++学习 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)

目录

[一. list的节点](#一. list的节点)

[二. list的迭代器](#二. list的迭代器)

[2.1 迭代器框架](#2.1 迭代器框架)

[2.2 迭代器实现](#2.2 迭代器实现)

[三. list的实现](#三. list的实现)

[3.1 list的构造函数](#3.1 list的构造函数)

[3.2 insert](#3.2 insert)

[3.2 erase](#3.2 erase)

[3.3 begin和end](#3.3 begin和end)

[3.4 push_back和push_front](#3.4 push_back和push_front)

[3.5 pop_back和pop_front](#3.5 pop_back和pop_front)

[3.6 clear和析构函数](#3.6 clear和析构函数)

[3.7 测试代码1](#3.7 测试代码1)

[3.8 拷贝构造函数和赋值运算符重载](#3.8 拷贝构造函数和赋值运算符重载)

[四. test.h 源代码](#四. test.h 源代码)

[五. 测试自定义类型和类类型](#五. 测试自定义类型和类类型)

5.1测试string类

[5.2 测试自定义类](#5.2 测试自定义类)

[六. 下篇重点:stack和queue的使用与模拟实现](#六. 下篇重点:stack和queue的使用与模拟实现)


一. list的节点

我们知道,list是一个带头的双向循环链表。所以这个节点应该包含以下成员变量。

    //表示链表的节点
    template<class T>
	struct ListNode
	{
		ListNode* _next;
		ListNode* _prev;
		T _data;

        //构造函数
		ListNode(const T& data = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{};
	};

二. list的迭代器

我们模拟过string和vector,它两的迭代器都用原生指针就能实现。但是list的迭代器使用原生指针无法实现。比如我们++it,不能简单的让指针+1就行。

2.1 迭代器框架

list迭代器结构体如下:

cpp 复制代码
	//迭代器,T为节点的data,Ptr表示data的地址,Ref表示data的引用
	template<class T, class Ptr, class Ref>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ptr, Ref> Self;
		Node* _node;

		//构造函数
		ListIterator(Node* node)
			:_node(node)
		{}

		//前置++
		Self& operator++()
		{}

		//后置++
		Self operator++(int)
		{}

		//前置--
		Self& operator--()
		{}

		//后置--
		Self operator--(int)
		{}

		//解引用,获取这个节点
		Ref operator*()
		{}

		//->,箭头获取的是节点的指针
		Ptr operator->()
		{}

		//判断是否相等
		bool operator==(const Self& self)
		{}

		//判断是否不等
		bool operator!=(const Self& self)
		{}
	};

2.2 迭代器实现

operator++

在list中,++只需要让我们的指针指向当前节点的下一个节点即可

前置++

cpp 复制代码
		//前置++
		Self& operator++()
		{	
			//返回++后的结果
			_node = _node->_next;
			return *this;	
		}

后置++。后置++注意要用中间变量保存并且不能引用返回!

cpp 复制代码
		//后置++
		Self operator++(int)
		{
			//返回++前的结果,需要保存++前的指针
			//注意这里不可使用引用返回,tmp在栈中。属于局部变量,出函数会销毁!
			Node* tmp = _node;
			_node = _node->_next;
			return tmp;
		}

operator* 和 operator->

*返回当前节点的data,->返回当前节点data的地址(即一个指针)

比如: *it = data it-> = &data

cpp 复制代码
		//解引用,获取这个节点
		Ref operator*()
		{
			return _node->_data;
		}

		//->,箭头获取的是节点的指针
		Ptr operator->()
		{
			return &_node->_data;
		}

根据上面的代码 --和== !=同理可以实现

迭代器的全部实现代码如下:

cpp 复制代码
	//迭代器,T为节点的data,Ptr表示data的地址,Ref表示data的引用
	template<class T, class Ptr, class Ref>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ptr, Ref> Self;
		Node* _node;

		//构造函数
		ListIterator(Node* node)
			:_node(node)
		{}

		//前置++
		Self& operator++()
		{	
			//返回++后的结果
			_node = _node->_next;
			return *this;	
		}

		//后置++
		Self operator++(int)
		{
			//返回++前的结果,需要保存++前的指针
			//注意这里不可使用引用返回,tmp在栈中。属于局部变量,出函数会销毁!
			Node* tmp = _node;
			_node = _node->_next;
			return tmp;
		}

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

		//后置--
		Self operator--(int)
		{
			Node* tmp = _node;
			_node = _node->_prev;
			return tmp;
		}

		//解引用,获取这个节点
		Ref operator*()
		{
			return _node->_data;
		}

		//->,箭头获取的是节点的指针
		Ptr operator->()
		{
			return &_node->_data;
		}

		//判断是否相等
		bool operator==(const Self& it)
		{
			return it._node == _node;
		}

		//判断是否不等
		bool operator!=(const Self& it)
		{
			return it._node != _node;
		}
	};

三. list的实现

list的框架如下。位于 test.h中

cpp 复制代码
	template<class T>
	class list
	{
		typedef ListNode<T> Node;	//typedef list的节点
	public:
		//list 的迭代器
		typedef ListIterator<T, T*, T&> iterator;
		typedef ListIterator<T, const T*, const T&> const_iterator;


	private:
		Node* _head;	//list的头节点
	};

3.1 list的构造函数

list是带头双向循环链表。只要在堆中开辟一个头节点,然后让它的next和prev都指向自己即可。注意头节点的data不存储任何值。

cpp 复制代码
		//构造函数
		list()
			:_head(new Node)
		{
			_head->_next = _head;
			_head->_prev = _head;
		}

3.2 insert

和vector一样,我们先定义出insert和erase。然后push_back和pop_back去复用insert和erase的代码可以提高代码的复用。

思考一下list的insert中的pos是什么? list没有下标,只能用迭代器表示pos

插入代码的逻辑图如下

我们只要让prev链接号newnode,再让newnode链接好cur即可 (注意提前保存好cur)

代码如下:

cpp 复制代码
		//insert。在pos位置插入data
		void insert(const iterator& pos, const T& data)
		{
			Node* newnode = new Node(data);
			Node* cur = pos._node;
			Node* pre = cur->_prev;

			//1.链接pre和newnode
			pre->_next = newnode;
			newnode->_prev = pre;

			//2.链接newnode和cur
			newnode->_next = cur;
			cur->_prev = newnode;
		}

即便只有一个头节点上面代码也没问题。逻辑图如下

3.2 erase

erase比较简单。找到pos的前后节点pre和next,链接pre和next,然后删除cur即可

注意:返回的节点应该是next节点(即删除cur后,next处于pos的位置)

代码如下:

cpp 复制代码
        //erase,删除pos位置的节点
		iterator erase(const iterator& pos)
		{
			Node* cur = pos._node;
			Node* pre = cur->_prev;
			Node* next = cur->_next;

			pre->_next = next;
			next->_prev = pre;

			delete cur;
			return next;	//删除cur后,pos就处于next了
		}

3.3 begin和end

begin返回第一个节点(头节点的next)的迭代器,end最后一个节点后一个的迭代器(就是头节点)

代码如下:

cpp 复制代码
		iterator begin()
		{
			return _head->_next;
		}

		iterator end()
		{
			return _head;
		}

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

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

3.4 push_back和push_front

有了insert这两个函数就简单了。push_back直接在end()这个迭代器调用insert,push_front直接在begin()调用insert。

代码如下:

cpp 复制代码
		//尾插
		void push_back(const T& data)
		{
			insert(end(), data);
		}

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

3.5 pop_back和pop_front

同理。调用erase即可。不过注意尾删是删除最后一个节点不是头节点!

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

		//尾删
		void pop_back()
		{
			//erase(end());			//不是删除头节点,而是头节点的前一个节点(尾节点)
			erase(_head->_prev);
		}

3.6 clear和析构函数

利用迭代器遍历链表,一个一个删除节点即可。clear不会删除头节点

cpp 复制代码
		//clear清空链表
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);	//后置++,it走到下一个节点后。返回前一个节点去删除即可
			}
		}

析构函数。调用clear然后删除头节点即可。

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

3.7 测试代码1

到此为止,整个链表基本实现了。我们来测试一下

测试代码 test.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include "test.h"
using namespace std;

void test1()
{
	YZC::list<int> l1;

	for (int i = 0; i < 5; i++)		// 0 1 2 3 4 
		l1.push_back(i);
	
	for (int i = 10; i < 15; i++)	// 14 13 12 11 10 0 1 2 3 4
		l1.push_front(i);	

	l1.pop_back();	// 14 13 12 11 10 0 1 2 3
	l1.pop_front();	// 13 12 11 10 0 1 2 3

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

int main()
{
	test1();
	return 0;
}

运行结果如下

3.8 拷贝构造函数和赋值运算符重载

拷贝构造。遍历构造即可

cpp 复制代码
		//拷贝构造
		list(const list<T>& l)
			:_head(new Node)
		{
			//1.初始化头节点
			_head->_next = _head;
			_head->_prev = _head;

			//2.遍历l,尾插节点即可
			for (auto const& e : l)
			{
				push_back(e);
			}
		}

赋值运算符重载。利用临时变量出函数销毁的特性

cpp 复制代码
		//operator=
		list& operator=(list<T> l)
		{
			swap(_head, l._head);
			return *this;
		}

四. test.h 源代码

cpp 复制代码
#pragma once

namespace YZC
{
	//表示链表的节点
	template<class T>
	struct ListNode
	{
		ListNode* _next;
		ListNode* _prev;
		T _data;

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

	//迭代器,T为节点的data,Ptr表示data的地址,Ref表示data的引用
	template<class T, class Ptr, class Ref>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ptr, Ref> Self;
		Node* _node;

		//构造函数
		ListIterator(Node* node)
			:_node(node)
		{}

		//前置++
		Self& operator++()
		{	
			//返回++后的结果
			_node = _node->_next;
			return *this;	
		}

		//后置++
		Self operator++(int)
		{
			//返回++前的结果,需要保存++前的指针
			//注意这里不可使用引用返回,tmp在栈中。属于局部变量,出函数会销毁!
			Node* tmp = _node;
			_node = _node->_next;
			return tmp;
		}

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

		//后置--
		Self operator--(int)
		{
			Node* tmp = _node;
			_node = _node->_prev;
			return tmp;
		}

		//解引用,获取这个节点
		Ref operator*()
		{
			return _node->_data;
		}

		//->,箭头获取的是节点的指针
		Ptr operator->()
		{
			return &_node->_data;
		}

		//判断是否相等
		bool operator==(const Self& it)
		{
			return it._node == _node;
		}

		//判断是否不等
		bool operator!=(const Self& it)
		{
			return it._node != _node;
		}
	};

	template<class T>
	class list
	{
		typedef ListNode<T> Node;	//typedef list的节点
	public:
		//list 的迭代器
		typedef ListIterator<T, T*, T&> iterator;
		typedef ListIterator<T, const T*, const T&> const_iterator;

		//构造函数
		list()
			:_head(new Node)
		{
			_head->_next = _head;
			_head->_prev = _head;
		}

		//拷贝构造
		list(const list<T>& l)
			:_head(new Node)
		{
			//1.初始化头节点
			_head->_next = _head;
			_head->_prev = _head;

			//2.遍历l,尾插节点即可
			for (auto const& e : l)
			{
				push_back(e);
			}
		}

		//operator=
		list& operator=(list<T> l)
		{
			swap(_head, l._head);
			return *this;
		}
		
		//析构函数
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		//clear清空链表
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);	//后置++,it走到下一个节点后。返回前一个节点去删除即可
			}
		}

		//insert。在pos位置插入data
		void insert(const iterator& pos, const T& data)
		{
			Node* newnode = new Node(data);
			Node* cur = pos._node;
			Node* pre = cur->_prev;

			//1.链接pre和newnode
			pre->_next = newnode;
			newnode->_prev = pre;

			//2.链接newnode和cur
			newnode->_next = cur;
			cur->_prev = newnode;
		}

		//erase,删除pos位置的节点
		iterator erase(const iterator& pos)
		{
			Node* cur = pos._node;
			Node* pre = cur->_prev;
			Node* next = cur->_next;

			pre->_next = next;
			next->_prev = pre;

			delete cur;
			return next;	//删除cur后,pos就处于next了
		}

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

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

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

		//尾删
		void pop_back()
		{
			//erase(end());			//不是删除头节点,而是头节点的前一个节点(尾节点)
			erase(_head->_prev);
		}

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

		iterator end()
		{
			return _head;
		}

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

		const_iterator end()const
		{
			return const_iterator(_head);
		}
	private:
		Node* _head;	//list的头节点
	};
}

五. 测试自定义类型和类类型

5.1测试string类

测试代码和结果如下:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include "test.h"
using namespace std;

void test1()
{
	YZC::list<string> l1;
	string s1 = "hello world!";
	string s2 = "YZC";
	string s3 = "yzc";
	string s4 = "list模拟实现";

	l1.push_back(s1);
	l1.push_back(s2);
	l1.push_back(s3);
	l1.push_back(s4);
	for (const auto& str : l1)
		cout << str << " ";
	cout << endl;

	//拷贝构造
	YZC::list<string> l2(l1);
	for (const auto& str : l2)
		cout << str << " ";
}

int main()
{
	test1();
	return 0;
}

5.2 测试自定义类

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include "test.h"
using namespace std;

struct Date
{
	int _year = 0;
	int _month = 1;
	int _day = 1;
};

void test1()
{
	YZC::list<Date> l1;
	Date d1;
	Date d2;
	l1.push_back(d1);
	l1.push_back(d2);

	//1 测试迭代器的解引用
	auto it = l1.begin();
	while (it != l1.end())
	{
		cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
		++it;
	}
	cout << endl;

	//测试赋值运算符和->
	YZC::list<Date>l2 = l1;
	auto it1 = l2.begin();
	while (it1 != l2.end())
	{
		cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
		++it1;
	}
}

int main()
{
	test1();
	return 0;
}

测试结果如下:

六. 下篇重点:stack和queue的使用与模拟实现

相关推荐
高山我梦口香糖12 分钟前
[react]searchParams转普通对象
开发语言·前端·javascript
冷眼看人间恩怨25 分钟前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
信号处理学渣34 分钟前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客34 分钟前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin37 分钟前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
jasmine s43 分钟前
Pandas
开发语言·python
biomooc1 小时前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言
骇客野人1 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
black^sugar1 小时前
纯前端实现更新检测
开发语言·前端·javascript
404NooFound1 小时前
Python轻量级NoSQL数据库TinyDB
开发语言·python·nosql