【C++】list容器

发布于:2024-05-22 ⋅ 阅读:(81) ⋅ 点赞:(0)

目录

一.list容器介绍

二.C++中list的基本组成

三.list容器相关接口的模拟实现 

1.push_back()

2.迭代器的begin()和end() 

 3.insert()

4.erase() 

5.pop_front()

6.pop_back()

7.size() 

8.empty()

9.析构~list()和清除数据clear()

10.拷贝构造

 11.赋值运算

四.模拟实现时所遇到的问题

1.实现链表的迭代器

(1)原生指针的正确使用

 2.自定义类型要想支持运算符重载需要自己写

3.const迭代器不能被修改

4.合并const类型迭代器和普通迭代器(模板的进阶使用) 


一.list容器介绍

  1. list是序列容器,允许在序列中的任何位置执行固定O(1)时间复杂度的插入和删除操作,并在两个方向进行迭代。
  2. list容器使用双链表实现;双链表将每个元素存储在不同的位置,每个节点通过next,prev指针链接成顺序表。
  3. list与其他标准序列容器(array,vector和deque)相比,list通常可以在容器内的任何位置插入、提取和移动元素。
  4. list与其他标准序列容器(array,vector和deque)相比,list和forward_list(单链表实现)的主要缺点是他们不能通过位置直接访问元素;例如,要访问列表中的第五个元素,必须从已知位置(开始或结束)迭代到该位置,需要哦线性时间开销。
  5. 存储密度低,list要使用一些额外的内容空间(next,prev)来保持与每个元素相关联(前后续的线性)的链接信息,从而导致存储小元素类型(如char,short,int等)的列表的存储密度低。

二.C++中list的基本组成

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

	ListNode(const T& x = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
		{}
	};

	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		//构造函数(哨兵位)
		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
	private:
		Node* _head;
        size_t _size;
	};
}

三.list容器相关接口的模拟实现 

1.push_back()

void push_back(const T& x)
{
		Node* newnode = new Node(x);
		Node* tail = _head->_prev;
			
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
}

2.迭代器的begin()和end() 

typedef ListIterator<T> iterator;
iterator begin()
{
	return iterator(_head->_next);
}
iterator end()
{
	return iterator(_head);
}

 3.insert()

	    //pos位置插入
		void insert(iterator pos, const T& val)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(val);
			Node* prev = cur->_prev;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
            _size++;
		}

4.erase() 

        //删除
		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

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

5.pop_front()

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

6.pop_back()

void pop_back()
{
	erase(--end());
}

7.size() 

size_t size() const
{
	return _size;
}

8.empty()

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

9.析构~list()和清除数据clear()

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
}
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

10.拷贝构造

        void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		//拷贝构造
		list(list<T>& It)
		{
			empty_init();
			for (auto& e : It)//写&为了避免It是String等类进行拷贝构造
			{
				push_back(e);
			}
		}

 11.赋值运算

		void swap(list<T> It)
		{
			std::swap(_head, It.head);
			std::swap(_size, It.size);
		}
		//赋值运算  It1=It3
		list<T>& operator=(list<T> It)
		{
			swap(It);
			return *this;
		}

四.模拟实现时所遇到的问题

1.实现链表的迭代器

(1)原生指针的正确使用

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

这里和vecctor进行对比,vecor中指针T*是可以进行++操作的,而指针Node*无法进行++操作,原因为:链表的每一个节点的物理空间不是连续的。

其次,Node*进行解引用并不能得到我们想要的数据(比如1),得的的是一个Node结点。

我们称这样的指针T*为不符合我们需求的原生指针。

  • 解决方法 

1.封装一个自定义迭代器的类

2.重载运算符,掌控原生指针的行为,使其满足需求

  • 代码
template <class T>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T> Self;

		Node* _node;//内置类型,编译器自动调用拷贝构造(浅拷贝)

		ListIterator(Node*node)
			:_node(node)
		{}
		//解引用  *it
		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& it)
		{
			return _node != it._node;
		}
	};
  • 注意 :该迭代器类不需要写析构函数,因为该迭代器的本质是要访问我们链表中的节点。不可以在访问完成后对该链表进行释放。

 2.自定义类型要想支持运算符重载需要自己写

  • 代码 
struct A
	{
		int _a1;
		int _a2;

		A(int a1 = 0, int a2 = 0)
			:_a1(a1)
			, _a2(a2)
		{}
	};
	void test_list2()
	{
		list<A> lt;
		A aa1(1, 1);
		A aa2 = { 1, 1 };
		lt.push_back(aa1);
		lt.push_back(aa2);
		lt.push_back(A(2, 2));
		lt.push_back({ 3, 3 });
		lt.push_back({ 4, 4 });

		A* ptr = &aa1;
		(*ptr)._a1;
		ptr->_a1;

		list<A>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout <<*it << endl;  报错
			++it;
		}
		cout << endl;
	}
}

  •  解决方法

1.公有数据直接访问 

while (it != lt.end())
{
	cout <<(*it)._a1<<":"<<(*it)._a2 << endl;
	++it;
}

2.使用  ->  访问

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

	while (it != lt.end())
	{	
		cout << it->_a1 << ":" << it->_a2 << endl;
		//cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;编译器转换的样子
	}

什么意思呢?

  • 图解

3.const迭代器不能被修改

  • 代码
iterator begin()
{
		return iterator(_head->_next);
}
iterator end()
{
		return iterator(_head);
}
const_iterator begin() const
{
		return iterator(_head->_next);
}
const_iterator end() const
{
		return iterator(_head);
}

 要同时写上不同版本迭代器和const版本迭代器,如果只写了const版本的迭代器会导致非const对象也可以调用const成员函数(权限缩小),造成本身不可以被修改的结果最终可以被修改。

  • 注意 

const迭代器指的是迭代器指向的内容不能被修改。

eg. iterator begin() const

const iterator 指的是迭代器本身不能被修改不是我们所需要的迭代器

4.合并const类型迭代器和普通迭代器(模板的进阶使用) 

  • 代码 
template <class T,class Ref,class Ptr>//Ref引用,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;
		}
		//->
		Ptr 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& it)
		{
			return _node != it._node;
		}
	};

template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
	/*	typedef ListIterator<T> iterator;
		typedef ListIterator<T> const_iterator;*/

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

		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		 

		const_iterator begin() const
		{
			return iterator(_head->_next);
		}
		const_iterator end() const
		{
			return iterator(_head);
		}
     }
  • 图解 


网站公告

今日签到

点亮在社区的每一天
去签到