• STL库:list


    STL库:list


    1.STL库对list的官方介绍

    1. list 是序列式容器,可以在序列中的任意位置进行常数时间 O(1) 的插入和删除操作,以及双向迭代
    2. list 的底层是「带头双向循环链表」结构,双向链表中每个元素存储在不同且不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素
    3. list 与 forward_list 非常相似:主要区别在于 forward_list 是单链表,因此只能向前迭代,让其更简单高效
    4. 与其他的序列式容器相比 (array,vector,deque),list 在容器内任意位置进行插入、删除、移动元素方面的执行效率通常表现更好
    5. 与其他序列式容器相比,list 和 forward_list 「最大的缺陷是不支持任意位置的随机访问」。比如:要访问 list 中的第 6 个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在它们之间的距离上迭代需要线性的时间开销;list 还需要一些额外的空间,以保存每个元素相关联的链接信息(对于存储类型较小元素的大型列表来说这可能是一个重要的因素)

    请添加图片描述


    2.list的常用接口

    2.1 list的构造函数

    1. list():构造一个没有元素的空容器,size = 0
    2. list (size_type n, const value_type& val = value_type()):构造一个包含 n 个元素的容器。每个元素都是 val
    3. list (const list& x):拷贝构造
    4. template list (InputIterator first, InputIterator last):用迭代器区间 [first, last) 中的元素构造 list
    //list的构造
    #include 
    #include 
    
    int main ()
    {
      
      std::list<int> first;                                
      std::list<int> second (4,100);                       
      std::list<int> third (second.begin(),second.end());  
      std::list<int> fourth (third);                       
    
      int myints[] = {16,2,77,29};
      std::list<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.2 list的迭代器与遍历操作

    1. begin(iterator / const_iterator):返回指向容器中第一个元素的迭代器
    2. rbegin(reverse_iterator / const_reverse_iterator):反向迭代器
    3. end():返回指向容器中的最后一个元素下一个位置的迭代器(即头节点)
    4. rend():反向迭代器
    5. 范围for:C++11 支持更简介的范围 for 的新遍历方式(底层其实是被替换成迭代器,所以支持迭代器就支持范围 for)
    void test1()
    {
    	list<int> lt;
    	lt.push_back(1);
    	lt.push_back(2);
    	lt.push_back(3);
    	
    	for (auto it = lt.begin(); it != lt.end(); it++)
    		cout << *it << " ";
    	cout << endl;
    
    	for (auto& e : lt)
    		cout << e << " ";
    	cout << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.3 list的容量操作

    1. empty():判断容器是否为空
    2. size():返回容器中有效元素的个数(需要遍历完链表,时间代价为O(n))
    //size()
    size_t size() // 这是一个O(n)的接口,尽量少用
    {
        size_t n = 0; // 遍历整个链表,统计有效元素个数
    
        iterator it = begin();
        while (it != end())
        {
            n++;
            it++;
        }
        return n;
    }
    
    //empty()
    bool empty()
    {
        return begin() == end();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.4 list的访问操作

    1. list 不支持 [] 运算符,因为 list 容器物理地址不是连续的
    2. front():返回容器中第一个元素的引用
    3. back():返回容器中最后一个元素的引用

    2.5 list的修改操作

    1. push_front():头插,在 list 的第一个元素之前插入一个新元素
    2. pop_front():头删,删除 list 中第一个元素
    3. push_back():尾插,在容器的最后一个元素之后添加一个新元素
    4. pop_back():尾删,删除容器中的最后一个元素
    5. insert():在指定迭代器位置的元素之前插入新元素来扩展容器,插入单个元素:iterator insert (iterator position, const value_type& val);
    6. erase():从列表容器中删除单个元素(迭代器位置)或一系列元素([first,last))删除单个元素:iterator erase (iterator position);
    7. swap():交换两个容器的内容
    8. resize():调整有效元素个数(size),多出的位置用缺省值填充
    9. clear():清空容器中的有效元素,size = 0,并没有清除头节点
    //insert()
    iterator insert(iterator pos, const T& data)
    {
        Node* cur = pos._node;   // 当前节点(pos中封装了节点的指针)
        Node* prev = cur->_prev; // 前驱节点
        Node* newnode = new Node(data); // 新节点
    
        // 建立前驱节点和新节点的链接
        prev->_next = newnode;
        newnode->_prev = prev;
    
        // 建立新节点与当前节点的链接
        newnode->_next = cur;
        cur->_prev = newnode;
    
        // 返回指向第一个新插入元素的迭代器
        return iterator(newnode); // 注意:此处调用iterator的构造函数构造出一个匿名对象
    
        // return newnode; // 也可以这样写,因为单参数的构造函数支持隐式类型的转换
    }
    
    //erase()
    iterator erase(iterator pos)
    {
        assert(pos != end()); // 不能删除头节点
    
        Node* cur = pos._node;   // 当前节点
    
        Node* prev = cur->_prev; // 前驱节点
        Node* next = cur->_next; // 后继节点
    
        // 建立前驱节点与后继节点的链接
        prev->_next = next;
        next->_prev = prev;
    
        // 删除当前节点
        delete cur;
    
        // 返回指向被删除节点的下一个节点的迭代器
        return iterator(next); // 注意:此处调用iterator的构造函数构造出一个匿名对象
    }
    
    //push_back() push_front()
    void push_back(const T& data) // 尾插
    {
    	/*
        Node* newNode = new Node(data); // 构造新节点
    
        Node* tail = _head->_prev; // 尾节点
    
        // 建立尾节点和新节点的链接
        tail->_next = newNode;
        newNode->_prev = tail;
    
        // 建立新节点和头节点的链接
        newNode->_next = _head;
        _head->_prev = newNode;
    	*/
    
        /* 复用 insert 函数的代码 */
        
        insert(end(), data); // 在头节点的前面插入,相当于是尾插
    }
    
    void push_front(const T& data) // 头插
    {
        insert(begin(), data); // 在第一个有效节点前面插入,相当于是头插
    }
    
    //pop_back() pop_front()
    void pop_back() // 尾删
    {
        erase(--end()); 
    }
    
    void pop_front() // 头删
    {
        erase(begin()); 
    }
    
    //clear()
    void clear()
    {
    	/*
        iterator it = begin();
        while (it != end())
        {
            Node* cur = it._node; // 记录当前节点
            it++;                 // 遍历下一个节点
            delete cur;           // 删除当前节点
        }
    
        // 建立头节点自身的链接
        _head->_next = _head;
        _head->_prev = _head;
    	*/
    
        iterator it = begin();
        while (it != end())
        {
            it = erase(it); // 删除当前节点,返回指向下一个节点的迭代器
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    特别:list可以在任意位置进行O(1)级别的插入和删除操作

    //insert
    // 返回指向第一个新插入元素的迭代器
    iterator insert (iterator position, const value_type& val); // 传值传参
    
    //erase
    // 返回指向被删除元素的下一个元素的迭代器,如果操作擦除了序列中的最后一个元素,则这是容器端
    iterator erase (iterator position); // 传值传参
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.6 list的特别容器操作

    1. sort():对 list 容器中的元素进行排序(注意:这是 list 的成员函数哦),效率很低,链表排序也没有太大的作用和价值
    2. unique():去重(删除重复值),必须要先排序,才能去完重复值
    3. remove():从容器中移除所有值等于 val 的元素(void remove (const value_type& val);)
    4. reverse():反转容器中元素的顺序
    5. merge():合并排序序列(合并前两个 list 容器都要排好序)

    1.sort()

    • 等价元素的结果顺序是稳定的:即等价元素保留它们在调用之前的相对顺序
    • 整个操作不涉及任何元素对象的构造、销毁或复制。元素在容器内移动
    void sort();//函数原型
    
    template <class Compare>//sort模板
    void sort (Compare comp);
    
    • 1
    • 2
    • 3
    • 4

    函数默认是排升序(<),如果想要排降序(>),需要传仿函数,比如:

    void test3()
    {
        int arr[] = { 10,2,5 };
    	list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
    	// > 排降序
        // 写法一:
    	greater<int> g; // 类模板,在头文件中
    	lt.sort(g);
        // 写法二:
    	lt.sort(greater<int>()); // 匿名对象 
    	// lt: 10 5 2    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.unique()

    void test4()
    {
        int arr[] = { 2,4,3,2,4 };
    	list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
    	lt.sort();   // 去重前,必须要先排序
    	lt.unique(); // 去除重复值
        // lt: 2 3 4
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.remove()

    void test5()
    {
    	int arr[] = { 1,2,3,3,4 };
    	list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
    	lt.remove(3); // lt: 1 2 4
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.reverse()

    void test6()
    {
    	int arr[] = { 1,2,3,4,5 };
    	list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
        lt.reverse(); // rlt: 5 4 3 2 1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.list的底层源码

    请添加图片描述

    // 链表节点结构
    template <class T>
    struct __list_node {
      typedef void* void_pointer;
      void_pointer next;
      void_pointer prev;
      T data;
    };
    
    // 迭代器 -- 一个类封装了节点指针
    template<class T, class Ref, class Ptr>
    struct __list_iterator {
      typedef __list_iterator<T, T&, T*>             iterator;
      typedef __list_iterator<T, const T&, const T*> const_iterator;
      typedef __list_iterator<T, Ref, Ptr>           self;
      // ...
      typedef Ptr pointer;
      typedef Ref reference;
      // ...
      typedef __list_node<T>* link_type;
      link_type node;                     // 节点指针
      // ...
    };
    
    // 带头双向循环链表结构
    template <class T, class Alloc = alloc>
    class list {
    protected:
      typedef __list_node<T> list_node;   // 节点
      // ...
    public:      
      typedef T value_type;
      typedef list_node* link_type;       // 节点指针
      // ...
    public:
      // 迭代器(这里的iterator和const_iterator是定义在list类域中的内嵌类型)
      typedef __list_iterator<T, T&, T*>             iterator; // 传了T,T&,T*模板参数
      typedef __list_iterator<T, const T&, const T*> const_iterator; // 传了T,const T&,const T*模板参数
      // ...
    protected:
      link_type node;                     // 成员变量,节点指针
      // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    4.list的模拟实现

    4.1 list的结点结构

    //双向循环链表节点结构:一个数据域,两个指针
    namespace winter
    {
    	// 节点结构
    	// T: 节点中数据的类型
    	template<class T>
    	struct __list_node
    	{
    		T _data;               // 数据域
    		__list_node<T>* _prev; // 前驱指针
    		__list_node<T>* _next; // 后继指针
    
    		// 默认构造函数,T()是缺省值(T类型的默认构造函数)
    		__list_node(const T& data = T())
    			:_data(data)
    			,_prev(nullptr)
    			,_next(nullptr)
    		{}
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.2 list的底层结构

    namespace winter
    {
    	// 带头双向循环链表结构
    	// T: 节点中数据的类型
    	template<class T>
    	class list
    	{
    	public:
    		typedef __list_node<T> Node; // 节点
    
    	public:
    		/*******************************************************/
    		// 迭代器
    		// iterator是内嵌类型,在list类域里面定义的类型(类中类)
    		// 为啥要typedef呢?为了统一规范名称,所有容器迭代器都叫iterator,用起来更方便
    
            // 普通迭代器,指明模板参数为 T, T&, T*
            typedef __list_iterator<T, T&, T*> iterator;
            
            // const迭代器,指明模板参数为 T, const T&, const T*
            typedef __list_iterator<T, const T&, const T*> const_iterator;
    
    		iterator begin(); // 返回指向第一个有效节点的迭代器
    		iterator end();   // 返回指向头节点的迭代器
    		const_iterator begin() const;
    		const_iterator end() const;
    
    	private:
    		Node* _head; // 头节点指针
    
    	public:
    		/*******************************************************/
    		// 默认成员函数
    
    		list(); // 默认构造函数
    		template<class InputIterator>
    		list(InputIterator first, InputIterator last); // 带参构造函数
    		list(const list<T>& lt); // 拷贝构造函数(深拷贝)
    		list<T>& operator=(list<T> lt) // 赋值运算符重载函数(深拷贝)
    		~list(); // 析构函数
    
    		/*******************************************************/
    		// 容量操作
    
    		size_t size(); // 有效元素个数
    		bool empty(); // 判空
    
    		/*******************************************************/
    		// 修改操作
    
    		iterator insert(iterator pos, const T& data); // 指定迭代器位置之前插入元素
    		iterator erase(iterator pos); // 删除指定迭代器位置的元素
    		void push_back(const T& data);  // 尾插
    		void push_front(const T& data); // 头插
    		void pop_back();  // 尾删
    		void pop_front(); // 头删
    		void clear(); // 清空有效元素
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    1. list类域中的__list_iterator__list_iterator是定义在类内部的类(嵌套类型)
    2. 用 typedef 重命名为 iteratorconst_iterator ,目的是为了统一规范迭代器的名称,所有容器的迭代器都叫 iterator 等,用起来更方便。要不然,每个容器都定义迭代器,如果每个容器迭代器的名字都不一样,那用户使用起来成本太高了
    // 普通迭代器,指明模板参数为 T, T&, T*
    typedef __list_iterator<T, T&, T*> iterator;
    
    // const迭代器,指明模板参数为 T, const T&, const T*
    typedef __list_iterator<T, const T&, const T*> const_iterator;
    
    //其中 T 、 T& 、 T* 都是类型参数,当 list 的类型确定了,T 、 T& 、 T* 的类型自动确定,那么 iterator 的类型同时也自动确定了,这看起来就像 iterator和list是共生的一样
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3 list的迭代器

    1.迭代器的结构

    请添加图片描述

    前面学习的 string 和 vector 容器的正向迭代器其实就是原生指针,++ 可以直接走到下一个元素,而 list 的正向迭代器是用一个类,把节点指针封装起来,然后通过实现运算符重载函数,让迭代器具有像指针一样的行为,比如:重载了 *运算符,实现了 *it 解引用返回节点中的存储的数据的引用,重载了 ++ 运算符,实现了 it++ 直接走到下一个元素

    namespace winter
    {
    	/* 
    	* 迭代器 -- 用一个类去封装了节点的指针
    	* T: 节点中数据的类型
    	* Ref: 节点中数据的引用
    	* Ptr: 节点中数据的指针(地址)
    	*/
    	template<class T, class Ref, class Ptr>
    	struct __list_iterator
    	{
    		typedef __list_iterator<T, Ref, Ptr> self; // 自己这个类型
    		typedef __list_node<T> Node; // 节点
    
    		Node* _node; // 节点指针
    
    		// 构造函数
    		__list_iterator(Node* node)  // 用节点指针来构造
    			:_node(node)
    		{}
    		
    		/*******************************************************/
    		// 运算符重载
    
    		// 让迭代器具有像指针一样的行为
    		Ref operator*() const;
    		Ptr operator->() const;
    
    		// 让迭代器可以移动(双向)
    		self& operator++();
    		self operator++(int); // 不能用引用返回
    		self& operator--();
    		self operator--(int); // 不能用引用返回
    
    		// 让两个迭代器可以进行比较
    		// 判断两个迭代器是否相等,其实判断的是节点指针是否相等
    		bool operator==(const self& it);
    		bool operator!=(const self& it);
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    2.迭代器中的运算符重载函数

    1.让迭代器具有像指针一样的行为,重载 *-> 运算符函数:这两个运算符重载函数也是实现 普通迭代器 和 const 迭代器 的关键
    // *it 本质是:it.operator*()
    Ref operator*() const
    {
        // 返回节点中数据(对象)的引用 / const常引用,具体返回什么取决于在list类域中定义迭代器时传给 Ref 的参数是什么
        return _node->_data;
    
        // 我们想要得到的是节点中存储的数据(对象),而不是整个节点(节点中包含数据和2个指针)
    }
    
    // it-> 本质是:it.operator->()
    Ptr operator->() const
    { 
        // 返回节点中数据(对象)的指针 / const常指针,具体返回什么取决于在list类域中定义迭代器时传给 Ptr 的参数是什么
        return &(_node->_data);
    }
    
    //-------------------------------------------------------------------------------------------------
    
    2.让迭代器可以移动(双向),重载 ++ 和 – 运算符函数
    // 这些函数返回的是自己这个类型 __list_iterator 的对象
    
    // ++it 本质是:it.operator++()
    self& operator++()
    {
        _node = _node->_next;
    
        return *this; // 返回++后的迭代器
    }
    
    // it++ 本质是:it.operator++(0)
    self operator++(int) // 不能用引用返回
    {
        self tmp(*this);
        _node = _node->_next;
    
        return tmp; // 返回++前的迭代器
    }
    
    // --it 本质是:it.operator--()
    self& operator--()
    {
        _node = _node->_prev;
    
        return *this; // 返回--后的迭代器
    }
    
    // it-- 本质是:it.operator--(0)
    self operator--(int) // 不能用引用返回
    {
        self tmp(*this);
        _node = _node->_prev;
    
        return tmp; // 返回--前的迭代器
    }
    
    //------------------------------------------------------------------------------------------------
    
    3.让两个迭代器可以进行比较,重载 ==!= 运算符函数
    // 判断两个迭代器是否相等,其实判断的是节点指针是否相等
    bool operator==(const self& it)
    {
        return _node == it._node;
    }
    
    bool operator!=(const self& it)
    {
        return _node != it._node;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    5.list的成员函数

    1.list的构造函数

    // 默认构造函数
    list()
    {
        // 构造头节点,自己指向自己
        _head = new Node;
        _head->_prev = _head;
        _head->_next = _head;
    }
    
    // 用迭代器区间初始化[first,last)
    template<class InputIterator>
    list(InputIterator first, InputIterator last)
        :_head(new Node) // 当前对象是一个正在构造的对象,成员变量是随机值,需要进行初始化
    {
    	// 建立头节点自身的链接
    	_head->_prev = _head;
    	_head->_next = _head;
            
        // 用迭代器遍历元素,尾插到新容器中
    	while (first != last)
    	{
    		push_back(*first);
    		first++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.list的拷贝构造函数

    //拷贝构造函数(深拷贝),传统写法
    // lt2(lt1)
    list(const list<T>& lt)
        :_head(new Node) // 当前对象是一个正在构造的对象,成员变量是随机值,需要进行初始化
    {
        // 建立头节点自身的链接
        _head->_prev = _head;
        _head->_next = _head;
    
        // 把lt中所有节点拷贝插入到新容器中
        for (const auto& e : lt)
        {
        	push_back(e);
        }
    }
    
    // 拷贝构造函数(深拷贝),现代写法
    // lt2(lt1)
    list(const list<T>& lt)
        :_head(new Node) // 当前对象是一个正在构造的对象,成员变量是随机值,需要进行初始化
    {
        // 建立头节点自身的链接
        _head->_prev = _head;
        _head->_next = _head;
    
    	list<T> tmp(lt.begin(), lt.end());
    	std::swap(_head, tmp._head); // 交换两个容器的内容
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3.list的赋值运算符重载函数

    //深拷贝
    //传统写法
    list<T>& operator=(const list<T>& lt)
    {
        if (this != &lt) // 防止自己给自己赋值
        {
            // 清除当前容器所有有效元素
            clear();
    
            // 把lt中所有节点拷贝插入到当前容器中
            for (const auto& e : lt)
            {
                push_back(e);
            }
        }
    
        return *this; // 返回当前容器
    }
    
    //现代写法
    list<T>& operator=(list<T> lt) // 传值传参,拷贝构造出临时对象
    {
        std::swap(_head, lt._head); // 交换两个容器的内容
    
        return *this; // 返回当前容器
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4.list的析构函数

    ~list()
    {
    	//方法一
        Node* cur = _head->_next; // 从第一个有效节点开始删除
    
        while (cur != _head) 
        {
            Node* next = cur->_next; // 先记录下一个节点
    
            delete cur; // 释放当前节点
    
            cur = next; // 遍历下一个节点
        }
    
        delete _head; // 释放头节点
        _head = nullptr;
    	
    
        //方法二:复用 clear 函数的代码
    
        clear();
        delete _head;
        _head = nullptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    6.list与vector的对比

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

    请添加图片描述


    扩展:容器迭代器的了解

    1.容器迭代器分类

    1. 从使用功能角度:正向 / 反向 + const
    2. 从容器底层结构角度:单向迭代器、双向迭代器、随机访问迭代器
      • 单向(Unidirectional Iterator):单链表 forward_list、哈希表 unordered_set/map 迭代器,可以 ++
      • 双向(Bidirectional Iterator):双向链表 list、红黑树 set/map 迭代器,可以 ++ / –
      • 随机(Random Access Iterator):string、vector、deque 迭代器,一般容器的底层都是连续的数组,可以 ++ / – / + / -
      • 单向 <-- 双向 <-- 随机

    迭代器的本质是:在不暴露容器底层实现细节的情况下,提供了一种统一的方式来访问 / 修改容器中存储的数据


    2.容器、迭代器、算法的关系

    请添加图片描述

    我们来看看 stl_iterator源码中对于迭代器的分类:

    请添加图片描述


    3.迭代器的实现方式分析

    迭代器有两种实现方式,具体应根据「容器底层数据结构的实现」来确定,因为每个特定的容器,都有特定的内部内存结构,也就意味着遍历/迭代过程的细节是不一样的

    方法一:原生指针指向的空间物理地址是连续的,比如 string、vector

    请添加图片描述

    方法二:原生指针指向的空间物理地址不连续,比如:list、map/set

    请添加图片描述


    扩展:对于const迭代器的思考

    思考一:如何实现const迭代器

    string 和 vector 的迭代器就是一个原生指针,我们给原生指针加上 const 就可以变成 const 迭代器,比如:

    // T: 容器中存储的数据的类型
    typedef T* iterator;
    typedef const T* const_iterator;
    
    • 1
    • 2
    • 3

    list 的迭代器不是一个原生指针,而是一个类里面封装了原生指针,那如何实现 list 的 const 迭代器呢?

    const 迭代器,顾名思义,就是不能改变的迭代器。是不能通过 * 和 -> 修改指向成员的,所以我们把「operator*()」和「operator->()」函数的返回值改成常引用即可

    const T& operator*() ; // 返回节点中数据(对象)的 const 常引用
    
    • 1

    这样实现也有问题,比如:那我们需要实现一个 __list_const_iterator 的类吗?

    处理方法:在类中重载一个返回值为常引用的 operator*() 函数

    普通写法需要这样,因为 T& operator*();const T& operator*(); 这两个函数只是返回值不同,无法放在一个类里面构成重载

    让我们来看看 SGI 版本 stl_list 源码中是怎么样实现的:

    请添加图片描述


    思考二:迭代器遍历list中结点的方式

    如果迭代器管理的结点中的数据是内置类型

    void test1()
    {
        int arr[] = { 1,2,3,4,5 };
        list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
    
        list<int>::iterator it = lt.begin();
        while (it != lt.end())
        {
            // 对指向当前节点的迭代器解引用 *it 可以得到当前节点中存储的数据
            cout << *it << " "; // it.operator*()
            ++it;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如果迭代器管理的节点中的数据是自定义类型 / 结构体

    struct TreeNode
    {
        int _val;
        struct TreeNode* _left;
        struct TreeNode* _right;
    
        TreeNode(int val = -1)
    		:_val(val)
    		,_left(nullptr)
    		,_right(nullptr)
    	{}
    };
    
    void test2()
    {
        list<TreeNode> lt;
        lt.push_back(TreeNode(1));
        lt.push_back(TreeNode(2));
        lt.push_back(TreeNode(3));
    
        list<TreeNode>::iterator it = lt.begin();
        while (it != lt.end())
        {
            /* 
            * 错误写法:
    		* 对指向当前节点的迭代器解引用 *it 可以得到当前节点中存储的 TreeNode 结构体
    		* TreeNode结构体中存储的数据 _val 是不能直接输出出来的
    		* 除非重载了专门针对输出 TreeNode 结构体中数据的流插入运算符,比如:
    		* ostream& operator<<(ostream& out, const TreeNode node);
    		*/
            // cout << *it << " "; // error!!!
    
            /* 正确写法1:
    		* 对指向当前节点的迭代器解引用 *it 得到当前节点中存储的 TreeNode 结构体
    		* 然后用'.'再去访问 TreeNode 结构体中存储的数据 _val
    		*/
            cout << (*it)._val << " ";
    
            /* 正确写法2:
    		* 调用 operator->() 函数
    		* 迭代器->,返回迭代器指向节点中存储的 TreeNode 结构体的地址(指针):TreeNode*
    		* 然后该指针再使用'->'访问结构体中存储的数据 _val
    		* 代码为:it->->_val,但可读性太差,编译器进行了特殊处理,省略掉了一个箭头
    		* 保持了程序的可读性
    		*/
            // 注意:一般结构体的指针才会使用'->'来访问成员
            // 当迭代器管理的节点中的数据是结构体的时候,就可以用'->'
            cout << it->_val << " "; // 这种写法常用
    
            it++;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    思考三:迭代器( __list_iterator 类)中需要写拷贝构造、赋值重载、析构函数吗?

    不需要,默认生成的就可以,而且也不需要我们去实现,虽然迭代器中封装了节点指针,但节点是归链表管理的,链表 list 中的节点也不需要迭代器去释放,list 自己会去释放


    思考四:vector 和 list 的迭代器哪个更复杂呢?有什么区别呢?

    1. 它们的大小都是一样的(32位下),vector 的迭代器是一个原生指针,大小为 4 字节;list 的迭代器是一个类封装了一个节点指针,大小也是 4 字节,物理上没啥区别
    2. list 的迭代器之所以要定义成自定义类型,是因为原生指针无法直接做到像指针一样的行为,所以需要用自定义的类把原生指针封装起来,然后在类中定义一些运算符重载函数,使得这个自定义类型的对象通过调用这些函数,从而可以做到像指针一样的行为
    3. 我们在物理上可以看到,都是存了一个指针,但在运行逻辑上完全不同
    4. vector 迭代器 ++,是一个指针直接 ++ 到下一个位置;而 list 迭代器 ++,是一个自定义类型的对象调用了 operator++() 函数,在函数内算出下一个位置
      cout << (*it)._val << " ";
  • 相关阅读:
    在windows下持续ping ip,将返回结果及时间记录到文件中
    使用Cent Browser+Aria2+Bilibili Envolved下载b站视频--保姆级安装步骤
    情人节程序员用HTML网页表白【爱心表白】 HTML5七夕情人节表白网页源码 HTML+CSS+JavaScript
    【每日随笔】驾驭人性 ③ ( 胡萝卜 - 用利益让员工离不开你 | 大棒 - 用规则让员工害怕你 | 如何建立制度规则 )
    自己动手从零写桌面操作系统GrapeOS系列教程——9.实模式介绍
    【MYSQL】5.7版本出错 this is incompatible with sql_mode=only_full_group_by
    AJAX学习(一)
    模型评价-期望对数似然和对应的估计量
    远程桌面登录Windows云服务器报错0x112f
    NodeMCU ESP8266 面包板的介绍和使用详解(图文并茂)
  • 原文地址:https://blog.csdn.net/qq_29678157/article/details/127845274