C++list类的常见函数以及其模拟实现


前言

C++list类的常见函数及其模拟实现

list的底层是一个双向循环链表。每个元素存储在互不相关的独立节点中,在节点中通过指针来访问前一个元素和后一个元素。与其他序列式容器相比(array,vector,deque),list可以在任意位置以O(1)的效率进行插入与删除,但其本身不可随机访问(因此不需要去重载[ ]符号)。
接下来我们将依次介绍list的模拟实现。

一、list内部成员有什么?

首先定义了一个命名空间jjw
接着在jjw内首先定义了一个双向链表的数据结构,并完成它的默认构造。
随后定义了一个Node* 类型的_head指针,指向这个链表的头结点。并且定义了一个_size表示链表结点的数量。
最后定义了一个Create_node函数,这个函数来为_head开辟一份空间,并让其_next,_pre指向其本身完成初始化。

namespace jjw
{
template<class T>
struct ListNode{
ListNode(const T& val=T())
{
	_pre = nullptr;
	_next = nullptr;
	_val = val;
}
ListNode<T>* _pre;
ListNode<T>* _next;
T _val;
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
  // 一系列函数
private:
void Create_node()
{
	_head = new Node;
	_head->_next = _head;
	_head->_pre = _head;
	_size = 0;
}
Node* _head;
size_t _size;
};
}

二、构造函数以及析构函数

1.默认构造

直接调用Create_node函数即可。

list()
{
	Create_node();
}

2.传参构造

传入两个参数,int n表示该链表的结点数目,val表示每个结点的值。
首先调用Create_node完成_head的初始化。
然后创建一个变量p用来表示每次链尾的元素。
最后通过for循坏依次插入元素,注意插入后需要将_size++。(后面我们可以直接调用push_back完成)

list(int n, const T& val = T())
{
	Create_node();
	Node* p = _head;
	for (int i = 0; i < n; i++)
	{
		Node* temp = new Node;
		temp->_val = val;
		temp->_next = p->_next;
		temp->_pre = p;
		p->_next = temp;
		_head->_pre = temp;
		p = temp;
		_size++;
		//push_back(val);
	}
}

3.迭代器构造

首先定义了一个迭代器模板,不管什么类型的迭代器都可以进行初始化。
然后就和上诉基本一样,循环遍历区间,依次在链表末尾插入元素。

template<class Iterator>
list(Iterator start,Iterator finish)
{
	Create_node();
	while (start != finish)
	{
		push_back(*start);
		++start;
	}
}

4.深拷贝以及运算符=的重载

1.深拷贝

和上述实现差不多,依次遍历lt,插入到链表的末端。

list(const list<T>& lt)
{
	Create_node();
	for (auto e : lt)
	{
		push_back(e);
	}
}

2.=的重载

首先通过lt创建了一个临时变量temp,然后将temp变量和this交换。
最后返回
this。(注意这里函数返回值类型不可以加引用)

list operator=(list<T>& lt)
{
	list temp(lt);
	swap(lt);
	return *this;
}

5.析构函数

首先调用clear函数清除每个结点(后面会介绍clear函数)。
然后释放_head 头结点指针完成析构。

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

三、迭代器的模拟实现

1.正向迭代器

首先定义了三个模板 T表示链表中元素的类型,Ref表示T&,Ptr表示T*(重载->运算符需要)。
然后在List_Iterator内定义了Node* _node,并且定义了其默认构造。
接着对迭代器一系列的操作进行了重载,来实现正向迭代器。
在List类中,begin返回第一个元素的位置,即_head->_next。
end返回最后一个元素的下一个位置,即_head。
list不同与string和vector,它需要通过一个类来封装实现迭代器。

namespace jjw
{
template<class T,class Ref,class Ptr>
struct List_Iterator{
	typedef ListNode<T> Node;
	typedef List_Iterator<T, Ref, Ptr> self;
	typedef Ref Ref; //反向迭代器要用到
	typedef Ptr Ptr; //反向迭代器要用到
	List_Iterator(Node* node=nullptr)
	{
		_node = node;
	}

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

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

	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	self operator++(int)
	{
		self temp(_node);
		_node = _node->_next;
		return temp;
	}

	self& operator--()
	{
		_node = _node->_pre;
		return *this;
	}

	self operator--(int)
	{
		self temp(_node);
		_node = _node->_pre;
		return temp;
	}

	Ref operator*()
	{
		return _node->_val;
	}

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

	Node* _node;
};
template<class T>
class list{
public:
	typedef ListNode<T> Node;
	typedef List_Iterator<T, T&, T*> iterator;
	typedef List_Iterator<T, const T&,const T*> const_iterator;
	iterator begin()
	{
		return iterator(_head->_next);
	}
	
	iterator end()
	{
		return _head;
	}
	
	const_iterator begin()const
	{
		return const_iterator(_head->_next);
	}
	
	const_iterator end()const
	{
		return const_iterator(_head);
    }
};
}

2.反向迭代器

反向迭代器通过封装正向迭代器来实现。
rbegin返回end()的位置,rend返回begin()的位置。
++去- -,- -去++。
注意这里的解引用操作,先 - - 再解引用。

template<class Iterator>
struct Reverse_Iterator
{
typedef Reverse_Iterator<Iterator> self;
typedef typename Iterator::Ref Ref;//typename表示取出的Ref和Ptr是Iterator中的一个类型
typedef typename Iterator::Ptr Ptr;//而不是一个静态成员变量。

Reverse_Iterator(Iterator it)
{
	_it = it;
}

Ref operator*() //返回前一个位置的元素值
{
	Iterator temp(_it);
	--temp;  
	return *temp;
}

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

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

self operator++(int)
{
	self temp(_it);
	--_it;
	return temp;
}

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

self operator--(int)
{
	self temp(_it);
	++_it;
	return temp;
}

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

bool operator==(const self l)
{
	return _it == l._it;
}
Iterator _it;
};

template<class T>
class list{
public:
typedef ListNode<T> Node;
typedef Reverse_Iterator<iterator> reverse_iterator;
typedef Reverse_Iterator<const_iterator> const_reverse_iterator;
reverse_iterator rbegin()
{
	return reverse_iterator(_head->_pre);
}

reverse_iterator rend()
{
	return reverse_iterator(_head->_next);
}

const_reverse_iterator rbegin()const
{
	return const_reverse_iterator(_head->_pre);
}

const_reverse_iterator rend()const
{
	return const_reverse_iterator(_head->_next);
}
};

四、常见函数及其实现

1.insert函数

在pos位置前插入一个值为val的结点。
最后更新_size
注意插入完返回插入后结点的迭代器,防止出现迭代器失效问题。

iterator insert(iterator pos,const T& val)
{
	Node* temp = new Node;
	temp->_val = val;
	temp->_pre = pos._node->_pre;
	pos._node->_pre->_next = temp;
	temp->_next = pos._node;
	pos._node->_pre = temp;
	_size++;
	return iterator(temp);
}

2.erase函数

删除pos当前位置的结点,并释放它的空间。
最后更新_size
注意:返回pos的下一个结点的迭代器,否则会出现迭代器失效。

iterator erase(iterator pos)
{
	Node* p = pos._node->_next;
	p->_pre = pos._node->_pre;
	pos._node->_pre->_next = p;
	delete pos._node;
	pos = nullptr;
	_size--;
	return iterator(p);
}

3.clear函数

依次遍历删除每一个结点,并释放它们的空间。
最后重置一下_head的_next和_pre指针,防止出错。

void clear()
{
	Node* p = _head->_next;
	while (p != _head)
	{
		_head->_next = p->_next;
		p->_next->_pre = _head;
		delete p;
		p = _head->_next;
		_size--;
	}
	_head->_next = _head;
	_head->_pre = _head;
}

4.push_back和push_front函数

直接调用insert函数即可。

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

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

5.pop_back和pop_front 函数

直接调用erase函数即可。
注意pop_back应传入最后一个元素的迭代器,即_head->_pre。

void pop_back()
{
	erase(end()._node->_pre);
}

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

6.resize函数

1.若newsize小于_size,则直接pop_back
2. 若newsize大于_size,则直接push_back
直到newsize==size

void resize(size_t newsize, const T& val=T())
{
	if (newsize < _size)
	{
		while (newsize != _size)
			pop_back();
	}
	else
	{
		while (newsize != _size)
			push_back(val);
	}
}

7.front和back函数

因为链表不支持随机访问,但其访问首元素和尾元素的效率很高。
front返回首元素的值,back返回尾元素的值。

T& front()
{
	return _head->_next->_val;
}

const T& front()const
{
	return _head->_next->_val;
}

T& back()
{
	return _head->_prev->_val;
}

const T& back()const
{
	return _head->_prev->_val;
}

8.swap,empty和size函数

void swap(list<T>& lt)
{
	std::swap(_head, lt._head);
	std::swap(_size, lt._size);
}

size_t size()
{
	return _size;
}

bool empty()
{
	return _size == 0;
}

总结

以上就是list的常用函数以及模拟实现(本人小白一枚,若有错误还望各位指正)
完整的代码存放在gitee上:https://gitee.com/jj-wen/c-beginner/blob/master/06.21%EF%BC%88list%EF%BC%89/06.21%EF%BC%88list%EF%BC%89/list.h
学习任重而道远,希望自己可以坚持下去。

  • 34
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值