C++修炼之路之list模拟实现--C++中的双向循环链表

目录

引言

一:STL源代码中关于list的成员变量的介绍

二:模拟实现list

1.基本结构

[2.普通迭代器 +const迭代器的结合](#2.普通迭代器 +const迭代器的结合)

[3.构造+拷贝构造+析构+赋值重载 +清空](#3.构造+拷贝构造+析构+赋值重载 +清空)

4.insert+erase+头尾插入删除

5.打印不同数据类型的数据《使用模板加容器来完成》

三:全部代码加测试用例链接

接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧

引言

在前面的数据结构中已经实现了c版本的list-双向循环链表,但在c++中注重的是分装,对于操作进行封装处理,对于我们使用便是方便了不少,但模拟实现的话还是有许多要注意的细节点,尤其是list的迭代器较复杂,需要认真理解

一:STL源代码中关于list的成员变量的介绍

在源代码中使用一个头结点的指针来 模拟实现的,但在文档中只介绍list是一个双向链表的容器,并没有说明是否带哨兵位了,之时就得观察初始化函数和构造函数等来观察

所以从这里就可以看出list的结构就是一个带头循环双向链表,接下来我们就开始模拟实现

二:模拟实现list

1.基本结构

template<class T>//泛型编程
struct list_node
{
	T _data;
	list_node<T>* _prev;
	list_node<T>* _next;

	list_node(const T &x=T())//缺省值为匿名对象
		:_data(x)
		,_prev(nullptr)
		,_next(nullptr)
	{}
};

template<class T>
class list
{
	typedef list_node<T> node;
public:

private:
	node* head;
	size_t size;//方便size()接口,省去便利的开销
};

2.普通迭代器 +const迭代器的结合

对于用迭代器来遍历list的数据,就不能像vector和string那样,直接使用,因为

list<int>::iterator it=lt1.begin();

while(it!=lt1.end())

{

cout<<*it<<" ";

++it;

}

cout<<endl;

对于这里面的*it和++it和it!=lt1.end()这些操作是不能直接支持的,因为*it是要取data的数据,++it就是要使_node=_node->next等,这些是和vector,string是不一样的,所以得自己手动支持,运用运算符重载+函数封装,我们先来观察源代码中的实现

我们先来实现普通迭代器

template<class T>
struct _list_iterator
{
	typedef list_node<T> Node;
	typedef _list_iterator<T> self;
	Node* node;

	_list_iterator(Node*node)
		:_node(node)
	{}

	self& operator++()
	{
		_node = _node->next;
		return *this;
	}
	self& operator--()
	{
		_node = _node->prev;
		return *this;
	}
	self operator++(int)
	{
		self tmp(*this);
		_node = _node->next;
		return tmp;
	}
	self operator--(int)
	{
		self tmp(*this);
		_node = _node->prev;
		return tmp;
	}
	T& operator*()
	{
		return _node->data;
	}
	T* operator->()
	{
		return &_node->data;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}

};
template<class T>
class list
{
	typedef list_node<T> node;
public:
	typedef _list_iterator<T> iterator;
	iterator begin()
	{
		//return iterator(_head->_next);
		return _head->_next;
	}
	iterator end()
	{
		return _head;
	}

对于这个结构可以这样来理解

我们知道const对象使用的是const迭代器那么在iterator之前加const的话,这样const iterator指的是迭代器本身不能修改,但在遍历过程迭代器本身是要改变的,如++it等,所以不能这样写

应该为const_iterator这是一个重新定义的一个类型,要做到迭代器本身可以改变,但迭代器指向的内容不能改变

在原来普通迭代器的基础上重载出一份const迭代器的版本如

但这样的话对于普通迭代器和const迭代器两份代码是由许多相同的代码的,比较冗余,所以在源代码中提出了一份更好的解决方案来解决这个问题

在原来基础上多添加两个参数,这样对于同一个类模板,实例化参数不同就是不同的类型

对于上面的代码就要优化为更完善的代码,展示主要优化的代码

template<class T,class Ref,class Ptr>
struct _list_iterator
{
	typedef list_node<T> Node;
	typedef _list_iterator<T,Ref,Ptr> self;
	Node* node;
	
	Ref operator*()
	{
		return _node->data;
	}
	Ptr operator->()
	{
		return &_node->data;
	}
	
	typedef _list_iterator<T,T&,T*> iterator;
	typedef _list_iterator<T, const T&, const T*> const_iterator;
	//typedef __list_const_iterator<T> const_iterator;
	iterator begin()
	{
		//return iterator(_head->_next);
		return _head->_next;
	}
	iterator end()
	{
		return _head;
	}
	const_iterator begin()const
	{
		//return iterator(_head->_next);
		return const_iterator(_head->_next);
	}
	const_iterator end()const
	{
		return const_iterator(_head);
	}

对于->编译器是做处理的,将两个->优化为一个详细看代码及测试 用例3中

3.构造+拷贝构造+析构+赋值重载 +清空

构造时要使用带头循环双向循环链表的初始化操作,观看https://blog.csdn.net/Miwll/article/details/136593441?spm=1001.2014.3001.5502

void empty_init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}
list()
{
	empty_init();
}
list(const list<T>& t)//拷贝构造实现为深拷贝
{
	empty_init();
	for (auto e : t)
	{
		push_back(e);
	}
}
/*list<int>& operator=(const list<int>& lt)
{
	if (this != &lt)
	{
		clear();
		for (auto e : lt)
		{
			push_back(e);
		}
	}
	return *this;
}*/
void swap(list<T>& t)
{
	std::swap(_head,t._head);
	std::swap(_size, t._size);
}
list<int>& operator=(const list<int>& lt)
{
	swap(lt);
	return *this;
}
~list()
{
	clear();
	delete _head;
	_head =nullptr;
}
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
}

4.insert+erase+头尾插入删除

void push_back(const T& x)
{
	insert(end(), x);
}

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

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

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

	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	++_size;
	return iterator(newnode);
}
iterator erase(iterator pos)
{
	Node* cur = pos._node;
	Node* next = cur->_next;
	Node* prev = cur->_prev;

	delete cur;
	prev->_next = next;
	next->_prev = prev;
	--_size;
	return iterator(next);
}
size_t size()
{
	return _size;
}

5.打印不同数据类型的数据《使用模板加容器来完成》

使用模板来打印自定义类型数据,注意这里加的typename

复制代码
list<T>未实例化的类模板,编译器不能去他里面去找
编译器就无法list<T>::const_iterator是内嵌类型,还是静态成员变量
前面加一个typename就是告诉编译器,这里是一个类型,等list<T>实例化
再去类里面去取

三:全部代码加测试用例链接

https://gitee.com/lin-ciyu/cplusplus/tree/master/my_list/my_list

相关推荐
Zfox_25 分钟前
【Linux】进程信号全攻略(二)
linux·运维·c语言·c++
起名字真南43 分钟前
【OJ题解】C++实现字符串大数相乘:无BigInteger库的字符串乘积解决方案
开发语言·c++·leetcode
少年负剑去44 分钟前
第十五届蓝桥杯C/C++B组题解——数字接龙
c语言·c++·蓝桥杯
cleveryuoyuo44 分钟前
AVL树的旋转
c++
神仙别闹1 小时前
基于MFC实现的赛车游戏
c++·游戏·mfc
小c君tt1 小时前
MFC中 error C2440错误分析及解决方法
c++·mfc
木向2 小时前
leetcode92:反转链表||
数据结构·c++·算法·leetcode·链表
阿阿越2 小时前
算法每日练 -- 双指针篇(持续更新中)
数据结构·c++·算法
hunandede2 小时前
FFmpeg存放压缩后的音视频数据的结构体:AVPacket简介,结构体,函数
c++
hunandede3 小时前
FFmpeg 4.3 音视频-多路H265监控录放C++开发十三:将AVFrame转换成AVPacket。视频编码,AVPacket 重要函数,结构体成员学习
c++·ffmpeg·音视频