C++ list

文章详细介绍了C++标准库中的list容器,包括其介绍、使用方法、迭代器失效情况,以及与vector的性能对比。list是一种基于双向链表的数据结构,适合于频繁插入和删除操作,但不支持随机访问。文章还提供了list的模拟实现代码,并展示了如何处理迭代器失效的问题。
摘要由CSDN通过智能技术生成

1. list的介绍及使用

1.1 list的介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
    其前一个元素和后一个元素。
  3. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  4. 与其他序列式容器相比,list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销。

在这里插入图片描述

1.2 list的使用

<-网站->: list 所有函数
在这里插入图片描述

#include<iostream>
using namespace std;
#include<list>
///
///
#include <vector>

void TestList1()
{
    list<int> L1;
    list<int> L2(10, 5);

    vector<int> v{ 1,2,3,4,5 };
    
    list<int> L3(v.begin(), v.end());//区间构造

    list<int> L4(L3);

    // C++11
    list<int> L5{ 1, 2, 3, 4, 5 };
    /
    // 链表遍历
    for (auto e : L2)
        cout << e << " ";
    cout << endl;

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

    auto rit = L4.rbegin();
    while (rit != L4.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;

    // 注意:list不能使用原生态指针,即Node*来遍历链表
    // 因为list没有提供获取头指针的方法
}

void TestList2()
{
    list<int> L{ 1, 2, 3, 4, 5 };
    cout << L.size() << endl;

    L.resize(10, 6);
    for (auto e : L)
        cout << e << " ";
    cout << endl;

    L.resize(20);
    for (auto e : L)
        cout << e << " ";
    cout << endl;

    L.resize(7);
    for (auto e : L)
        cout << e << " ";
    cout << endl;
}

void TestList3()
{
    list<int> L{ 1, 2, 3, 4, 5 };
    
    L.push_back(6);
    L.push_back(7);
    L.push_back(8);
    L.push_back(9);
    cout << L.size() << endl;
    cout << L.front() << endl;
   
    cout << L.back() << endl;

    L.pop_back();
    L.pop_back();
    for (auto e : L)
        cout << e << " ";
    cout << endl;

    L.push_front(0);
    for (auto e : L)
        cout << e << " ";
    cout << endl;

    L.pop_front();
    for (auto e : L)
        cout << e << " ";
    cout << endl;

    L.insert(L.begin(), 0); //插入一个元素

    auto pos = find(L.begin(), L.end(), 5);
    if (pos != L.end())
        L.insert(pos, 5, 8); // 插入5 个 8

    int array[] = { 10, 20, 30, 40, 50 };

    L.insert(L.end(), array, array + sizeof(array) / sizeof(array[0]));//在末尾插入一段区间
}

void TestList4()
{
    list<int> L{ 1, 2, 3, 4, 5 };
    L.push_back(6);
    L.push_back(7);
    L.push_back(8);
    L.push_back(9);

    L.erase(L.begin());
    L.erase(find(L.begin(), L.end(), 8));
    L.erase(L.begin(), L.end());    // 相当于clear();

    L.assign(10, 5);// 相当于Linux里面的打开文件并清除之前的内容,有可能底层空间会改变

    int array[] = { 10, 20, 30, 40, 50 };
    L.assign(array, array + sizeof(array) / sizeof(array[0]));
}

void TestList5()
{
    list<int> L{ 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 ,1,1,1 };
    L.remove(1);//删除所有值为1的元素

    L.sort();

    L.unique(); //排序后才能去重

    L.reverse(); //逆置
}

bool IsEven(int data)//判断是否为偶数{
    return 0 == data % 2;
}

void TestList6()
{
    list<int> L{ 1, 2, 3, 4, 5 };
    L.push_back(6);
    L.push_back(7);
    L.push_back(8);
    L.push_back(9);

    L.remove_if(IsEven);//删除所有偶数
}

void TestList7()//迭代器失效
{
    list<int> L{ 1, 2, 3, 4, 5 };
    L.push_back(6);
    L.push_back(7);
    L.push_back(8);
    L.push_back(9);

    auto it = L.begin();
    /*L.pop_front();
    while (it != L.end())
    {
    	cout << *it << " ";
    	++it;
    }*/

    //L.resize(0);
    //while (it != L.end())
    //{
    //	cout << *it << " ";
    //	++it;
    //}

    // L.assign(10, 5);
    //list<int> L1{ 10, 20, 30, 40, 50 };
    //L = L1;

    //while (it != L.end())
    //{
    //	cout << *it << " ";
    //	++it;
    //}

    while (it != L.end())
    {
         L.erase(it);
         ++it;
        it = L.erase(it);
    }
}

int main()
{
    //TestList1();
    //TestList2();
    //TestList3();
    //TestList4();
    //TestList5();
    //TestList6();
    TestList7();
    return 0;
}

1.3 list的迭代器失效

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

void TestListIterator1()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> L1(array, array+sizeof(array)/sizeof(array[0]));
	auto it = L1.begin();
	while (it != L1.end())
	{
 // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
	L1.erase(it); 
	++it;
 	}
}
// 改正
void TestListIterator2()
{
 	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 	list<int> L2(array, array+sizeof(array)/sizeof(array[0]));
 	auto it = L2.begin();
 	while (it != L2.end())
 	{
 		 it = L2.erase(it);
 	}
}

在这里插入图片描述

2. list的模拟实现

先定义一个 List.h 头文件
链接: List.h 头文件

#pragma once

namespace yan {

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

		ListNode(const T& value = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_value(value)
		{}
	};

	template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListNode<T>  Node;

		typedef Ref ItRef;
		typedef Ptr ItPtr;

		typedef ListIterator<T, Ref, Ptr>  Self;
	public:
		ListIterator(Node* pNode = nullptr)
			:_pNode(pNode)
		{}
		//
		// 具有指针类似的操作
		Ref operator*()
		{
			return _pNode->_value;
		}
		Ptr operator->()
		{
			return &(operator*());
		}
		
		// 移动
		Self& operator++()
		{
			_pNode = _pNode->_next;
			return *this;
		}
		Self operator++(int)
		{
			Self temp(*this);
			_pNode = _pNode->_next;
			return temp;
		}
		

		Self& operator--()
		{
			_pNode = _pNode->_prev;
			return *this;
		}
		Self operator--(int)
		{
			Self temp(*this);
			_pNode = _pNode->_prev;
			return temp;
		}
		///
		// 比较
		bool operator==(const Self& s)const
		{
			return _pNode == s._pNode;
		}

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

	public:
		Node* _pNode;
	};

	// 反向迭代器:就是对正向迭代器的包装
	template<class Iterator>
	struct ListReverseIterator
	{
		// 静态成员变量也可以通过类名::静态成员变量名称
		// typedef Iterator::ItRef Ref;  编译报错
		// 编译器无法在编译时无法确定ItRef是静态成员变量 还是 类型
		// 需要显式告诉编译器ItRef就是Iterator类中的类型
		// typename
		typedef typename Iterator::ItRef Ref;
		typedef typename Iterator::ItPtr Ptr;
		typedef ListReverseIterator<Iterator> Self;
	public:
		ListReverseIterator(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(*this);
			--_it;
			return temp;
		}

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

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

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

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

		Iterator _it;
	};


	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;

		typedef ListReverseIterator<iterator> reverse_iterator;
		typedef ListReverseIterator<const_iterator> const_reverse_iterator;
	
		// 注意:list的迭代器一定不能是原生态的指针
		// vector之所以可以,因为vector底层是连续空间
		// 如果指针指向连续的空间,对指针++/--,该指针就可以移动到下一个/前一个位置
		// 但是list不行,因为链表中的节点是通过next和prev指针组织起来的,不一定连续
		// 如果将list的迭代器设置为原生态指针,++it/--it没有意义
		// typedef Node* iterator;  // 

	public:
		
		// 构造
		list()
		{
			CreateHead();
		}

		list(int n, const T& value = T())
		{
			CreateHead();
			for (int i = 0; i < n; ++i)
			{
				push_back(value);
			}
		}

		template<class Iterator>
		list(Iterator first, Iterator last)
		{
			CreateHead();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list(const list<T>& L)
		{
			CreateHead();
			auto it = L.cbegin();
			while (it != L.cend())
			{
				push_back(*it);
				++it;
			}
		}

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

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		//
		// 迭代器
		// 增加begin和end的方法
		iterator begin(){
			/*iterator ret(_head->_next);
			return ret;*/

			return iterator(_head->_next);//返回一个匿名对象
		}

		iterator end(){
			return iterator(_head);
		}

		const_iterator cbegin()const{
			return const_iterator(_head->_next);
		}

		const_iterator cend()const{
			return const_iterator(_head);
		}

		reverse_iterator rbegin(){
			return reverse_iterator(end());
		}

		reverse_iterator rend(){
			return reverse_iterator(begin());
		}

		const_reverse_iterator crbegin()const{
			return const_reverse_iterator(cend());
		}

		const_reverse_iterator crend()const{
			return const_reverse_iterator(cbegin());
		}

		//
		// 容量
		size_t size()const
		{
			size_t count = 0;
			Node* cur = _head->_next;
			while (cur != _head)
			{
				count++;
				cur = cur->_next;
			}

			return count;
		}

		bool empty()const
		{
			return _head == _head->_next;
		}

		void resize(size_t newsize, const T& value = T())
		{
			size_t oldsize = size();
			if (newsize <= oldsize)
			{
				for (size_t i = newsize; i < oldsize; ++i)
				{
					pop_back();
				}
			}
			else
			{
				for (size_t i = oldsize; i < newsize; ++i)
				{
					push_back(value);
				}
			}
		}

		//
		// 元素访问
		T& front()
		{
			return *begin();
		}

		const T& front()const
		{
			//return _head->_next->_value;
			return *cbegin();
		}

		T& back()
		{
			return *(--end());
		}

		const T& back()const
		{
			// return _head->_prev->_value;
			return *(--cend());
		}
		///
		// 修改
		void push_front(const T& value)
		{
			insert(begin(), value);
		}

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

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

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

		iterator insert(iterator it, const T& value)
		{
			Node* pos = it._pNode;
			Node* newNode = new Node(value);
			newNode->_next = pos;
			newNode->_prev = pos->_prev;
			newNode->_prev->_next = newNode;
			pos->_prev = newNode;

			return newNode;
		}

		iterator erase(iterator it)
		{
			if (it == end())
				return end();

			Node* pos = it._pNode;
			Node* ret = pos->_next;
			pos->_prev->_next = pos->_next;
			pos->_next->_prev = pos->_prev;
			delete pos;
			return ret;
		}

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

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

	private:
		void CreateHead()//构造头结点的函数
		{
			_head = new Node();//typedef ListNode<T>  Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

	private:
		Node* _head;
	};
}
void TestList1()
{
	yan::list<int> L1;
	yan::list<int> L2(10, 5);

	int array[] = { 1, 2, 3, 4, 5 };
	yan::list<int> L3(array, array + sizeof(array) / sizeof(array[0]));

	yan::list<int> L4(L3);

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

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

void TestList2()
{
	int array[] = { 1, 2, 3, 4, 5 };
	yan::list<int> L1(array, array + sizeof(array) / sizeof(array[0]));
	auto itL1 = L1.begin();
	*itL1 = 10;

	auto citL1 = L1.cbegin();
	//*citL1 = 100;

	cout << L1.front() << endl;
	cout << L1.back() << endl;
	cout << L1.size() << endl;

	const yan::list<int> L2(L1);
	auto it = L2.cbegin();

	cout << L2.front() << endl;
	cout << L2.back() << endl;
}

void TestList3()
{
	yan::list<int> L;
	L.push_back(1);
	L.push_back(2);
	L.push_back(3);
	L.push_back(4);
	L.push_back(5);
	cout << L.size() << endl;

	L.push_front(0);
	for (auto e : L)
		cout << e << " ";
	cout << endl;

	L.pop_front();
	for (auto e : L)
		cout << e << " ";
	cout << endl;
	
	
}

void TestList4()
{
	yan::list<int> L;
	L.push_back(1);
	L.push_back(2);
	L.push_back(3);
	L.push_back(4);
	L.push_back(5);

	auto it = L.rbegin();
	while (it != L.rend())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	auto rit = L.crbegin();
	while (rit != L.crend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

写一个test.cpp测试
链接: test.cpp

3. list与vector的对比

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值