【STL】之 list 使用方法和模拟实现

目录

前言:

list是什么?

节点类

迭代器类:

list类

list的迭代器失效问题


前言:

之前我们分别手撕了string类和vector类,今天我们来跟list类打打交道~

list是什么?

通过查c++文档可知,list也是一个模板类,我们主要利用他进行数据的插入和删除操作,并且与vector不同的是,list的插入和删除操作用到的时间复杂度是O(1),而对于vector类的头插或者头删需要O(N)的时间复杂度,接下来让我们探索list是如何实现以及应用的吧!

通过查询文档可知,list的底层使用带头双向循环列表进行实现的,这样才能做到在任意位置删除和插入的时间复杂度都是O(1)。

节点类

因为是链表,我们需要先定义一个节点类,用来存储节点的相关信息。

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

注意这里的节点类以及下面的迭代器类都是用struct实现的,因为struct类默认成员都是public类型,方便我们在list类中进行操作,不然还要用到友元函数,减少不必要的麻烦。


迭代器类:

下面我们来实现list的迭代器类

我们之前实现的string和vector的迭代器都是原生指针,直接typedef指针即可,因为前者的底层存储空间是连续的,这样我们在使用迭代器进行遍历时,可以直接用指针++即可。

但是list类不同,list底层实现是用一个一个节点组成,是我们自定义类型实现,没有办法保证地址连续,因此迭代器直接++就无用武之地了。

因此我们要将Node*进行运算符重载,但是Node*本身是一个指针,只有自定义类型才能用运算符重载,因此我们需要一个类将Node*封装起来,然后对Node*进行运算符的重载~

//List的迭代器类
    template<class T, class Ref, class Ptr>
    struct ListIterator
    {
        typedef ListNode<T>* PNode;
        typedef ListIterator<T, Ref, Ptr> Self;
    
        PNode _pNode;

        ListIterator(PNode pNode = nullptr)
            :_pNode(pNode)
        {
           //_pNode = pNode;
        }
        //ListIterator(const Self& l)
        //{
        //    _pNode = l;
        //}
        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)
        {
            return l._pNode != _pNode;
        }
        bool operator==(const Self& l)
        {
            return l._pNode == _pNode;
        }
    };

首先这里的迭代器存在一个很严重的问题:如果是一个const对象无法调用这个迭代器!而你可能会想说在创造一个const版本的迭代器类,这样固然是可以,但是这样会使得代码冗余!

public:
        typedef ListIterator<T, T&, T*> iterator;
        typedef ListIterator<T, const T&, const T*> const_iterator;

这里就需要模板出手了!STL里面利用了三个模板参数,T, Ref,Ptr。而Ref,Ptr,分别就是引用和指针。所以当我们传递的是非const的迭代器,编译器就会匹配非const的,反之const就会匹配const。这就是大佬设计的独到之处~

const_iterator自己可以修改,不是const对象,但是指向的内容不能修改


list类

在实现了迭代器之后,我们就可以正式手撕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;
        ///
        // List的构造
        list()
        {
            CreateHead();
        }
        list(int n, const T& value = T());
        template <class Iterator>
        list(Iterator first, Iterator last);
        
        //拷贝构造
        list(const list<T>& l)
        {
            CreateHead();
            for (const auto i : l)
            {
                push_back(i);
            }
        }

        //赋值重载
        list<T>& operator=(const list<T> l)
        {
            swap(l);
            return *this;
        }


        ~list()
        {
            clear();
            delete _pHead;
            //注意delete之后,要将指针赋值为空指针
            _pHead = nullptr;
        }


        ///
        // List Iterator
        iterator begin()
        {
            return _pHead->_pNext;
        }
        iterator end()
        {
            return _pHead;
        }
        const_iterator begin()const
        {
            return _pHead->_pNext;
        }
        const_iterator end()const
        {
            return _pHead;
        }


        ///
        // List Capacity
        size_t size()const
        {
            size_t num = 0;
            const_iterator it = begin();
            while (it != end())
            {
                it++;
                num++;
            }
            return num;
        }

        //判断是否为空
        bool empty()const
        {
            return size() == 0;
        }

        
        // 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);
            PNode prev = pos._pNode->_pPre;
            PNode cur = pos._pNode;

            prev->_pNext = newnode;
            newnode->_pPre = prev;
            newnode->_pNext = cur;
            cur->_pPre = newnode;

            //return iterator(newnode);
            //单参数的构造函数可以隐式类型转换
            return newnode;
        }


        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos)
        {
            //assert(pos != end());

            PNode cur = pos._pNode;
            PNode next = cur->_pNext;
            PNode prev = cur->_pPre;

            prev->_pNext = next;
            next->_pPre = prev;

            delete cur;
            cur = nullptr;

            return next;
        }

        void clear()
        {
            iterator it = begin();
            while (it != end())
            {
                it = erase(it);
            }
        }
        void swap(list<T>& l)
        {
            std::swap(l->_pHead, _pHead);
        }
    private:
        void CreateHead()
        {
            _pHead = new Node;
            _pHead->_pNext = _pHead;
            _pHead->_pPre = _pHead;
        }
        PNode _pHead;
    };

注意在实现的时候,可以多复用之前写过的代码,比如前插、前删、尾插、尾删就可以复用insert和erase函数。

拷贝构造函数也可以用push_back函数复用,析构函数使用erase复用~

list的迭代器失效问题

对于insert而言,因为insert方法仅仅只是改变了指针的指向,所以本质pos指向的那个节点的绝对地址并不会随着insert而改变,所以insert不会导致迭代器失效。

反而是erase方法反而因为释放了原来的空间导致出现野指针失效 而和vector的处理方式一致,erase方法也是返回指向被删除元素的下一个位置元素的迭代器。

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以给您讲解一下如何模拟STLlist类。 List是一个双向链表,我们可以通过自己实现节点类和链表类来模拟这个数据结构。 首先,我们需要定义一个节点类,包含前驱节点指针、后继节点指针、以及存储数据的成员变量。代码如下: ``` template <typename T> class ListNode { public: T data; ListNode* prev; ListNode* next; ListNode(T data) : data(data), prev(nullptr), next(nullptr) {} }; ``` 接着,我们定义链表类,包含头节点指针和尾节点指针,以及一些常用的操作方法,如插入、删除、查找等。代码如下: ``` template <typename T> class List { public: List() : head(nullptr), tail(nullptr), size(0) {} ~List() { clear(); } void insert(T data) { ListNode<T>* node = new ListNode<T>(data); if (head == nullptr) { head = node; tail = node; } else { tail->next = node; node->prev = tail; tail = node; } size++; } void remove(T data) { ListNode<T>* node = head; while (node != nullptr) { if (node->data == data) { if (node == head) { head = node->next; if (head != nullptr) { head->prev = nullptr; } } else if (node == tail) { tail = node->prev; if (tail != nullptr) { tail->next = nullptr; } } else { node->prev->next = node->next; node->next->prev = node->prev; } delete node; size--; return; } node = node->next; } } ListNode<T>* find(T data) { ListNode<T>* node = head; while (node != nullptr) { if (node->data == data) { return node; } node = node->next; } return nullptr; } void clear() { ListNode<T>* node = head; while (node != nullptr) { ListNode<T>* next = node->next; delete node; node = next; } head = nullptr; tail = nullptr; size = 0; } int getSize() const { return size; } private: ListNode<T>* head; ListNode<T>* tail; int size; }; ``` 这样,我们就实现了一个简单的模拟STLlist类。您可以通过调用insert、remove、find等方法来操作链表中的元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可涵不会debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值