C++ list容器 与 模拟实现

168 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


特性

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代
  2. list的底层是==双向链表==结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. listforward_list非常相似,最主要的不同在于:forward_list是单链表,只能向后迭代,其更简单高效。
  4. 与其他的序列式容器相比(arrayvectordeque),list通常在任意位置进行插入、移除元素的执行效率更高。
  5. 与其他序列式容器相比,listforward_list最大的缺陷是不支持任意位置的随机访问。 比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

迭代器

  1. beginend为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
  3. cbegincend为const的正向迭代器,与beginend不同的是:该迭代器指向节点中的元素值不能修改
  4. crbegincrend为const的反向迭代器,与rbeginrend不同的是:该迭代器指向节点中的元素值不能修改

失效问题

处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。

因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。


模拟实现

template<class T>
struct ListNode{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;

	//构造函数
	ListNode(const T& x = T())	//T生成一个匿名对象进行全缺省
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};

		/*	迭代器都是浅拷贝		*/
// typedef __ListIterator<T, T&, T*> iterator;
// typedef __ListIterator<T, const T&, const T*> const_iterator;
template<class T, class Ref, class Ptr>
struct __ListIterator{
	typedef ListNode<T> Node;
	typedef __ListIterator<T, Ref, Ptr> Self;
	Node* _node;

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

	// *it:返回的是结点中的数据
	Ref operator*(){
		return _node->_data;
	}

	// it->:返回的是结点的指针,地址
	//T* operator->()
	Ptr operator->(){	//操作数是自定义类型才会用->,因为内置类型int不会使用这个操作符
		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;
	}

	// it1 != it2
	bool operator!=(const Self& it){
		return _node != it._node;
	}
	
	bool operator==(const Self& it){
		return _node == it._node;
	}
};

template<class T>
class List{
	typedef ListNode<T> Node;
public:
	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;
	}
	
	//copy(l)	至少要拷贝头结点
	List(const List<T>& l){	//深拷贝
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		
		for(auto e : l){
			PushBack(e);
		}
	}
	
	List<T>& operator=(List<T> l){
		swap(_head,l._head);
		return *this;
	}
	
	~List(){
		Clear();
		
		//清除掉所有资源
		delete _head;
		_head = nullptr;
	}

	void Clear(){
		auto it = begin();
		while(it != end()){
			it = Erase(it++);	//Erase返回下一个it的迭代器
			//因为Erase之后此空间还给系统,就成随机值了
		}
	}

	iterator begin(){
		return _head->_next;
	}
	
	iterator end(){
		return iterator(_head);
	}
	
	const_iterator begin() const{
		return const_iterator(_head->_next);
	}
	
	const_iterator end() const{
		return _head;	//单参数的隐式类型转换
	}
	

	void PushBack(const T& x){
		Insert(end(),x);
		/*
		Node* tail = _head->_prev;
		Node* newnode = new Node(x);

		tail->_next = newnode;
		newnode->_prev = tail;

		newnode->_next = _head;
		_head->_prev = newnode;
		*/
	}
	
	void PushFront(const T& x){
		Insert(begin(),x);
	}
	
	void PopBack(const T& x){
		Erase(--end());	//不可以写为(end()-1)
	}
	
	void PopFront(const T& x){
		Erase(begin());
	}
	
	void Insert(iterator pos,const T& x){
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* newNode = new Node(x);
		
		prev->_next = newnode;
		newnode->_prev = prev;
		
		newnode->_next = cur;
		cur->_prev = newnode;
	}
	
	iterator Erase(iterator pos){
		assert(pos._node != _head);	//不能删除头结点
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;
		
		prev->_next = next;
		next->_prev = prev;
		delete cur;
		
		return next;
	}
	
	bool Empty(){
		return _head->next == _head;
	}
	
	size_t Size(){
		size_t size = 0;
		for(auto e : *this){	//:之后必须给对象
			++size;
		}
		return size;
	}
	
private:
	Node* _head;
};

测试代码

struct AA{
	int _a1;
	int _a2;
};

void TestList1(){
	List<int> l;
	l.PushBack(1);
	l.PushBack(2);
	l.PushBack(3);

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

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

	List<AA> laa;
	AA aa1 = { 1, 2 };
	laa.PushBack(aa1);
	laa.PushBack({3, 4});
	List<AA>::iterator ita = laa.begin();
	while (ita != laa.end()){
		//cout << (*ita)._a1 << (*ita)._a2 << endl;
		cout << ita->_a1 << ita->_a2 << endl;

		++ita;
	}

	AA* pa = new AA;
	pa->_a1 = 10;
	cout << (*pa)._a1 << endl;

	int* pi = new int;
	*pi;
}

list与vector的对比

vectorlist
底层结构动态顺序表,一段连续空间头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,(增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低)任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器==重新赋值==,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问