C++笔记---list

1. list的介绍

list其实就是就是我们所熟知的链表(双向循环带头结点),但其是作为STL中的一个类模板而存在。

也就是说,list是可以用来存储任意类型数据的顺序表,既可以是内置类型,也可以是自定义类型,或是STL中的其他容器。

除了底层的实现不同以外,用法与vector基本相同,但不支持随机访问,以及与随机访问有关的接口。

具体以list - C++ Reference为准,本文为该文档的一个简单总结与标注。

2. list的重要接口

以下表格中的函数都不是官方库中的函数原型,而是为了方便学习和理解而进行了简化的。

2.1 默认成员函数

2.1.1 构造函数

|------------------------------------------------------------------------------------|------------------|
| (1) list(); | 默认构造 |
| (2) list(size_t n, const T& val = T()); | 用val初始化list前n个数据 |
| (3) template<class InputIterator> list(InputIterator first, InputIterator last); | 用迭代器区间进行初始化 |
| (4) list(const list& x); | 拷贝构造 |
[四种构造方式]

2.1.2 赋值重载

2.2 迭代器相关

|---------|--------------------|
| begin | 返回开始位置的迭代器 |
| end | 返回最后一个数据的下一个位置的迭代器 |
| rbegin | 用于逆向迭代 |
| rend | 用于逆向迭代 |
| cbegin | 用于const修饰的容器的迭代 |
| cend | 用于const修饰的容器的迭代 |
| crbegin | 用于const修饰的容器的逆向迭代 |
| crend | 用于const修饰的容器的逆向迭代 |

2.3 大小容量相关

|--------------------------|---------------------------------------|
| bool empty() const; | 判断list是否为空 |
| size_t size() const; | 返回list中的数据个数 |
| size_t max_size() const; | 返回由于系统或数据库限制,list能够存储数据的最大容量(并不一定能达到) |

2.4 访问相关

|----------------------------------------------|-------------|
| (1) T& front(); (2) constT& front() const; | 返回第一个元素的引用 |
| (1) T& back(); (2) constT& back() const; | 返回最后一个元素的引用 |

2.5 元素修改相关

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
| (1) template<class InputIterator> void assign(InputIterator first, InputIterator last); (2) void assign(size_t n, const T& val); | 给list赋新的值,效果类似于重新构造这个list |
| void push_front(const T& val); | 在list头部插入一个元素 |
| void push_back(const T& val); | 在list尾部插入一个元素 |
| void pop_front(); | 删除list头部的一个元素 |
| void pop_back(); | 删除list尾部的一个元素 |
| (1) iterator insert (iterator pos, const T& val); (2) void insert (iterator pos, size_t n, const T& val); (3) template <class InputIterator> void insert(iterator position, InputIterator first,InputIterator last); | 在指定位置插入元素,常数时间O(1),效率很高,不会导致迭代器失效 |
| (1) iterator erase(iterator pos); (2) iterator erase(iterator first, iterator last); | 删除指定位置的数据,常数时间O(1),效率很高,会导致当前位置迭代器失效 |
| void swap(list<T>& x); | 交换两个list的数据 |
| void resize(size_t n, T val = T()); | 改变list的元素个数,使其容纳n个元素,n<size则删除多余的,n>size则加入n个值与val相同的元素 |
| void clear(); | 清空list |

2.6 list结点移动

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| (1) void splice(iterator position, list& x); (2) void splice(iterator position, list& x, iterator i); (3) void splice(iterator position, list& x, iterator first, iterator last); | (1) 将x的结点全部移动到position位置 (2) 将x中i指向的结点移动到position位置 (3) 将x中first到last的结点移动到position位置 |
| void remove(const T& val); | 移除list中与val值相同的结点 |
| template <class Predicate> void remove_if(Predicate pred); | 按照pred(*it)函数的返回值(true移除/false不移除)来移除结点 |
| (1) void unique(); (2) template <class BinaryPredicate> void unique(BinaryPredicate binary_pred); | (1) 移除值连续相等(operator==)的几个结点,只留下这组结点中的第一个(在处理经过排序的list时,能确保各种值得结点只留下一个) (2) 按照binary_pred(*it, *(it-1))给出的逻辑判断结点的值是否相等,进而删除结点 |
| (1) void merge(list& x); (2) template <class Compare> void merge(list& x, Compare comp); | (1) 将x合并到调用函数的list中(x的结点全部移动到list中,并确保合并后的list中的结点也是有序(operator<)的,但要求两个list事先都是有序的。如果list==*this,该函数无行为) (2) 按照comp函数给出的比较方式来进行有序的合并 |
| (1) void sort(); (2) template <class Compare> void sort(Compare comp); | (1) 按照operator<进行排序 (2) 按照comp给出的比较方式排序 |
| void reverse(); | 逆置list |

3. 迭代器失效

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。

因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

4. list不完全模拟实现示例

cpp 复制代码
#pragma once
#include<iostream>
using namespace std;

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


    //List的迭代器类
    template<class T, class Ref, class Ptr>
    struct ListIterator
    {
        typedef ListNode<T>* PNode;
        typedef ListIterator<T, Ref, Ptr> Self;
        typedef Ref _Ref;
        typedef Ptr _Ptr;
    public:
        ListIterator(PNode pNode = nullptr)
            :_pNode(pNode)
        {}
        ListIterator(const Self& l)
            :_pNode(l._pNode)
        {}

        T& operator*()
        {
            return _pNode->_val;
        }
        // 按理来说在使用时需要两个->,但编译器为了可读性做了优化
        T* operator->()
        {
            return &(_pNode->_val);
        }
        Self& operator++()
        {
            _pNode = _pNode->_pNext;
            return (*this);
        }
        Self operator++(int)
        {
            Self tmp = (*this);
            _pNode = _pNode->_pNext;
            return tmp;
        }
        Self& operator--()
        {
            _pNode = _pNode->_pPre;
            return (*this);
        }
        Self& operator--(int)
        {
            Self tmp = (*this);
            _pNode = _pNode->_pPre;
            return tmp;
        }
        bool operator!=(const Self& l) const
        {
            return (this->_pNode != l._pNode);
        }
        bool operator==(const Self& l) const
        {
            return (this->_pNode == l._pNode);
        }

        PNode _pNode;
    };

    template<class iterator>
    struct reverseListIterator
    {
        typedef typename iterator::_Ref Ref;
        typedef typename iterator::_Ptr Ptr;
        typedef reverseListIterator<iterator> Self;

        reverseListIterator(iterator it)
            :_it(it)
        {}

        Ref operator*()
        {
            iterator tmp(_it);
            --tmp;
            return *tmp;
        }
        Ptr operator->()
        {
            return &(operator*());
        }
        Self& operator++()
        {
            --_it;
            return *this;
        }
        Self& operator++(int)
        {
            Self tmp(*this);
            --_it;
            return tmp;
        }
        Self& operator--()
        {
            ++_it;
            return *this;
        }
        Self& operator--(int)
        {
            Self tmp(*this);
            ++_it;
            return tmp;
        }

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

        iterator _it;
    };


    //list类
    template<class T>
    class list
    {
        typedef ListNode<T> Node;
        typedef Node* PNode;
    public:
        typedef ListIterator<T, T&, T*> iterator;
        typedef ListIterator<T, const T&, const T&> const_iterator;
        typedef reverseListIterator<iterator> reverse_iterator;
        typedef reverseListIterator<const_iterator> const_reverse_iterator;
    public:
        ///
        // List的构造
        list()
        {
            CreateHead();
        }

        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;
            }
        }
        list(const list<T>& l)
        {
            CreateHead();
            list tmp(l.begin(), l.end());
            swap(tmp);
        }
        // 支持用大括号参数列表构造/隐式类型转换
        list(initializer_list<T> il)
        {
            CreateHead();
            for (auto& e : il)
            {
                push_back(e);
            }
        }
        list<T>& operator=(const list<T> l)
        {
            list tmp(l.begin(), l.end());
            swap(tmp);
            return *this;
        }
        ~list()
        {
            clear();
            delete _pHead;
            _pHead = nullptr;
        }


        ///
        // List 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);
        }
        const_iterator cbegin() const
        {
            return const_iterator(_pHead->_pNext);
        }
        const_iterator cend() const
        {
            return const_iterator(_pHead);
        }

        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());
        }


        ///
        // List Capacity
        size_t size()const
        {
            return _size;
        }
        bool empty()const
        {
            return !_size;
        }


        
        // List Access
        T& front()
        {
            return _pHead->_pNext->_val;
        }
        const T& front()const
        {
            return _pHead->_pNext->_val;
        }
        T& back()
        {
            return _pHead->_pPre->_val;
        }
        const T& back()const
        {
            return _pHead->_pPre->_val;
        }


        
        // List Modify
        void push_back(const T& val) { insert(end(), val); }
        void pop_back() { erase(--end()); }
        void push_front(const T& val) { insert(begin(), val); }
        void pop_front() { erase(begin()); }
        // 在pos位置前插入值为val的节点
        iterator insert(iterator pos, const T& val)
        {
            PNode newnode = new Node(val);
            newnode->_pNext = pos._pNode;
            newnode->_pPre = pos._pNode->_pPre;
            pos._pNode->_pPre->_pNext = newnode;
            pos._pNode->_pPre = newnode;
            _size++;
            return pos;
        }
        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos)
        {
            pos._pNode->_pNext->_pPre = pos._pNode->_pPre;
            pos._pNode->_pPre->_pNext = pos._pNode->_pNext;
            iterator ret = pos._pNode->_pNext;
            delete pos._pNode;
            --_size;
            return ret;
        }
        void clear()
        {
            PNode cur = _pHead->_pNext->_pNext;
            while (cur != _pHead->_pNext)
            {
                delete cur->_pPre;
                cur = cur->_pNext;
            }
            _pHead->_pNext = _pHead;
            _pHead->_pPre = _pHead;
            _size = 0;
        }
        void swap(list<T>& l)
        {
            std::swap(_pHead, l._pHead);
            std::swap(_size, l._size);
        }
    private:
        void CreateHead()
        {
            _pHead = new Node;
            _pHead->_pNext = _pHead;
            _pHead->_pPre = _pHead;
        }
        PNode _pHead;
        size_t _size = 0;
    };
};

5. list的迭代器

由于底层结构的复杂性,list的迭代器不再像string和vector那样可以直接由指针代劳。

我们依然将结点指针作为list迭代器的底层,但是各操作符原本的逻辑已经无法满足我们的需要,均需要进行重载。

于是,我们用ListIterator类对结点的指针进行了包装,并对所需的操作符进行了相应的重载。

cpp 复制代码
//List的迭代器类
template<class T, class Ref, class Ptr>
struct ListIterator
{
    typedef ListNode<T>* PNode;
    typedef ListIterator<T, Ref, Ptr> Self;
    typedef Ref _Ref;
    typedef Ptr _Ptr;
public:
    ListIterator(PNode pNode = nullptr)
        :_pNode(pNode)
    {}
    ListIterator(const Self& l)
        :_pNode(l._pNode)
    {}

    T& operator*()
    {
        return _pNode->_val;
    }
    // 按理来说在使用时需要两个->,但编译器为了可读性做了优化
    T* operator->()
    {
        return &(_pNode->_val);
    }
    Self& operator++()
    {
        _pNode = _pNode->_pNext;
        return (*this);
    }
    Self operator++(int)
    {
        Self tmp = (*this);
        _pNode = _pNode->_pNext;
        return tmp;
    }
    Self& operator--()
    {
        _pNode = _pNode->_pPre;
        return (*this);
    }
    Self& operator--(int)
    {
        Self tmp = (*this);
        _pNode = _pNode->_pPre;
        return tmp;
    }
    bool operator!=(const Self& l) const
    {
        return (this->_pNode != l._pNode);
    }
    bool operator==(const Self& l) const
    {
        return (this->_pNode == l._pNode);
    }

    PNode _pNode;
};

其中Ref代表T的引用,Ptr代表T的指针,根据这两个参数是否被const修饰,我们可以实例化出普通的迭代器和const迭代器:

cpp 复制代码
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T&> const_iterator;

6. list反向迭代器

与正向迭代器相比,反向迭代器只是在进行++或--的行为与其不同。

我们可以采用适配器模式(stack和queue也是采用该种模式对其他容器进行包装)来实现反向迭代器:

用迭代器作为反向迭代器的底层,通过对正向迭代器的接口进行包装,使其行为满足我们的需求。

cpp 复制代码
template<class iterator>
struct reverseListIterator
{
    typedef typename iterator::_Ref Ref;
    typedef typename iterator::_Ptr Ptr;
    typedef reverseListIterator<iterator> Self;

    reverseListIterator(iterator it)
        :_it(it)
    {}

    Ref operator*()
    {
        iterator tmp(_it);
        --tmp;
        return *tmp;
    }
    Ptr operator->()
    {
        return &(operator*());
    }
    Self& operator++()
    {
        --_it;
        return *this;
    }
    Self& operator++(int)
    {
        Self tmp(*this);
        --_it;
        return tmp;
    }
    Self& operator--()
    {
        ++_it;
        return *this;
    }
    Self& operator--(int)
    {
        Self tmp(*this);
        ++_it;
        return tmp;
    }

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

    iterator _it;
};

同理,我们可以定义出普通反向迭代器和const反向迭代器:

cpp 复制代码
typedef reverseListIterator<iterator> reverse_iterator;
typedef reverseListIterator<const_iterator> const_reverse_iterator;

7. list和vector的区别

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

相关推荐
Gu Gu Study5 分钟前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
时光の尘20 分钟前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
我们的五年24 分钟前
【Linux课程学习】:进程描述---PCB(Process Control Block)
linux·运维·c++
-一杯为品-29 分钟前
【51单片机】程序实验5&6.独立按键-矩阵按键
c语言·笔记·学习·51单片机·硬件工程
以后不吃煲仔饭34 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师35 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
前端拾光者39 分钟前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
程序猿阿伟40 分钟前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链
傻啦嘿哟1 小时前
如何使用 Python 开发一个简单的文本数据转换为 Excel 工具
开发语言·python·excel
大数据编程之光1 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink