C++_list的实现

该文详细介绍了如何使用C++模板类实现一个带有哨兵节点的双向链表,包括节点结构、构造函数、拷贝构造、赋值构造、迭代器的定义及操作符重载,以及插入和删除等操作。文章强调了迭代器在链表操作中的重要性,并提供了测试示例。
摘要由CSDN通过智能技术生成

类的成员变量

类的私有成员变量只要一个哨兵位节点_head

template<class T>
    class list
    {
        typedef list_node<T> node;//方便写代码
    public:
		}

为了方便写代码,我们把list_node重命名为node

template<class T>
    struct list_node//struct 不打算对成员进行限制
    {
        //node的初始化
        T _data;
        list_node<T>* _next;
        list_node<T>* _prev;

        list_node(const T& x=T())
        :_next(nullptr)
        ,_prev(nullptr)
        ,_data(x)
        {}
    };

使用struct定义list_node是为了不对成员进行限制,外部可以修改它。

把node封装成struct后,当对象实例化时,会调用list_node这个结构体中的构造函数,并将node型对象初始化,这样初始化会首先创建一个哨兵位节点:_head。

基本函数

swap

void swap(list<T>& tmp)
        {
            std::swap(_head, tmp._head);
        }

链表的交换很方便,直接交换头节点即可。

封装初始化:

void empty_init()
        {
            _head=new node;
            _head->_next=_head;
            _head->_prev=_head;
        }

把构造初始化封装起来,让多个构造函数可以使用,减少代码量。

构造初始化、拷贝构造

无参构造:

list()
        {
						empty_init();
         // _head=new node;
         // _head->_next=_head;
         // _head->_prev=_head;
        }

_head的初始化直接调用list_node)。

其他拷贝构造都需要这三行,封装起来后可以节省代码量。

迭代器遍历拷贝构造:

template <class Iterator>
list(Iterator first, Iterator last)
        {
            empty_init();//初始化
            while (first != last)
            {
                push_back(*first);//依次插入迭代器中的值
                ++first;
            }
        }

迭代器传入后,依次遍历迭代器中的值并尾插到this中,即可完成迭代器拷贝构造。

lt2(lt1)拷贝构造:

list(const list<T>& lt)//拷贝构造
        {
            empty_init();
            list<T> tmp(lt.begin(), lt.end());//用迭代器拷贝构造后再交换值
            swap(tmp);
        }

复用拷贝构造后this和tmp交换头节点指针即可。

赋值重载拷贝构造:

list<T>& operator=(list<T> lt)//赋值拷贝构造
        {
            swap(lt);
            return *this;
        }

传入lt然后this和它交换,再返回*this即可。

注意:传入的list类型是形参,如果传入引用,那lt就会被交换掉,不是拷贝构造了,是交换!

迭代器的实现

首先在类中定义一个迭代器中需要什么类型的值并重命名,如下代码所示

template<class T>
    class list
    {
        typedef list_node<T> node;//方便写代码
    public:
        typedef __list_iterator<T,T&,T*> iterator;//迭代器
        typedef __list_iterator<T,const T&,const T*> const_iterator;//const迭代器
		}

对象实例化时,把类型传入模版中,例如int型,那么传入迭代器中的三种类型分别为:int,int&,int*

const迭代器亦是如此:int , const int& , constT*

在类外定义迭代器:

template<class T,class Ref,class Ptr>
    struct __list_iterator
    {
        typedef list_node<T> node;//局部重命名
        typedef __list_iterator<T,Ref,Ptr> self;
        node* _node;

        __list_iterator(node* n)//n传的是一个去掉哨兵位head的头节点指针
        :_node(n)//_node被赋值的是头节点指针,此时_node就是一个没有哨兵位节点的链表!
        {}
    };

当迭代器被构造时,传入的node* n是一个指针,且应该是一个不带哨兵位节点的指针,因为迭代器中的值应当都有效,哨兵位中数据无效。

例如:迭代器中传入的是_head→next;那么迭代器指向的就是第一个节点。

Tip:迭代器不释放节点,释放是list的事情,迭代器只是使用节点为用户读写

迭代器基本函数:

				iterator begin()
        {
            return iterator(_head->_next);
        }
        iterator end()
        {
            return iterator(_head);
        }
        const_iterator begin() const
        {
            return const_iterator(_head->_next);
        }
        const_iterator end() const
        {
            return const_iterator(_head);
        }

使用匿名对象返回值编译器更容易优化,begin()迭代器指向的是第一个有效节点,即_head->next(哨兵位无效)。

把哨兵位节点_head当作尾节点,当begin()迭代器遍历到_head(即end()迭代器)时,可以判断此链表已经结束迭代。

Tip:哨兵位相当于string中的npos,用来判断双向循环链表的结束。

迭代器的操作符重载:

				Ref operator*()//解引用,把节点中的值放出来
        {
            return _node->_data;//值是模版类型
        }

        Ptr operator->()//返回的是T类型指针,T*
        {
            return &_node->_data;
        }
        self& operator++()
        {
            _node=_node->_next;//迭代器往后走
            return *this;//直接返回this,此时this->node的头节点已经变更
        }
        self operator++(int)//后置++括号中写int
        {
            self tmp(*this);//保存之前的节点
            _node=_node->_next;//迭代器往后走
            return tmp;//后置++特性就是先用后加
        }
        self& operator--()
        {
            _node=_node->_prev;//迭代器往前走
            return *this;//直接返回this,此时this->node的头节点已经变更
        }
        self operator--(int)//后置--括号中写int
        {
            self tmp(*this);//保存之前的节点
            _node=_node->_prev;//迭代器往前走
            return tmp;//后置--特性就是先用后减
        }

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

如上代码注释,除了后置++和后置- -其他没什么好说的,都是基本的重载。

后置++和后置- -都是先用再加,所以返回的值应该是原本的值,先用tmp把原本的节点存起来,迭代器再往后走,返回tmp。

->指向重载:

Ptr operator->()//返回的是T类型指针,T*
        {
            return &_node->_data;
        }

		struct AA
    {
        int _a1;
        int _a2;

        AA(int a1 = 0, int a2 = 0)
                :_a1(a1)
                , _a2(a2)
        {}
    };

    void test2()
    {
        list<AA> lt;
        lt.push_back(AA(1, 1));
        lt.push_back(AA(2, 2));
        lt.push_back(AA(3, 3));

        // AA* ptr
        list<AA>::iterator it = lt.begin();
        while (it != lt.end())
        {
            //cout << (*it)._a1 << ":" << (*it)._a2 << endl;
            cout << it->_a1 << ":" << it->_a2 << endl;//为了可读性,省略了一个->
            //cout << it.operator->()->_a1 << ":" << it.operator->()->_a1 << endl;
            ++it;
        }
        cout << endl;
    }

如上所示,当一个结构体中有两个值,我们需要访问时,需要用指向->来访问地址中的数据,所以需要重载->,为了可读性,可以省略一个->来指向访问。

反向迭代器

namespace lty
{
    template<class Iterator, class Ref, class Ptr>
    struct ReverseIterator
    {
        typedef ReverseIterator<Iterator, Ref, Ptr> Self;
        Iterator _cur;

        ReverseIterator(Iterator it)
                :_cur(it)
        {}

        Ref operator*()
        {
            Iterator tmp = _cur;
            --tmp;
            return *tmp;
        }

        Self& operator++()//反向迭代器的++就是--,--就是++
        {
            --_cur;
            return *this;
        }

        Self& operator--()
        {
            ++_cur;
            return *this;
        }

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

复用正向迭代器,当迭代器进行自增/自减操作时,调用的是正向迭代器的自减/自增,和正向迭代器是反着来的。

解引用时,因为反向迭代器是从end()开始的rbegin(),当需要解引用时,应当取前面那个值:

在这里插入图片描述

插入和删除

insert插入:

void insert(iterator pos, const T& x)
        {
            //pos迭代器里面的_node是指针,例如begin()传入pos,那么_node被初始化为_head->next,pos._node=_head->next。
            node* cur = pos._node;  //定义cur头指向pos位置的节点
            node* prev = cur->_prev;//prev指向pos前一个节点

            node* new_node = new node(x);
           
            prev->_next = new_node;//链上新节点
            new_node->_prev = prev;//新节点前一个节点为之前的尾节点
            new_node->_next = cur;//尾后链头
            cur->_prev = new_node;//头前链尾
        }

在pos位置插入即是头。

参数pos迭代器里面存的_node是指针,pos也是一个指针,所以节点的引用是pos._node

Tip:insert操作时迭代器不会失效,依旧指向的是原来的节点

双向链表的插入如图所示:

在这里插入图片描述

erase删除:

iterator erase(iterator pos)
        {
            assert(pos != end());//不能删除哨兵位节点,需断言

            node* prev = pos._node->_prev;
            node* next = pos._node->_next;

            prev->_next = next;
            next->_prev = prev;
            delete pos._node;//迭代器失效
            return iterator(next);//防止外部迭代器失效
        }

首先断言链表是否为空,为空时无法删除,且哨兵位节点不能被删除。

迭代器失效:当重新链完节点后,删除pos位置的节点,此时迭代器失效,被delete掉了。

erase函数可以返回一个迭代器防止外部迭代器失效:即返回pos位置的下一个节点。

头插删尾插删:

				void push_back(const T& x)
        {
            insert(end(),x);//尾插复用insert,在迭代器end处插入x
        }

        void push_front(const T& x)
        {
            insert(begin(),x);//头插在begin处插入x
        }

        void pop_front()
        {
            erase(begin());//复用头删
        }

        void pop_back()
        {
            erase(--end());//复用erase,尾删,不能删end位置的哨兵位节点。
        }

完成insert和erase后,链表的头插删和尾插删复用它们即可。

头文件源码:

#include <assert.h>
namespace lty
{
    template<class T>
    struct list_node//struct 不打算对成员进行限制
    {
        //node的初始化
        T _data;
        list_node<T>* _next;
        list_node<T>* _prev;

        list_node(const T& x=T())
        :_next(nullptr)
        ,_prev(nullptr)
        ,_data(x)
        {}
    };

    template<class T,class Ref,class Ptr>
    struct __list_iterator//迭代器不释放节点,释放是链表list的事情,迭代器只是使用节点为用户读写
    {
        typedef list_node<T> node;//局部重命名
        typedef __list_iterator<T,Ref,Ptr> self;
        node* _node;

        __list_iterator(node* n)//n传的是一个去掉哨兵位head的头节点指针
        :_node(n)//_node被赋值的是头节点指针,此时_node就是一个没有哨兵位节点的链表!
        {}

        Ref operator*()//解引用,把节点中的值放出来
        {
            return _node->_data;//值是模版类型
        }

        Ptr operator->()//返回的是T类型指针,T*
        {
            return &_node->_data;
        }
        self& operator++()
        {
            _node=_node->_next;//迭代器往后走
            return *this;//直接返回this,此时this->node的头节点已经变更
        }
        self operator++(int)//后置++括号中写int
        {
            self tmp(*this);//保存之前的节点
            _node=_node->_next;//迭代器往后走
            return tmp;//后置++特性就是先用后加
        }
        self& operator--()
        {
            _node=_node->_prev;//迭代器往前走
            return *this;//直接返回this,此时this->node的头节点已经变更
        }
        self operator--(int)//后置--括号中写int
        {
            self tmp(*this);//保存之前的节点
            _node=_node->_prev;//迭代器往前走
            return tmp;//后置--特性就是先用后减
        }


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


    };


    template<class T>
    class list
    {
        typedef list_node<T> node;//方便写代码
    public:
        typedef __list_iterator<T,T&,T*> iterator;//迭代器
        typedef __list_iterator<T,const T&,const T*> const_iterator;//const迭代器

        iterator begin()
        {
            return iterator(_head->_next);//匿名对象编译器更容易优化,传入_head->next后迭代器中的链表没有哨兵位节点了
        }
        iterator end()
        {
            return iterator(_head);//把_head当作尾节点,当begin迭代器++到_head时,可以判断此链表已经结束迭代
        }
        const_iterator begin() const
        {
            return const_iterator(_head->_next);
        }
        const_iterator end() const
        {
            return const_iterator(_head);
        }

        void empty_init()//构造初始化 封装起来 让多个构造可以使用
        {
            _head=new node;
            _head->_next=_head;
            _head->_prev=_head;
        }

        void swap(list<T>& tmp)//交换头节点即可
        {
            std::swap(_head, tmp._head);
        }

        list()
        {
//            empty_init();
            _head=new node;
            _head->_next=_head;
            _head->_prev=_head;
        }

        template <class Iterator>
        list(Iterator first, Iterator last)//用迭代器拷贝构造
        {
            empty_init();//初始化
            while (first != last)
            {
                push_back(*first);//依次插入迭代器中的值
                ++first;
            }
        }

        //lt2(lt1);
        list(const list<T>& lt)//拷贝构造
        {
            empty_init();
            list<T> tmp(lt.begin(), lt.end());//用迭代器拷贝构造后再交换值
            swap(tmp);
        }

        list<T>& operator=(list<T> lt)//赋值拷贝构造
        {
            swap(lt);
            return *this;
        }
        void push_back(const T& x)
        {
//            node* tail=_head->_prev;
//            node* new_node=new node(x);
//
//            tail->_next=new_node;//链上新节点
//            new_node->_prev=tail;//新节点前一个节点为之前的尾节点
//            //双向循环链表
//            new_node->_next=_head;//尾后链头
//            _head->_prev=new_node;//头前链尾
            insert(end(),x);//尾插复用insert,在迭代器end处插入x
        }

        void push_front(const T& x)
        {
            insert(begin(),x);//头插在begin处插入x
        }

        void pop_front()
        {
            erase(begin());//复用头删
        }

        void pop_back()
        {
            erase(--end());//复用erase,尾删,不能删end位置的哨兵位节点。
        }

        //在pos位置插入=头插
        void insert(iterator pos, const T& x)//迭代器不会失效
        {
            //pos迭代器里面的_node是指针,例如begin()传入pos,那么_node被初始化为_head->next,pos._node=_head->next。
            node* cur = pos._node;  //定义cur头指向pos位置的节点
            node* prev = cur->_prev;//prev指向pos前一个节点

            node* new_node = new node(x);
            //画图理解
            prev->_next = new_node;//链上新节点
            new_node->_prev = prev;//新节点前一个节点为之前的尾节点
            new_node->_next = cur;//尾后链头
            cur->_prev = new_node;//头前链尾
        }
        //画图理解
        iterator erase(iterator pos)//此处的pos迭代器会失效,被delete掉了
        {
            assert(pos != end());//不能删除哨兵位节点,需断言

            node* prev = pos._node->_prev;
            node* next = pos._node->_next;

            prev->_next = next;
            next->_prev = prev;
            delete pos._node;//迭代器失效
            return iterator(next);//防止外部迭代器失效
        }

        ~list()
        {
            clear();//除了哨兵位节点没删其余都删了
            delete _head;//删除哨兵位节点并置空
            _head= nullptr;
        }

        void clear()
        {
            iterator it=begin();
            while(it!=end())
            {
                //it= erase(it);//删除一个后,erase返回下一个节点给it,防止it失效
                erase(it++);//后置++重载返回的是tmp,迭代器it再往后移动,it也不会失效
            }
        }


    private:
        node* _head;//哨兵位节点

    };


    void Print(const list<int>& lt)
    {
        list<int>::const_iterator it = lt.begin();
        while (it != lt.end())
        {
            //(*it) *= 2;
            cout << *it << " ";
            ++it;
        }
        cout << endl;
//        for(auto e:lt)
//        {
//            cout << e << " ";
//        }
//        cout << endl;
    }
    //测试
    void test1()
    {
        list<int> lt;
        lt.push_back(1);
        lt.push_back(2);
        lt.push_back(3);
        lt.push_back(4);
        list<int>::iterator it=lt.begin();
        Print(lt);
        cout << endl;

    }

    struct AA
    {
        int _a1;
        int _a2;

        AA(int a1 = 0, int a2 = 0)
                :_a1(a1)
                , _a2(a2)
        {}
    };

    void test2()
    {
        list<AA> lt;
        lt.push_back(AA(1, 1));
        lt.push_back(AA(2, 2));
        lt.push_back(AA(3, 3));

        // AA* ptr
        list<AA>::iterator it = lt.begin();
        while (it != lt.end())
        {
            //cout << (*it)._a1 << ":" << (*it)._a2 << endl;
            cout << it->_a1 << ":" << it->_a2 << endl;//为了可读性,省略了一个->
            //cout << it.operator->()->_a1 << ":" << it.operator->()->_a1 << endl;
            ++it;
        }
        cout << endl;
    }

    void test3()
    {
        list<int> lt;
        lt.push_back(1);
        lt.push_back(2);
        lt.push_back(3);
        lt.push_back(4);
        Print(lt);

        auto pos = lt.begin();
        ++pos;
        lt.insert(pos, 20);
        Print(lt);

        lt.push_back(100);
        lt.push_front(1000);
        Print(lt);

        lt.pop_back();
        lt.pop_front();
        Print(lt);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值