C++初阶 — list的使用及mini版的模拟实现

目录

一、 list的介绍及使用

  1. list的介绍

  2. list的使用 

   2.1 list的构造

   2.2 list iterator的使用

   2.3 list capacity

   2.4 list element access

   2.5 list modifiers

   2.6 list的迭代器失效

二、 list的模拟实现

 1. list的结构

 2. insert和push

 3. erase和pop

 4. 迭代器类

  4.1 * , -> , != , ==

  4.2 ++,--

 5. begin 和end

 6. 构造,拷贝构造,赋值重载, 析构

 7. size和empty,swap,clear

 8. front和back

三、 list与vector的对比


一、 list的介绍及使用

  1. list的介绍

1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。

3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高 效。

4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。

5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)

  2. list的使用 

        list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。

   2.1 list的构造

   2.2 list iterator的使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

 【注意】 :

1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

   2.3 list capacity

    2.4 list element access

   2.5 list modifiers

    2.6 list的迭代器失效

        前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

二、 list的模拟实现

 1. list的结构

        list的结构是带头双向循环链表,这里的链表结构虽然复杂,但是操作起来却简单不少。

//List的节点类
	template<class T>
	struct ListNode
	{
		ListNode(const T& val = T())
			:_pPrve(nullptr)
			,_pNext(nullptr)
			,_val(val)
		{}
		ListNode<T>* _pPrve;
		ListNode<T>* _pNext;
		T _val;
	};

//list类
	template<class T>
	class list
	{
		typedef ListNode<T> Node;

    private:
//初始化哨兵位节点
		void CreateHead()
		{
			_pHead = new Node(T());
			_pHead->_pPrve = _pHead;
			_pHead->_pNext = _pHead;
			_size = 0;
		}
		Node*_pHead;
		size_t _size;
		};

 2. insert和push

        这里的头插尾插都是复用的insert,这里的insert表示在pos位置之前插入,知道当前位置,再通过当前位置的前置指针找到前一个节点的位置,把新节点插入到他们两个中间就可以了。

        如果没有实现迭代器,那么头插就是在哨兵位之后插入节点,尾插就是在哨兵位之前插入,因为链表是个循环,我们可以很容易找到头和尾。

            // 在pos位置前插入值为val的节点
            iterator insert(iterator pos, const T& val)
			{
				Node* newnode = new Node(val);
				Node* cur = pos._pNode;
				Node* prve = cur->_pPrve;

				prve->_pNext = newnode;
				newnode->_pPrve = prve;
				newnode->_pNext = cur;
				cur->_pPrve = newnode;
				++_size;
                //迭代器构造
				return iterator(newnode);
			}
            //尾插
            void push_back(const T& val)
			{
				insert(end(), val);
			}
            //头插
            void push_front(const T& val)
			{
				insert(begin(), val);
			}

3. erase和pop

        erase这里也是用迭代器,删除当前位置,返回节点的下一个位置。

        如果没有实现迭代器,头删就是删除哨兵位的下一个节点,尾删就是删除哨兵位的前一个节点。

// 删除pos位置的节点,返回该节点的下一个位置
			iterator erase(iterator pos)
			{
				assert(pos != end());
				Node* prve = pos._pNode->_pPrve;
				Node* next = pos._pNode->_pNext;

				prve->_pNext = next;
				next->_pPrve = prve;

				delete pos._pNode;
				--_size;
                //迭代器构造
				return iterator(next);
			}
            //尾删
            void pop_back()
			{
				erase(--end());
			}
		    //头删
			void pop_front()
			{
				erase(begin());
			}

 4. 迭代器类

//List的迭代器类
	template<class T,class Ref,class Ptr>
	class ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T,Ref,Ptr> Self;
	public:
        //迭代器构造
		ListIterator(Node* pNode = nullptr)
			:_pNode(pNode)
		{}
		ListIterator(const Self& it)
			:_pNode(it._pNode)
		{}
		

		Node* _pNode;
	};

  4.1 * , -> , != , ==

        //返回当前节点的值
        Ref operator*()
		{
			return _pNode->_val;
		}
        //返回当前节点的值的地址
		Ptr operator->()
		{
			return &(_pNode->_val);
		}
		//迭代器相不相同
		bool operator!=(const Self& it)
		{
			return _pNode != it._pNode;
		}
		bool operator==(const Self& it)
		{
			return _pNode == it._pNode;
			
		}

  4.2 ++,--

        //前置++
        Self& operator++()
		{
			_pNode = _pNode->_pNext;
			return *this;
		}
        //后置++
		Self operator++(int)
		{
			Self tmp(*this);
			_pNode = _pNode->_pNext;
			return tmp;
		}
        //前置--
		Self& operator--()
		{
			_pNode = _pNode->_pPrve;
			return *this;
		}
        //后置--
		Self operator--(int)
		{
			Self tmp(*this);
			_pNode = _pNode->_pPrve;
			return *this;
		}

 5. begin 和end

        begin是哨兵位的下一个节点。

        end是哨兵位节点,也就是尾结点的下一个节点。


public:
			typedef ListIterator<T, T&, T*> iterator;
			typedef ListIterator<T, const T&, const T*> const_iterator;
//迭代器
			iterator begin()
			{
				return iterator(_pHead->_pNext);
			}
			iterator end()
			{
				return iterator(_pHead);
			}
			const_iterator begin()const
			{
				return const_iterator(_pHead->_pNext);
			}
			const_iterator end()const
			{
				return const_iterator(_pHead);
			}

 6. 构造,拷贝构造,赋值重载, 析构

        这里的拷贝构造和赋值重载用的是一个现代写法,找一个打工人,然后把他的成果拿来用。

            //构造
            list()
			{
                //初始化哨兵位节点
				CreateHead();
			}
            //构造n个节点
			list(int n, const T& value = T())
			{
				CreateHead();
				while (n--)
				{
					push_back(value);
				}
			}
            //用迭代器区间去构造
			template<class iterator>
			list(iterator first, iterator last)
			{
				CreateHead();
				while (first != last)
				{
					push_back(*first);
					++first;
				}
			}
            //拷贝构造 it2(it1)
			list(const list<T>& it)
			{
				CreateHead();
                
				list<T> tmp(it.begin(), it.end());
				swap(tmp);
			}
			//赋值重载
			list<T>& operator=( list<T> it)
			{
				CreateHead();
				swap(it);
				return *this;
			}
			//析构
			~list()
			{
                //清空链表节点(不删除哨兵位)
				clear();
				delete _pHead;
				_pHead = nullptr;
			}

 7. size和empty,swap,clear

            //容量
			size_t size()const
			{
				return _size;
			}
			bool empty()const
			{
				return _size == 0;
			}
            //清除链表节点,不删除哨兵位节点
			void clear()
			{
				iterator it = begin();
				while (it != end())
				{
					it = erase(it);
				}
			}
			void swap(list<T>& it)
			{
				std::swap(_pHead, it._pHead);
				std::swap(_size, it._size);
			}

 8. front和back

            //取头节点的值
			T& front()
			{
				return _pHead->_pNext->_val;
			}
			const T& front()const
			{
				return _pHead->_pNext->_val;
			}
			//取尾节点的值
			T& back()
			{
				return _pHead->_pPrve->_val;
			}
			const T& back()const
			{
				return _pHead->_pPrve->_val;
			}

操作演示:

 

 

 

三、 list与vector的对比

        vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不 同,其主要不同如下:

完整代码:list/list/List.h · 晚风不及你的笑/作业库 - 码云 - 开源中国 (gitee.com)

猜你喜欢

转载自blog.csdn.net/weixin_68993573/article/details/128262378