1. list的介绍及使用

  1.1 list的介绍

list的文档介绍

  1.2 list的特点

list是C++标准模板库(STL)中的一个双向链表容器。以下是其主要特点:

  基本特性

  • 双向链表结构:每个元素包含指向前驱和后继节点的指针

  • 非连续存储:元素在内存中不是连续存储的

  • 动态大小:可以根据需要动态增长或缩小

  主要优点

  1. 高效的插入和删除:

    1. 在任何位置插入/删除元素的时间复杂度都是O(1)

    2. 特别适合频繁在中间位置进行插入/删除操作的场景

  2. 不失效的迭代器:

    1. 插入操作不会使现有迭代器失效

    2. 只有删除操作会使指向被删除元素的迭代器失效

  主要缺点

  • 随机访问性能差:访问第n个元素需要O(n)时间

  • 内存开销较大:每个元素需要额外存储前后指针

1.3 list的使用

  1.3.1 listt的构造

构造函数(constructor)

接口说明

list (size_type n, const value_type& val= value_type ())

构造的list中包含n个值为val的 元素

list()

构造空的list

list (const list& x)

拷贝构造函数

list (InputIterator first, InputIterator last)

用[first, last)区间中的元素构造list

  1.3.2 list iterator的使用

list的迭代器可以看做是一个指针,指针指向list中的某个节点

函数声明

接口说明

begin + end

返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器

rbegin + rend

返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置

  【注意】

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

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

  1.3.3 list capacity

函数声明

接口说明

empty

检测list是否为空,是返回true,否则返回false

size

返回list中有效节点的个数

  1.3.4 list element access

函数声明

接口说明

front

返回list的第一个节点中值的引用

back

返回list的最后一个节点中值的引用

  1.3.5 list modifiers

函数声明

接口说明

push_front

在list首元素前插入值为val的元素

pop_front

删除list中第一个元素

push_back

在list尾部插入值为val的元素

pop_back

删除list中最后一个元素

insert

在list position 位置中插入值为val的元素

erase

删除list position位置的元素

swap

交换两个list中的元素

clear

清空list中的有效元素

  1.3.6 list迭代器失效

  list 迭代器失效的情况

  1. 元素被删除时:

    1. 指向被删除元素的迭代器会失效

    2. 其他迭代器仍然有效

  2. 整个 list 被销毁或清空时:

    1. 所有迭代器都会失效

  3. 使用 splice 转移元素时:

    1. 被转移元素的迭代器仍然有效,只是现在指向新容器中的元素

  list 迭代器不会失效的情况

  1. 插入新元素:

    1. 在任何位置插入元素都不会使任何迭代器失效

  2. 合并 lists:

    1. 使用 merge 或 splice 合并 lists 不会使迭代器失效

示例代

#include <iostream>
#include <list>

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

// 改正 
void TestListIterator() 
{ 
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; 
    list<int> l(array, array+sizeof(array)/sizeof(array[0])); 
    auto it = l.begin(); 
    while (it != l.end()) 
    { 
        l.erase(it++); // it = l.erase(it); 
    } 
}

2. list的模拟实现

#pragma once
#include <iostream>
#include <assert.h>

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

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

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

        Node* _node;

        ListIterator(Node* node) : _node(node) {}
        ListIterator() : _node(nullptr) {}

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

        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }
        Self operator++(int)
        {
            Self tmp = *this;
            ++(*this);
            return tmp;
        }

        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }
        Self operator--(int)
        {
            Self tmp = *this;
            --(*this);
            return tmp;
        }

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

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

        // 构造 / 析构
        list() :_head(new Node()), _size(0)
        {
            _head->_next = _head->_prev = _head;
        }

        list(const list& x) : list() // 委托构造
        {
            for (const auto& val : x) push_back(val);
        }

        list& operator=(const list& x)
        {
            if (this != &x)
            {
                list tmp(x);
                swap(tmp);
            }
            return *this;
        }

        ~list()
        {
            clear();
            delete _head;
            _head = nullptr;
        }

        void clear()
        {
            Node* cur = _head->_next;
            while (cur != _head)
            {
                Node* next = cur->_next;
                delete cur;
                cur = next;
            }
            _head->_next = _head->_prev = _head;
            _size = 0;
        }

        void swap(list& x)
        {
            std::swap(_head, x._head);
            std::swap(_size, x._size);
        }

        bool empty() const { return _size == 0; }
        size_t size() const { return _size; }

        // 迭代器
        iterator begin() { return _head->_next; }
        iterator end() { return _head; }
        const_iterator begin() const { return _head->_next; }
        const_iterator end() const { return _head; }

        // 反向迭代器
        iterator rbegin() { return _head->_prev; }
        iterator rend() { return _head; }

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

            newnode->_prev = prev;
            newnode->_next = cur;
            prev->_next = newnode;
            cur->_prev = newnode;

            ++_size;
            return iterator(newnode);
        }

        void insert(iterator pos, size_t n, const T& val)
        {
            size_t count = 0;
            Node* cur = pos._node->_prev;
            Node* next = cur->_next;
            while (n--)
            {
                Node* newnode = new Node(val);
                cur->_next = newnode;
                newnode->_prev = cur;
                cur = newnode;
                ++count;
            }
            cur->_next = next;
            next->_prev = cur;
            _size += count;
        }

        // push / pop
        void push_back(const T& val) { insert(end(), val); }
        void push_front(const T& val) { insert(begin(), val); }

        void pop_back() { if (!empty()) erase(--end()); }
        void pop_front() { if (!empty()) erase(begin()); }

        // front / back
        T& front() { assert(!empty()); return _head->_next->_data; }
        T& back() { assert(!empty()); return _head->_prev->_data; }
        const T& front() const { assert(!empty()); return _head->_next->_data; }
        const T& back() const { assert(!empty()); return _head->_prev->_data; }

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

            prev->_next = next;
            next->_prev = prev;

            delete cur;
            --_size;

            return iterator(next);
        }

        iterator erase(iterator first, iterator last)
        {
            if (first == last) return last;

            Node* first_node = first._node;
            Node* last_node = last._node->_prev;

            Node* before_first = first_node->_prev;
            Node* after_last = last_node->_next;

            before_first->_next = after_last;
            after_last->_prev = before_first;

            Node* cur = first_node;
            while (cur != after_last)
            {
                Node* next_node = cur->_next;
                delete cur;
                cur = next_node;
                --_size;
            }

            return iterator(after_last);
        }

    private:
        Node* _head;
        size_t _size = 0;
    };
}

3. list与vector的对比

vector

list

底

层

结

构

动态顺序表,一段连续空间

带头结点的双向循环链表

随

机

访

问

支持随机访问,访问某个元素效率O(1)

不支持随机访问,访问某个元素效率O(N)

插

入

和

删

除

任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低

任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)

空

间

利

用

率

底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高

底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低

迭

代

器

原生态指针

对原生态指针(节点指针)进行封装

迭

代

器

失

效

在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效

插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响

使

用

场

景

需要高效存储,支持随机访问,不关心插入删除效率

大量插入和删除操作,不关心随机访问

更多推荐