【C++笔记】C++ list类模拟实现

一、初始化和各种构造

C++的list类使用的模型是带头双向循环链表:
在这里插入图片描述
所以今天我们也是基于这个模型来实现一个list。

1.1、准备工作

在定义链表类之前,我们先要定义链表的节点类:

// list节点类
template <class T>
struct ListNode {
	// 构造函数
	ListNode(const T& val = T()) {
		_val = val;
		_pre = nullptr;
		_next = nullptr;
	}
	T _val;
	ListNode<T>* _pre; // 指向前一个节点
	ListNode<T>* _next; // 指向后一个节点
};

然后就是list类了:

template <class T>
class list {
	typedef ListNode<T> Node;
public :
private :
	Node* _head;
	size_t _size;
};

因为我们所写的是带头链表,所以在list类里就只需要顶一个头节点和size即可。

1.2、各种构造和析构

有了链表类,我们就要先来实现各种构造和析构了。
空初始化
但在开始实现构造之前,我们必须先得实现一个“空初始化”,因为我们设计的链表是带头结点的,并且这个头结点是不算入有效数据的。
所以我们要先将这个头结点申请出来,这也就是空初始化要做的事情:

// 空初始化
void emptyInit() {
	// 开辟一个头节点
	Node* newHead = new Node();
	_head = newHead;
	_head->_next = _head;
	_head->_pre = _head;
	_size = 0;
}

无参构造
无参构造的作用就是只创建一个链表对象,其他什么都不做,所以我们直接调用空初始化即可:

// 无参构造函数
list() {
	emptyInit();
}

以n个相同值构造
这个其实我们可以直接复用后面写的尾插,直接一直追加即可。
在正式插入之前,我们还是先要处理头节点,即调用空初始化:

// 以n个相同值构造
list(int n, const T& val = T()) {
	// 先空初始化
	emptyInit();
	//  插入
	for (int i = 0; i < n; i++) {
		push_back(val);
	}
}

以一段迭代器区间初构造
这个其实也和其他容器是一样的:

// 以一段迭代器区间初始化
template <class Iterator>
list(Iterator first, Iterator last) {
	// 先空初始化
	emptyInit();
	while (first != last) {
		push_back(*first);
		first++;
	}
}

拷贝构造
对于其他容器而言,链表的拷贝构造我想是最简单的了,因为它的空间本身就不连续,也就不必申请新的一段连续的空间再拷贝数据了。
其实我们直接将两个链表的哨兵位节点交换即可。
所以我们可以先实现一个交换函数:

// 交换
void Swap(list<T>& lt) {
	swap(_head, lt._head);
	swap(_size, lt._size);
}

然后我们在构造函数中创建一个临时对象,再将这个对象与我们要构造的对象交换即可:

// 拷贝构造
list(const list<T>& lt) {
	// 先空初始化
	emptyInit();

	// 创建一个临时对象
	list<T> temp(lt.begin(), lt.end());
	// 交换即可
	Swap(temp);
}

析构函数
析构函数,我这里并不想使用以前的方法依次删除节点,我这里打算复用其他的函数。
我们先得要实现一个清数据的函数——只清理有效数据,不清理哨兵头节点:

// 清除数据
void clear() {
	iterator it = begin();
	while (it != end()) {
		it = erase(it);
	}
}

这里用到的迭代器后面后讲到。

然后在析构函数中,我们只需要调用一次clear然后再将哨兵头节点释放掉即可:

// 析构函数
~list() {
	clear();
	delete _head;
}

二、插入和删除

2.1、插入

尾插
尾插其实就和我们以前写的带头双向循环链表一模一样了:

	// 尾插
void push_back(const T& val) {
	Node* tail = _head->_pre;
	// 申请一个新节点
	Node* newNode = new Node(val);
	tail->_next = newNode;
	newNode->_pre = tail;
	newNode->_next = _head;
	_head->_pre = newNode;
	_size++;
}

头插

	// 头插
	void push_front(const T& val) {
		// 申请新的头节点
		Node* newHead = new Node(val);
		// 原来的头节点
		Node* head = _head->_next;
		_head->_next = newHead;
		newHead->_pre = _head;
		newHead->_next = head;
		head->_pre = newHead;
		_size++;
	}

随机插入
在pos位置之前插入一个新节点,并返回新节点的位置:

iterator insert(iterator pos, const T& val) {
	// 申请一个新节点
	Node* newNode = new Node(val);
	Node* Pre = pos._node->_pre;
	Node* Next = pos._node;
	Pre->_next = newNode;
	newNode->_pre = Pre;
	newNode->_next = Next;
	Next->_pre = newNode;
	_size++;
	return newNode;
}

2.2、删除

尾删

// 尾删
void pop_back() {
	assert(!empty());
	Node* tail = _head->_pre;
	Node* Pre = tail->_pre;
	// 释放尾节点
	delete tail;
	Pre->_next = _head;
	_head->_pre = Pre;
	_size--;
}

头删

// 头删
void pop_front() {
	assert(!empty());
	// 旧的头节点
	Node* head = _head->_next;
	// 新的头节点
	Node* newHead = head->_next;
	// 释放旧的头节点
	delete head;
	_head->_next = newHead;
	newHead->_pre = _head;
	_size--;
}

随机删除
删除pos位置的节点,并返回被删除节点的下一个节点的位置:

iterator erase(iterator pos) {
	assert(!empty());
	Node* cur = pos._node;
	Node* Pre = cur->_pre;
	Node* Next = cur->_next;
	// 释放原来的节点
	delete cur;
	Pre->_next = Next;
	Next->_pre = Pre;
	_size--;
	return Next;
}

三、迭代器

因为链表的哨兵头节点是私有的,并且链表也不支持随机访问(不能实现方括号重载),所以我们遍历链表的方式就只剩一种——迭代器。

3.1、正向迭代器

因为链表的空间不是连续的,所以我们就不能直接用原生指针来模拟了。我们需要对原生指针再进行封装。
并且迭代器的++和–也要通过运算符重载的方式达到:

// list迭代器类
template <class T, class ref, class ptr>
struct ListIterator {
	typedef ListNode<T> Node;
	typedef ListIterator<T, ref, ptr> iterator;
public :
	// 构造函数
	ListIterator(Node* node = nullptr) {
		_node = node;
	}

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

	// 星号解引用运算符重载
	ref operator*() {
		return _node->_val;
	}

	// 箭头解引用运算符重载
	ptr operator->() {
		return &_node->_val;
	}
	// 前置++运算符重载
	iterator& operator++() {
		_node = _node->_next;
		return *this;
	}

	// 后置++运算符重载
	iterator& operator++(int) {
		iterator* temp = new iterator(this->_node);
		++(*this);
		return *temp;
	}

	

	// 前置--运算符重载
	iterator& operator--() {
		_node = _node->_pre;
		return *this;
	}

	// 后置--运算符重载
	iterator& operator--(int) {
		iterator* temp = new iterator(this->_node);
		--(*this);
		return *temp;
	}

	// 等于运算符重载
	bool operator==(const iterator& it) {
		return _node == it._node;
	}

	// 不等于运算符重载
	bool operator!=(const iterator& it) {
		return !((*this) == it);
	}
public :
	Node* _node;
};

3.2、反向迭代器

实现反向迭代器我们其实只需要适配正向迭代器即可,因为反向迭代器的逻辑几乎和正向迭代器是一样的,只是在对反向迭代器进行++和–的时候需要和正向迭代器相反:

// list反向迭代器类
template <class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{
	Iterator _it;
	typedef Reverse_iterator<Itertor, Ref, Ptr> Self;

	Reverse_iterator(Iterator it)
		: _it(it)
	{}

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

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

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

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

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

3.3、提供迭代器位置

// 正向const迭代器起始位置
const_iterator begin() const
{
	return const_iterator(_head->_next);
}
// 正向const迭代器结束位置
const_iterator end() const
{
	return const_iterator(_head);
}
 // 正向迭代器起始位置
iterator begin()
{
	return iterator(_head->_next);
}
 // 正向迭代器结束位置
iterator end()
{
	return iterator(_head);
}
 // 反向const迭代器起始位置
const_reverse_iterator rbegin() const
{
	return const_reverse_iterator(end());
}
 // 反向const迭代器结束位置
const_reverse_iterator rend() const
{
	return const_reverse_iterator(begin());
}
 // 反向迭代器起始位置
reverse_iterator rbegin()
{
	return reverse_iterator(end());
}
 // 反向迭代器结束位置
reverse_iterator rend()
{
	return reverse_iterator(begin());
}

四、其他一些接口

4.1、链表的长度和判空

// 返回长度
	size_t size() {
		return _size;
	}
	// 判断是否为空
	bool empty() {
		return _size == 0;
	}

4.2、返回链表的头尾结点

// 返回链表的头一个节点(第一个有效节点的值)
	T& front() {
		assert(!empty());
		return _head->_next->_val;
	}

	// const版本的头节点
	const T& front() const {
		assert(!empty());
		return _head->_next->_val;
	}

	// 返回链表的尾节点(最后一个有效节点的值)
	T& back() {
		assert(!empty());
		return _head->_pre->_val;
	}

	// const版本尾节点
	const T& back() const {
		assert(!empty());
		return _head->_pre->_val;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林先生-1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值