list类模拟实现

目录

序言

1.基本构型

2.构造函数

3.迭代器构造(1)

4.迭代器构造(2)—Ref参数

5.迭代器构造(3)—Ptr参数

6.增删查改实现 

1.insert函数

2.erase函数

3.尾插,尾删,头插,头删操作

7.迭代器构造 

8.析构和拷贝构造实现 

1.析构函数

2.拷贝构造函数

9.测试代码

10.总体代码展示 


序言

本篇文章主要是用C++模拟实现list类,关键是了解迭代器除原生指针外的另一种实现,并且进一步

巩固之前学过的类和对象的相关知识.

1.基本构型

C++的list类是一个双向带头链表,我们曾经用C语言实现过.

 简单回忆C语言时候的结构.

 每一个结点node都是一个结构体,结构体中包含三个元素,一个是前驱指针prev,一个是后驱指

针next,最后是数据域data.(由于data类型不确定,为了方便起见,我们typedef进行重命名,那需

要修改的时候,直接在typedef处修改即可.)

typedef int LTDataType;
typedef struct __list_node{
	LTDataType data;
	struct __list_node* next;
	struct __list_node* prev;
}Node;

不同于C语言,C++有以下两大区别

1.不需要对类进行重命名,即__list_node本身就是类名.

2.不需要再typedef数据域的类型,可以用模板,进行全部不同类型的统一.

同时我们还知道,C++的类,支持函数放置在内部.

因此,在实现C++的结点类(__liet_node)时,就可以直接完成构造函数,也就是我们之前实现的

BuylistNode函数.

//之前C实现的结点构造函数
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//判断申请是否失败
	if (newnode == NULL)
	{
		//申请失败,报错,并直接返回
		perror("malloc fail");
		exit(-1);
	}
	//申请成功,数值域赋为x,指针域赋为空
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

对比来看,C++的实现更为简单,而且直接明了.

采用初始化列表初始化,并且还支持数据为自定义类型.(用匿名对象作为缺省参数.)

//C++实现
template <class T>
struct __list_node {
	__list_node<T>* _next;
	__list_node<T>* _prev;
	T _data;

	//节点初始化,初始化列表初始化即可
	__list_node(const T& x = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
	{}
};

PS:这里采用Struct来实现类,即是公有开放,这样list类就可以访问里面的成员. 

 在C++类和模板最开始我们就说过,C++更关注不同对象之间的交互,是面向对象编程.

因此,我们现在关注的就是list类和node类之间的交互.

具体list类的成员是什么呢?我们同样可以参考源码进行模拟实现.

我们在list类可以找到相应的成员变量,但link_type我们并不熟悉其是什么类型.

我们可以采取Ctrl+F在vs下直接对其搜索,或者在notepad下Alt+g跳转找到link_type的相应定义.

可以看到link_type实际上就是指向头节点的指针,不过对其进行重命名而已.

当然在模拟实现的时候,为了使程序更为清晰,我们就直接用_head作为头节点的名称了,而不采

用_node.

//PS:这里只抽取了list类的成员变量代码
typedef __list_node<T> node;
private:
	node* _head;

2.构造函数

和C语言一样,初始化list链表,实际上就是对头节点进行初始化.

由于后续要实现尾插操作,会有类似node* prev = _head->_prev的操作,假如头节点的_prev成员

赋值为空指针,则程序就会直接崩溃.

C语言实现 

因此和C语言实现的时候保持一致,列表初始化,都让头节点的_prev和_next指针都指向自己.

C++实现则直接放在构造函数完成,由于初始化操作后面将会频繁使用,因此我们把它单独分装成

一个函数实现.

void empty_initialize() 
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
}
list()
{
	empty_initialize();
}

源码实现也是类似的方式. 

3.迭代器构造(1)

接下来,就到了list类最为核心的部分,迭代器的实现.

我们知道,如果只是单纯用原生指针来实现迭代器,由于链表存储空间并不是连续的,因此对指针

进行++,--等操作,得到的并不是我们想要的结果.

我们想要实现像vector,string类迭代器的操作,对指针++,就可以得到访问下一个元素的指针.

这时候就体现处类的魅力,用类来封装原生指针,构造相应函数来模拟实现++等操作.

迭代器类仅有一个成员,就是结点指针 

typedef __list_iterator<T> self;

typedef __list_node<T> node;
typedef __list_node<T>* link_type;
link_type _node;

不过不能把它就理解为一个指针,它是一个类,它有相应的成员函数

下面两行代码,代表的含义并不相同,第一行只是单纯的指针,但第二行是一个迭代器类,可以在

里面实现++等函数.

__list_node<T>* pnode = lt._head->_next;
list<int>::iterator it = lt.begin();

迭代器的构造函数实现起来也很简单,就是将相应结点的指针赋给类里面的_node指针变量即可.

typedef __list_iterator<T> self;

typedef __list_node<T> node;
typedef __list_node<T>* link_type;
link_type _node;
__list_iterator(link_type x)
	:_node(x)
{}

我们先来简单实现前置++和后置++的运算符重载.

++运算符重载,实际上想要的就是下一个结点的指针.

PS:为了书写简便,我们重命名iterator迭代器类为self

简单回顾:前置++直接返回当前对象即可,后置++实现需要存储没移动之前的对象.

//++运算符重载
self& operator++()
{
	_node = _node->_next;
	return *this;
}
self operator++(int)
{
	self tmp(*this);
	++* this;
	return tmp;
}

源码实现类似. 

类似地,--等运算符重载也是同样实现. 

//解引用返回
T& operator*()
{
	return _node->_data;
}
//--运算符重载
self& operator--()
{
	_node = _node->_prev;
	return *this;
}
self operator--(int)
{
	self tmp(*this);
	--* this;
	return tmp;
}
//!=和==运算符重载
bool operator!=(const self& x)
{
	return _node != x._node;
}
bool operator==(const self& x)
{
	return _node == x._node;
}

源码如下: 

4.迭代器构造(2)—Ref参数

接下来,我们实现const迭代器.

const迭代器不允许我们修改指向结点的data,只能访问.

一个常见的写法错误就是在list类里直接重命名.

typedef const iterator const_iterator

但这个显然是错误的写法,const修饰迭代器,反而是迭代器不能被修改,即指针不能被修改,反而

意味着指针不能随意++,--移动,这显然是错误的.

比较正确但挫的写法就是重新写一个类,但是返回数据的时候,返回的是const val.

template<class T>
struct __list_const_iterator
{
	typedef list_node<T> node;
	typedef __list_const_iterator<T> self;
	node* _node;

	__list_const_iterator(node* n)
		:_node(n)
	{}

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

	self& operator++()
	{
		_node = _node->_next;

		return *this;
	}

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

		return tmp;
	}

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

		return *this;
	}

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

		return tmp;
	}

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

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

整段代码,仅仅在*运算符重载有不同,其余和非const修饰的迭代器类完全一样,代码十分冗余

此时模板的作用就发挥出来了,模板支持传引用,指针等等.

我们在模板处加上第二个参数Ref就可以将两个迭代器类合并成一个.

template <class T,class Refr>
	struct __list_iterator
	{   
		typedef __list_iterator<T,Ref> self;

		typedef __list_node<T> node;
		typedef __list_node<T>* link_type;
		link_type _node;

		__list_iterator(link_type x)
			:_node(x)
		{}
		//*运算符重载
		Ref operator*()
		{
			return _node->_data;
		}

		//++运算符重载
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		self operator++(int)
		{
			self tmp(*this);
			++* this;
			return tmp;
		}
		//--运算符重载
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			--* this;
			return tmp;
		}
		//!=和==运算符重载
		bool operator!=(const self& x)
		{
			return _node != x._node;
		}
		bool operator==(const self& x)
		{
			return _node == x._node;
		}
	};

在list类重命名时,只需要传const T引用,或者普通引用即可.

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

5.迭代器构造(3)—Ptr参数

struct AA
{
	int _a1;
	int _a2;

	AA(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{}
};

假如list里面的data数据是一个自定义类型,那我们用迭代器去访问就没有这么方便了.

it是我们的迭代器对象,*it可以获取迭代器指向的结点中的data,它是一个自定义类型,或者说在

这个例子里,它是一个结构体,那就还需用.进行配合访问.

 (*it)._a1

但我们想要的却并不这样的效果,假如是自定义类型,此时迭代器的成员变量就应该是一个自定义

类型指针.

即上述例子来说,是AA*类型的.

结构体指针访问,应该采取->来访问对应元素.

因此我们需要对->进行运算符重载.

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

当然,实际上正确的写法应该有两个箭头,因为一个箭头得到相应的自定义类型指针,再用一个箭

头来访问自定义类型的数据,不过编译器会进行相应优化,直接用一个箭头,便可以取到对应数据.

//实际的正确写法
it.operator->()->_a1
it->->_al

由于指针,同样需要有const版本,即不能通过->来修改data.

因此,我们给迭代器添加第三个参数——Ptr.

于是完整版迭代器基本构造如下

// 1、迭代器要么就是原生指针
// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
template <class T,class Ref,class Ptr>
struct __list_iterator
{   
	typedef __list_iterator<T,Ref,Ptr> self;

	typedef __list_node<T> node;
	typedef __list_node<T>* link_type;
	link_type _node;

	__list_iterator(link_type x)
		:_node(x)
	{}
	//*运算符重载
	Ref operator*()
	{
		return _node->_data;
	}
	//->运算符重载
	Ptr operator->()
	{
		return &_node->_data;
	}
	//++运算符重载
	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	self operator++(int)
	{
		self tmp(*this);
		++* this;
		return tmp;
	}
	//--运算符重载
	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	self operator--(int)
	{
		self tmp(*this);
		--* this;
		return tmp;
	}
	//!=和==运算符重载
	bool operator!=(const self& x)
	{
		return _node != x._node;
	}
	bool operator==(const self& x)
	{
		return _node == x._node;
	}
};

同步的,list类相应位置也进行修改,const迭代器,就传const T*指针;普通迭代器,就传T*指针.

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

//迭代器实现
iterator begin()
{
	return iterator(_head->_next);
}
const_iterator begin()const
{
	return const_iterator(_head->_next);
}
iterator end()
{
	return iterator(_head);
}
const_iterator end()const
{
	return const_iterator(_head);
}

总结:

1.迭代器要么是原生指针

2.要么是自定义类型对原生指针的封装,模拟指针的行为

6.增删查改实现 

1.insert函数

优先实现insert函数,后面头插,尾插就可以直接赋用.

分为两步:1.创建新结点  2.调节指针位置即可.  

//insert实现
void insert(iterator pos, const T& x)
{
	node* cur = pos._node;
	node* prev = cur->_prev;
	node* new_node = new node(x);
	prev->_next = new_node;
	new_node->_prev = prev;
	new_node->_next = cur;
	cur->_prev = new_node;
}

  

2.erase函数

优先实现erase函数,后面头删,尾删就可以直接赋用.

分为两步:1.找到前后结点指针  2.调节指针位置即可.

PS:注意判断头结点不能删除

//erase实现,存在迭代器失效问题
iterator erase(iterator pos)
{  
	//头节点不能删除
	assert(pos != end());
	node* prev = pos._node->_prev;
	node* next = pos._node->_next;
	prev->_next = next;
	next->_prev = prev;
	delete pos._node;
	return iterator(next);
}

   

PS:链表的erase函数同样存在迭代器失效问题,因此需要返回被删除结点的下一个结点位置作为迭代器返回. 

3.尾插,尾删,头插,头删操作

//尾插实现
void push_back(const T& val)
{
	insert(end(), val);
}
//头插实现
void push_front(const T& val)
{
	insert(begin(), val);
}
//尾删实现
void pop_back()
{
	erase(--end());
}
//头删实现
void pop_front()
{
	erase(begin());
}

7.迭代器构造 

实现尾插后,我们可以进一步着手迭代器构造实现.

这时候,我们就可以看出单独将头节点初始化(链表初始化)分装为一个函数的优势在哪,我们不需

要再重复写相应代码.

//迭代器构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
	empty_initialize();

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

8.析构和拷贝构造实现 

1.析构函数

由于链表和顺序表空间不同,其不是一块连续的空间,因此不能直接delete掉这块空间.

一种常见的作法,是像我们C语言一样,边遍历链表,边释放相应的结点.

另一种就是C++写法,用迭代器实现释放全部结点的函数.

析构函数,就是释放所有结点+头结点.

//析构函数和clear
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		erase(it++);
	}
}

2.拷贝构造函数

拷贝构造和赋值运算符重载我们这里就统一都采取现代写法,即用Swap函数进行实现.

//拷贝构造
void swap(list<T>& tmp)
{
	std::swap(_head,tmp._head);
}
list(const list<T>& lt)
{
	empty_initialize();
	list<T> tmp(lt.begin(), lt.end());
	swap(tmp);
}
list<T>& operator=(list<T> lt)
{
	swap(lt);
	return *this;
}

由于进了函数,构造的tmp,lt都是临时对象,出了函数作用域会被销毁,所以可以大胆放心swap,

夺取成果.

但不要传引用作为参数,否则就真的成为两个对象互换名字而已. 

9.测试代码

//迭代器检验
void test_list1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

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

}

//增删查改函数检验
void test_list3()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	auto pos = lt.begin();
	++pos;
	lt.insert(pos, 20);

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.push_back(100);
	lt.push_front(1000);

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.pop_back();
	lt.pop_front();

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

 

//拷贝构造和赋值重载函数检验
void test_list4()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int> lt2(lt);

	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int> lt3;
	lt3.push_back(10);
	lt3.push_back(20);
	lt3.push_back(30);

	for (auto e : lt3)
	{
		cout << e << " ";
	}
	cout << endl;

	lt2 = lt3;

	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : lt3)
	{
		cout << e << " ";
	}
	cout << endl;
}

 

10.总体代码展示 

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace zzq {
    //结点类
	template <class T>
	struct __list_node {
		__list_node<T>* _next;
		__list_node<T>* _prev;
		T _data;

		//节点初始化,初始化列表初始化即可
		__list_node(const T& x = T())
			:_next(nullptr)
			,_prev(nullptr)
			,_data(x)
		{}
	};

	// 1、迭代器要么就是原生指针
	// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
    //迭代器类
	template <class T,class Ref,class Ptr>
	struct __list_iterator
	{   
		typedef __list_iterator<T,Ref,Ptr> self;

		//typedef __list_node<T> node;
		typedef __list_node<T>* link_type;
		link_type _node;

		__list_iterator(link_type x)
			:_node(x)
		{}
		//*运算符重载
		Ref operator*()
		{
			return _node->_data;
		}
		//->运算符重载
		Ptr operator->()
		{
			return &_node->_data;
		}
		//++运算符重载
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		self operator++(int)
		{
			self tmp(*this);
			++* this;
			return tmp;
		}
		//--运算符重载
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			--* this;
			return tmp;
		}
		//!=和==运算符重载
		bool operator!=(const self& x)
		{
			return _node != x._node;
		}
		bool operator==(const self& x)
		{
			return _node == x._node;
		}
	};
    //list类
	template <class T>
	class list {
		typedef __list_node<T> node;

	public:
		typedef __list_iterator<T,T&,T*> iterator;
		typedef __list_iterator<T,const T&,const T*> const_iterator;
		void empty_initialize() {
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		list()
		{
			empty_initialize();
		}

		//迭代器构造
		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_initialize();

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

		//拷贝构造
		void swap(list<T>& tmp)
		{
			std::swap(_head,tmp._head);
		}
		list(const list<T>& lt)
		{
			empty_initialize();
			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}
		//析构函数和clear
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
			}
		}
		//迭代器实现
		iterator begin()
		{
			return iterator(_head->_next);
		}
		const_iterator begin()const
		{
			return const_iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}
		//push_back实现
		void push_back(const T& val)
		{
			insert(end(), val);
		}
		//push_front实现
		void push_front(const T& val)
		{
			insert(begin(), val);
		}
		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}
		//insert实现
		void insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;
			node* new_node = new node(x);
			prev->_next = new_node;
			new_node->_prev = prev;
			new_node->_next = cur;
			cur->_prev = new_node;
		}
		//erase实现,存在迭代器失效问题
		iterator erase(iterator pos)
		{  
			//头节点不能删除
			assert(pos != end());
			node* prev = pos._node->_prev;
			node* next = pos._node->_next;
			prev->_next = next;
			next->_prev = prev;
			delete pos._node;
			return iterator(next);
		}
	private:
		node* _head;
	};

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值