<C++>STL->list

list的介绍

list

  • list是一个类模板,第一个模板参数为存储数据类型;第二个模板参数为空间适配器
  • list是一个可以在常数时间内完成任意位置的插入和删除的顺序容器。
  • list容器是以双链表的形式实现的;双链表可以将其包含的每个元素存储在不同且不相关的存储位置。每个元素都有一个指向其前面元素的指针和一个指向其后面元素的指针,从而在内部保持排序。
  • 与其他基本标准序列容器(arrayvectordeque)相比,list在插入、提取和移动容器内任何位置的元素(已获得迭代器)时通常表现更好,因此在密集使用这些元素的算法(如排序算法)中也是如此。
  • 与其他序列容器相比,listforward_lists 的主要缺点是它们无法通过元素的位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置(如开始或结束位置)迭代到该位置,而这需要花费这些位置之间距离的线性时间。此外,它们还需要消耗一些额外的内存来保存与每个元素相关的链接信息(这可能是由小尺寸元素组成的大列表的一个重要因素)。

list的使用

list的使用和vector类似,主要介绍listvector之间的不同点:

  • 时间复杂度:vector获取元素的时间复杂度为 O ( 1 ) O(1) O(1),尾插和尾删的时间复杂度为 O ( n ) O(n) O(n),其他位置插入和删除是 O ( n ) O(n) O(n);list获取元素的时间复杂度为 O ( n ) O(n) O(n),任意位置插入删除时间复杂度为 O ( 1 ) O(1) O(1)。

  • 迭代器失效:vector在insert,erase之后,原本指向插入、删除后面所有元素的迭代器都会失效;list在insert,erase之后,只有指向插入或删除位置的迭代器才会失效

  • clear成员函数:vectorclear函数会将size清零,capacity保持不变;listclear函数会将size清零,只留下头节点

  • 迭代器的实现不同:

    • vector的迭代器底层就是原生指针。即typedef T* std::vector::iterator ,此后对迭代器进行解引用和++就相当于对指针进行解引用和++,可以访问容器的数据和遍历容器。
    • list的迭代器底层是指针,但不是原生指针,而是封装后的指针。在使用上我们需要解引用迭代器可以获得节点的数据,迭代器++可以遍历容器。list的数据结构是链表,链表各个节点的存储是不连续的 ,所以直接对指针进行解引用和++是不能实现获取链表节点的数据和遍历链表的。我们必须对指针进行运算符重载,但是内置类型不支持运算符重载,需要将指针封装成一个以该指针为成员变量的类,对类进行operator+operator*重载。
    • vector没有的成员函数:
    1. splice

      • 功能:将元素从 x 传输到容器中,并将它们插入到位置

      • 参数:

        position:容器内插入x元素的位置

        x:与*this具有相同类型元素的list

        i:x中的迭代器,只有单个数据会被传输

        first,last:指向容器x要被传输区间的头和尾部的下一个位置,及区间[first,last)会被传输到*thisposition位置。

      • 返回值:空

      • 注意:再调用函数后,原本指向元素的迭代器仍然指向该元素,但是迭代器可能指向的容器发生改变

      demo:

      c 复制代码
      // splicing lists
      #include <iostream>
      #include <list>
      
      int main ()
      {
        std::list<int> mylist1, mylist2;
        std::list<int>::iterator it;
      
        // set some initial values:
        for (int i=1; i<=4; ++i)
           mylist1.push_back(i);      // mylist1: 1 2 3 4
      
        for (int i=1; i<=3; ++i)
           mylist2.push_back(i*10);   // mylist2: 10 20 30
      
        it = mylist1.begin();
        ++it;                         // points to 2
      
        mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                      // mylist2 (empty)
                                      // "it" still points to 2 (the 5th element)
                                                
        mylist2.splice (mylist2.begin(),mylist1, it);
                                      // mylist1: 1 10 20 30 3 4
                                      // mylist2: 2
                                      // "it" is now invalid.
        it = mylist1.begin();
        std::advance(it,3);           // "it" points now to 30
      
        mylist1.splice ( mylist1.begin(), mylist1, it, mylist1.end());
                                      // mylist1: 30 3 4 1 10 20
      
        std::cout << "mylist1 contains:";
        for (it=mylist1.begin(); it!=mylist1.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        std::cout << "mylist2 contains:";
        for (it=mylist2.begin(); it!=mylist2.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }// splicing lists
      #include <iostream>
      #include <list>
      
      int main ()
      {
        std::list<int> mylist1, mylist2;
        std::list<int>::iterator it;
      
        // set some initial values:
        for (int i=1; i<=4; ++i)
           mylist1.push_back(i);      // mylist1: 1 2 3 4
      
        for (int i=1; i<=3; ++i)
           mylist2.push_back(i*10);   // mylist2: 10 20 30
      
        it = mylist1.begin();
        ++it;                         // points to 2
      
        mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                      // mylist2 (empty)
                                      // "it" still points to 2 (the 5th element)
                                                
        mylist2.splice (mylist2.begin(),mylist1, it);
                                      // mylist1: 1 10 20 30 3 4
                                      // mylist2: 2
                                      // "it" is now invalid.
        it = mylist1.begin();
        std::advance(it,3);           // "it" points now to 30
      
        mylist1.splice ( mylist1.begin(), mylist1, it, mylist1.end());
                                      // mylist1: 30 3 4 1 10 20
      
        std::cout << "mylist1 contains:";
        for (it=mylist1.begin(); it!=mylist1.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        std::cout << "mylist2 contains:";
        for (it=mylist2.begin(); it!=mylist2.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }

      Output:

      c 复制代码
      mylist1 contains: 30 3 4 1 10 20
      mylist2 contains: 2
    2. remove

      • 功能:移除容器中所有值为val的数据,会调用移除数据对应的析构函数,同时会减少容器的size。
      • 参数:需要移除的元素,类型value_type是类模板T的重定义
      • 返回值:无
      • 注意:
        • remove函数是删除所有值为val的数据,erase删除迭代器pos处的位置。
        • 指向val数据处的迭代器会失效,其余的不会失效。

      demo:

      c 复制代码
      // remove from list
      #include <iostream>
      #include <list>
      
      int main ()
      {
        int myints[]= {17,89,7,14};
        std::list<int> mylist (myints,myints+4);
      
        mylist.remove(89);
      
        std::cout << "mylist contains:";
        for (std::list<int>::iterator it=mylist.begin(); it!=mylist.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }

      output:

      c 复制代码
      mylist contains: 17 7 14
    3. remove_if:删除满足条件的数据,条件需要通过仿函数传递, 等我们讲解仿函数后再来讲解。

    4. unique

      • 功能:删除连续的重复元素,所有重复元素只保留第一个
      • 参数:
        第一个版本是严格删除所有值相等的连续元素。第二个版本我们可以根据传参自己定义"相同性",通过仿函数实现。
      • 返回值:空
      • 注意:指向删除元素的迭代器会失效。

      demo:

      output:

    5. merge

      • 功能:通过将 x 的所有元素在各自有序位置传输到容器中(两个容器都应已排序),将 x 合并到列表中。
      • 参数:
        • x:与*this具有相同类型的list,函数调用结束后x会被修改
        • comp:仿函数参数,决定具体是如何比较的
      • 返回值:空
      • 注意:
        • 执行merge的前提是两个list都是有序的,list无序请使用std::list::splicex中的元素按照operator<或者comp定义的比较方法找到合适的位置进行插入,由此产生的等价元素顺序是稳定的。
        • 执行完后,xsize会变为0。
        • 执行该操作时,既不构造也不销毁任何元素。
        • 如果this==&x,则函数不会执行任何操作。
        • 调用函数之后,迭代器仍然指向之前的元素,但是指向的可能从一个容器变成另一个容器。

      demo:

      c 复制代码
      // list::merge
      #include <iostream>
      #include <list>
      
      // compare only integral part:
      bool mycomparison (double first, double second)
      { return ( int(first)<int(second) ); }
      
      int main ()
      {
        std::list<double> first, second;
      
        first.push_back (3.1);
        first.push_back (2.2);
        first.push_back (2.9);
      
        second.push_back (3.7);
        second.push_back (7.1);
        second.push_back (1.4);
      
        first.sort();
        second.sort();
      
        first.merge(second);
      
        // (second is now empty)
      
        second.push_back (2.1);
      
        first.merge(second,mycomparison);
      
        std::cout << "first contains:";
        for (std::list<double>::iterator it=first.begin(); it!=first.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }

      output:

      c 复制代码
      first contains: 1.4 2.2 2.9 2.1 3.1 3.7 7.1

      请注意,在第二次合并中,函数 mycomparison(仅比较整数部分)没有考虑 2.1 低于 2.2 或 2.9,因此它被插入到它们之后、3.1 之前。

    6. sort

      • 功能:对列表中的元素进行排序,改变它们在容器中的位置。排序是通过使用 operator<(版本(1))或 comp(版本(2))来比较元素的算法进行的。这种比较应产生严格的元素弱排序(即一致的反式比较,不考虑其反射性)。
      • 参数:二元谓词,在取两个与列表中的值类型相同的值时,如果第一个参数在其定义的严格弱排序中位于第二个参数之前,则返回 true,否则返回 false。该谓词应是函数指针或函数对象。
      • 返回值:空
      • 注意:
        • 整个操作不涉及任何元素对象的构造、销毁或复制。元素在容器内移动。
        • 迭代器不会失效,还是只想之前的元素。

      demo:

      c 复制代码
      // list::sort
      #include <iostream>
      #include <list>
      #include <string>
      #include <cctype>
      
      // comparison, not case sensitive.
      bool compare_nocase (const std::string& first, const std::string& second)
      {
        unsigned int i=0;
        while ( (i<first.length()) && (i<second.length()) )
        {
          if (tolower(first[i])<tolower(second[i])) return true;
          else if (tolower(first[i])>tolower(second[i])) return false;
          ++i;
        }
        return ( first.length() < second.length() );
      }
      
      int main ()
      {
        std::list<std::string> mylist;
        std::list<std::string>::iterator it;
        mylist.push_back ("one");
        mylist.push_back ("two");
        mylist.push_back ("Three");
      
        mylist.sort();
      
        std::cout << "mylist contains:";
        for (it=mylist.begin(); it!=mylist.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        mylist.sort(compare_nocase);
      
        std::cout << "mylist contains:";
        for (it=mylist.begin(); it!=mylist.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }

      output:

      c 复制代码
      mylist contains: Three one two
      mylist contains: one Three two

      对于默认字符串,比较是严格的字符代码比较,其中所有大写字母都比所有小写字母小,在第一次排序操作中将所有以大写字母开头的字符串放在前面。

    7. reverse

      • 功能:逆置list中的数据。
      • 参数:空
      • 返回值:空
      • 注意:迭代器不会失效,让然指向之前的元素,但是位置可能发生改变

list的实现

  • 有关list的拷贝时,注意深拷贝问题!

  • 实现list的重头主要是迭代器的实现,其余的操作和双链表没有什么区别。下面直接直接看代码~

    c 复制代码
    /*list.h*/
    #define _CRT_SECURE_ 1
    #pragma once
    #include <iostream>
    #include <assert.h>
    #include <algorithm>
    #include <string>
    #include <list>
    using std::cout;
    using std::cin;
    using std::endl;
    using std::string;
    namespace myList
    {
    	/*定义节点类型*/
    	template<class T>
    	struct Node
    	{
    		T _val;
    		Node* _next;
    		Node* _prev;
    
    		Node(const T& val = T())
    			:_val(val),
    			_next(nullptr),
    			_prev(nullptr)
    		{}
    	};
    
    	/*定义list迭代器,我们想让list迭代器的++和*与普通指针操作不同
    	所以要将迭代器封装为一个以指向节点的指针为成员变量的类,对类进行运算符重载*/
    	template <class T, class Ref, class Ptr>
    	struct __list_iterator	//封装的迭代器类
    	{
    		typedef Node<T> Node;
    		typedef __list_iterator<T, Ref, Ptr> self;//简化迭代器的名称
    		/*成员变量---指向节点的指针*/
    		Node* _node;
    		/*成员函数*/
    		__list_iterator( Node* node)			  //单参数构造函数
    			:_node(node)				
    		{}
    		__list_iterator(const self& it)			  //拷贝构造函数
    		{
    			_node = it._node;
    		}
    		//重载后缀++								  //迭代器++相当于指向节点的指针指向链表中的下一个节点---_node=_node->next
    		self operator++(int)
    		{
    			self tmp(*this);
    			_node = _node->_next;
    			return tmp;
    		}
    		//重载前缀++
    		self& operator++()
    		{
    			_node = _node->_next;
    			return *this;
    		}
    		//重载后缀--
    		self operator--(int)
    		{
    			self tmp(*this);
    			_node = _node->_prev;
    			return tmp;
    		}
    		//重载前缀--
    		self& operator--()
    		{
    			_node = _node->_prev;
    			return *this;
    		}
    		//重载*
    		Ref operator*()							   //迭代器解引用相当于访问链表节点的数据与---_node->_val
    		{
    			return _node->_val;
    		}
    		//重载->
    		Ptr operator->()						   //list存储自定义类型时,重载->使得通过it->自定义成员可以直接访问自定义类型
    		{
    			return &_node->_val;
    		}
    		//重载==
    		bool operator==(const self& it)			   //2个迭代器指向链表的节点是否一样
    		{
    			return _node == it._node;
    		}
    		//重载!=
    		bool operator!=(const self& it)
    		{
    			return _node != it._node;
    		}
    
    	};
    	template<class T>
    	class list
    	{
    	private:
    		Node<T>* _head;							  //指向头节点
    	public:
    		typedef Node<T> Node;
    		//泛型编程
    		typedef __list_iterator<T, T&, T*> iterator;//普通迭代器的operator* operator->返回值所引用/指向的对象是可以修改的,返回普通引用/指针
    		typedef __list_iterator<T, const T&, const T*> const_iterator;//const迭代器的operator* operator->的返回值所引用/指向的对象不可以修改
    																	  //无法对*it或it->__进行写入,返回常引用/指向常量的指针
    
    		//下面直接返回指针,通过单参数构造函数的隐式类型转换,可以根据返回的指针构造一个对象,传值时在进行拷贝构造-->会优化为直接构造
    		iterator begin()
    		{
    			return _head->_next;//单参数构造函数的隐式类型转换
    		}
    		const_iterator begin() const
    		{
    			return _head->_next;//单参数构造函数的隐式类型转换
    		}
    		iterator end()
    		{
    			return _head;		//单参数构造函数的隐式类型转换
    		}
    		const_iterator end() const
    		{
    			return _head;		//单参数构造函数的隐式类型转换
    		}
    
    		//初始化一个带头双向循环链表
    		void empty_init()
    		{
    			_head = new Node;
    			_head->_prev = _head->_next = _head;
    		}
    		list()
    		{
    			empty_init();
    		}
    		list(int n, const T& val = T())					//fill构造函数
    		{
    			assert(n >= 0);
    			empty_init();
    			while (n--)
    			{
    				push_back(val);
    			}
    		}
    		template<class InputIterator>
    		list(InputIterator first, InputIterator last)  //range构造函数
    		{
    			empty_init();
    			while (first != last)
    			{
    				push_back(*first++);
    			}
    		}
    		//lt1(lt2)拷贝时注意深拷贝问题,使用push_back可以进行深拷贝
    		list(const list<T>& lt)
    		{
    			empty_init();
    			for (auto e : lt)
    			{
    				push_back(e);
    			}
    		}
    		/*修改类函数*/
    		void push_back(const T& val)
    		{
    			insert(end(), val);
    		}
    		void push_front(const T& val)
    		{
    			insert(begin(), val);
    		}
    		void pop_back()
    		{
    			erase(--end());
    		}
    		void pop_front()
    		{
    			erase(begin());
    		}
    		iterator insert(iterator pos, const T& val)//pos位置前插入元素
    		{
    			Node* newnode = new Node(val);
    			Node* cur = pos._node;
    			Node* prev = pos._node->_prev;
    			//连接节点
    			prev->_next = newnode;
    			newnode->_prev = prev;
    			newnode->_next = cur;
    			cur->_prev = newnode;
    			return pos;
    		}
    		iterator erase(iterator pos)
    		{
    			assert(pos != _head);//不能删除头节点
    			Node* prev = pos._node->_prev;
    			Node* next = pos._node->_next;
    			prev->_next = next;
    			next->_prev = prev;
    			delete pos._node;
    			return next;
    		}
    
    		void clear()//除头节点外所有节点全部删除
    		{
    			iterator it = begin();
    			while (it != end())
    			{
    				it = erase(it);
    			}
    		}
    		void swap(list<T>& lt)//交换两个list的值
    		{
    			std::swap(_head, lt._head);
    		}
    		//lt1=lt3
    		list<T>& operator=( list<T> lt)
    		{
    			swap(lt);
    			return *this;
    		}
    
    		size_t size()
    		{
    			size_t sz = 0;
    			iterator it = begin();
    			while (it != end())
    			{
    				sz++;
    			}
    			return sz;
    		}
    		~list()
    		{
    			clear();
    			delete _head;
    		}
    	};
    
    		//test1现在不是成员函数
    	void test1()
    	{
    		int arr[5] = { 1, 3 ,5, 7, 9 };
    		list<int> lt1(arr, arr + 5);
    		for (auto e : lt1)
    		{
    			cout << e << " ";
    		}
    		cout << endl;
    		list<int> lt2(3, 1);
    		for (auto e : lt2)
    		{
    			cout << e << " ";
    		}
    		cout << endl;
    		cout << endl;
    	}
    	void test2()
    	{
    		list<string> lt_str;
    		lt_str.push_back("hello");
    		lt_str.push_back("list");
    		lt_str.push_back("!!!");
    		list<string> lt_copy_str(lt_str);
    		for (auto e : lt_copy_str)
    		{
    			cout << e << endl;
    		}
    		lt_copy_str.pop_back();
    		lt_copy_str.pop_front();
    		for (auto e : lt_copy_str)
    		{
    			cout << e << endl;
    		}
    		lt_str = lt_copy_str;
    		for (auto e : lt_str)
    		{
    			cout << e << endl;
    		}
    	}
    }
    
    /*main.cpp*/
    #define _CRT_SECURE_NO_WARNINGS 1
    #include "List.h"
    //using namespace std;
    int main()
    {
            myList::test1();
    	myList::test2();
    	return 0;
    }

    output:

说明:list的模板参数有三个

  • 第一个参数class T决定list存储的数据类型。
  • 第二个参数class Ref决定operator*的返回值类型
    1. iterator的operator*返回值类型为可以修改的引用T&
    2. const_iterator的operator*返回值类型为不可修改的引用const T&
  • 第三个参数clss Ptr决定operator->的返回值类型
    1. iterator的operator*返回值类型为指向对象可修改的指针T*
    2. const_iterator的operator->返回值类型为指向不可修改的对象的指针const T*
c 复制代码
struct AA
{
    int a;
    int b;
};

重载operator->后,假若list中存放的是自定义类型AA,可以通过it->a1访问自定义类型中的成员

相关推荐
谈谈叭6 分钟前
Javascript中的深浅拷贝以及实现方法
开发语言·javascript·ecmascript
lx学习7 分钟前
Python学习26天
开发语言·python·学习
大今野1 小时前
python习题练习
开发语言·python
爱编程的鱼1 小时前
javascript用来干嘛的?赋予网站灵魂的语言
开发语言·javascript·ecmascript
捕鲸叉2 小时前
怎样在软件设计中选择使用GOF设计模式
c++·设计模式
捕鲸叉2 小时前
C++设计模式和编程框架两种设计元素的比较与相互关系
开发语言·c++·设计模式
未知陨落3 小时前
数据结构——二叉搜索树
开发语言·数据结构·c++·二叉搜索树
大波V53 小时前
设计模式-参考的雷丰阳老师直播课
java·开发语言·设计模式
无敌最俊朗@3 小时前
unity3d————接口基础知识点
开发语言·c#
一丝晨光3 小时前
gcc 1.c和g++ 1.c编译阶段有什么区别?如何知道g++编译默认会定义_GNU_SOURCE?
c语言·开发语言·c++·gnu·clang·gcc·g++