C++ list讲解

༺ 个人主页 · 纪念229 ༻

🏠我的博客主页🏠

༒专栏目录:《数据结构》༒

༒专栏目录:《算法》༒

༒专栏目录:《C++》༒

༒专栏目录:《MySQL数据库》༒

༒专栏目录:《前端开发》༒

༒其它有趣的计算机知识༒

༺世上本没有路,走的人多了自然就有了༻


本篇文章主要通过代码来讲解C++中的list类模板讲解,希望对你有所帮助


文章目录


1.list.h

list模板在list.h文件实现模板的声明和定义要写在同一文件中

因为是模板我先展示所有代码之后再一一讲解

c 复制代码
#pragma once

namespace zjz
{
    template<class T>
    struct list_node
    {
        T _data;
        list_node<T>* _next;
        list_node<T>* _prev;

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

    template<class T, class Ref, class Ptr>
    //这里设计类模板有三个可变类型是为了减少代码将之前的两个类模板的功能实现到一个类模板里(原因是两个类模板功能差不多就是有几个函数有区别函数名相同返回值不同但是无法写在同一个模板里会重定义)
    //这里的可变类型T我们一般写在变为普通类型,Ref一般写在引用类型,Ptr一般变为指针类型
    struct _list_iterator
    {
        typedef list_node<T> Node;
        typedef _list_iterator<T, Ref, Ptr> Self;
        Node* _node;

        _list_iterator(Node* node)
            :_node(node)
        {
        }

        Ref operator* ()
        {
            return _node->_data;
        }

        Ptr operator->()
        {
            return &_node->_data;
        }

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

        Self operator++(int)
            //Self& operator++(int)这里返回函数里定义的Self不能用引用里面的对象已经销毁了
        {
            Self tmp(*this);
            //Self tmp = new Self;这里是默认拷贝构造不能这么写而且要写也要是  //Self* tmp = new Self;而且Self没有默认构造编译报错
            _node = _node->_next;
            return tmp;
        }

        Self& operator--()
        {
            _node = _node->_prev;
            //_node->_prev;这里将成员变量--所以要_node = _node->_prev;
            return *this;
        }

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

        bool operator!=(const Self& it)//判断的是类是否不同
        {
            return _node != it._node;
        }

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

    /*template<class T>
    struct __list_const_iterator
    {
        typedef list_node<T> Node;
        Node* _node;

        __list_const_iterator(Node* node)
            :_node(node)
        {}

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

        __list_const_iterator<T>& operator++()
        {
            _node = _node->_next;
            return *this;
        }

        __list_const_iterator<T> operator++(int)
        {
            __list_iterator<T> tmp(*this);
            _node = _node->_next;
            return tmp;
        }

        __list_const_iterator<T>& operator--()
        {
            _node = _node->_prev;
            return *this;
        }

        __list_const_iterator<T> operator--(int)
        {
            __list_const_iterator<T> tmp(*this);
            _node = _node->_prev;
            return tmp;
        }

        bool operator!=(const __list_const_iterator<T>& it) const
        {
            return _node != it._node;
        }

        bool operator==(const __list_const_iterator<T>& it) const
        {
            return _node == it._node;
        }
    };*/


    //reverse_iterator迭代器的底层与iterator迭代器功能几乎相同(rend就是begin,rbegin就是end)但是我们实现的时候rnbegin要指向最后一个元素,rend指向第一元素的上一个位置(就是我们实现要在第一个元素的上一个位置到最后一个元素(左开右闭(a,b]))
    template<class Iterator,class Ref, class Ptr>
    struct Reverse_iterator
    {
        Iterator _it;//底层封装的正向迭代器
        
        //构造函数
        Reverse_iterator(Iterator it) : _it(it) {}

        //解引用:返回当前底层迭代器前一个位置元素
       
        /*Ref operator*()
        {
            _it--;
            return *_it;
        
        }*///这种写法是错的解引用只是看看但不动迭代器

        Ref operator*()
        {
            //利用迭代器运算符重载返回迭代器指向的元素的引用,但并不改变迭代器
            Iterator tmp = _it;
            return *(--tmp);
        }

        //箭头操作符:返回前一个位置元素的地址,原迭代器不改变(定义一个新迭代器去遍历)
        Ptr operator->()
        {
            Iterator tmp = _it;
            --tmp;
            return &(*tmp);//这里为什么不返回--tmp因为要返回的的不是Iterator(指针的指针或类指针)是元素的指针迭代器调用运算符重载返回元素的指针
        }
        //这里这两个运算符重载假设的对象是it
        //*it就会变成it.operator*()it->就会变成it.operator->()但是我们用operator->()的时候返回的是元素指针指向元素(当我们想利用元素指针调用元素时)(所以我们会加->该类型变量调用该元素)(普通元素指针可以->(指向)自己类类型元素可以->自己也可以指向自己的成员)
       
        //前置++:反向迭代器的++,底层其实是--(加加减减迭代器返回的就是迭代器)加加减减的都是_it这个成员变量
        Reverse_iterator& operator++()//这里为什么返回的不是类加<XXX>,原因是该函数在类模板内模板确定是什么类型就是什么类型不用加<>  ////这里返回类型是&要求是返回的迭代器是原来的那个要求地址相同
        {
            _it--;
            return *this;
        }

        //后置++(前置加加要的是迭代器和原迭代器指针后置加加要的只是这个迭代器)
        Reverse_iterator operator++(int)
        {
            Reverse_iterator tmp(*this);
            _it--;
            return tmp;
        }

        //前置--:方向迭代器的--,底层实际上是++
        Reverse_iterator& operator--()
        {
            _it++;
            return *this;
        }

        //后置--:反向迭代器的--,底层实际上是++
        Reverse_iterator operator--(int)
        {
            Reverse_iterator tmp(*this);
            _it++;
            return tmp;
        }

        //比较操作符
        bool operator!=(const Reverse_iterator& s)const
        {
            return _it != s._it;
        }

        bool operator==(const Reverse_iterator& s)const
        {
            return _it == s._it;
        }
    };


    template<class T>
    class list
    {
        typedef list_node<T> Node;
        //typedef list_node<T>类模板不写<>就根本不知道这个类是什么所以要加<>
    public:
        typedef _list_iterator<T, T&, T*> iterator;//这个也是类模板要加<>确定类型
        typedef _list_iterator<T, const T&, const T*> const_iterator;//这个也是类模板要加<>确定类型

        //typedef _list_const_iterator<T> const_iterator


        //这个iterator只是一个别名利用typedef它实际上是个类   
        iterator begin()
        {
            return iterator(_head->_next);//这里返回iterator类型是因为返回类型是iterator
        }
        iterator end()
        {
            return iterator(_head);//因为iterator是类所以iterator(_head)这不是隐式类型转换是构造函数
        }
        const_iterator begin() const
        {
            return const_iterator(_head->_next);//这里为什么不触发重定义是因为begin(list* this)const实际上是begin(const list* this)这是函数重载不会触发重定义
        }
        const_iterator end() const
        {
            return const_iterator(_head);//迭代器是指针就是隐式类型转换是类就是构造函数
        }
        void empty_init()
        {
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }

        list()
        {
            empty_init();
        }

        list(const list& lt)
        {
            empty_init();
            //利用范围for遍历插入数据
            for (auto e : lt)
            {
                push_back(e);
            }
        }
        list(initializer_list<T> il)//initializer_list<T> il本来就是只读对象不用加const
        {
            empty_init();
            for (auto e : il)
            {
                push_back(e);
            }
        }
        void swap(list<T>& lt)
        {
            std::swap(_head, lt._head);
            std::swap(_size, lt._size);
        }

        //lt1 = lt3
        //list& operator=(list lt)
        list<T>& operator = (list<T> lt)
        {
            //这里返回值用引用,目的是让返回对象还是原来的那个对象
            //这里利用swap进行赋值重载是因为形参不是&对实参没有影响
            swap(lt);

            return *this;
        }
        //第二种写法
    //    list& operator=(const list& lt)
    //{
    //    clear();
    //    if (this != &lt)//如果不是一个对象就进行赋值
    //    {
    //        for (auto e : lt)
    //        {
    //            push_back(e);
    //        }
    //    }
    //    return *this;
    //} 

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

        void clear()
        {
            //遍历所有节点删除节点数据
            iterator it = begin();
            while (it != end())
            {
                it = erase(it);
                //erase(it);这里要更新迭代器
            }
        }
        //    void push_back(const T& x)
        //{
        //    Node* newnode = new Node(x);
        //    //Node* newnode = new Node;这里要在Node后加(x)因为是要插入的数据
        //    Node* tail = _head->_prev;//这些list_node里的成员变量也都要_这个符号

        //    tail->_next = newnode;
        //    newnode->_prev = tail;
        //    newnode->_next = _head;
        //    _head->_prev = newnode;
        //}

        void push_back(const T& x)
        {
            insert(end(), x);
        }

        void push_front(const T& x)
        {
            insert(begin(), x);
        }
        //类里面的成员函数调用类里面的成员函数调用的成员函数可以在该成员函数上面也可以在下面
        void pop_back()
        {
            erase(--end());//这里实际上是将哨兵移到了上一个位置哨兵下一个数据还是第一个数据它们仍然相连
        }
        void pop_front()
        {
            erase(begin());
        }

        iterator insert(iterator pos, const T& val)
            //iterator& insert(iterator pos, T& val)返回的是函数里面的对象不能用引用
        {
            //先弄三个类指针
            //这里的Node*就是指针所以就是普通的赋值不是赋值重载
            Node* cur = pos._node;//cur指的是当前目标指针 //pos不是指针对象是对象用.遍历
            Node* newnode = new Node(val);
            Node* prev = cur->_prev;

            //然后将三个节点相连
            prev->_next = newnode;
            newnode->_prev = prev;
            cur->_prev = newnode;
            newnode->_next = cur;

            //将有效数加加
            ++_size;

            return iterator(newnode);//栈里面的对象和返回的对象内容相同但地址不同也就是说它们不是一个对象
        }

        iterator erase(iterator pos)
        {
            //利用三个指针将要删除的节点删除
            Node* cur = pos._node;
            Node* prev = cur->_prev;//这里pos无法遍历到_prev要利用pos->_node的变量cur
            Node* next = cur->_next;

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

            //最后还要清空pos迭代器指向的节点指向的数据,还要将有效数据减减
            delete cur;
            --_size;

            //最后返回迭代器指向数据的下一个数据的迭代器
            return next;//这里没发生构造函数发生了隐式构造(没有这个构造函数这个隐式构造也无法实现)
            //return iterator(next);
        }
        size_t size()const
        {
            //size_t n = 0;
            ////这里是利用范围for数有多少个数据(范围for利用的的是迭代器遍历)
            //for (auto e : *this)
            //{
            //    n++;
            //}
            //return n;

            return _size;
        }

    private:
        Node* _head;//作为哨兵
        size_t _size = 0;//计数
    };



}

我们看到它有三个类模板(class 和 struct我们都称之为类)list_node、_list_iterator、list

list_node就是list类的节点,list类构成的是双向链表

_list_iterator类是迭代器利用它的成员变量遍历节点

list类的成员变量可以遍历双向链表的节点


1.1list_node

c 复制代码
template<class T>
struct list_node
{
    T _data;
    list_node<T>* _next;
    list_node<T>* _prev;

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

list_node是由struc类形成这里我就简单介绍一下,双向链表之前的文章有解释这里就不多赘述了(list_node就是我们所说的节点(它是类构成的))

struct list_node里没写访问限定符默认就是public

因为是类模板所以T可以是大多数类型

_data为节点存储数据 _next 和 _prev都是list_node*类型指向的是下一个节点和上一个节点

c 复制代码
 T _data;
 list_node<T>* _next;
 list_node<T>* _prev;

然后里面就一个构造函数初始化里面_data(x)初始化传入数据,其它变量都为nullptr函数里什么都没有

c 复制代码
        list_node(const T& x = T())
            :_data(x)
            , _next(nullptr)
            , _prev(nullptr)
        {
        }

1.2_list_iterator

_list_node迭代器这个类用于遍历双向链表的节点

这里反向迭代器就不解释了感兴趣的可以了解一下

c 复制代码
template<class T, class Ref, class Ptr>
//这里设计类模板有三个可变类型是为了减少代码将之前的两个类模板的功能实现到一个类模板里(原因是两个类模板功能差不多就是有几个函数有区别函数名相同返回值不同但是无法写在同一个模板里会重定义)
//这里的可变类型T我们一般写在变为普通类型,Ref一般写在引用类型,Ptr一般变为指针类型
struct _list_iterator
{
    typedef list_node<T> Node;
    typedef _list_iterator<T, Ref, Ptr> Self;
    Node* _node;

    _list_iterator(Node* node)
        :_node(node)
    {
    }

    Ref operator* ()
    {
        return _node->_data;
    }

    Ptr operator->()
    {
        return &_node->_data;
    }

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

    Self operator++(int)
        //Self& operator++(int)这里返回函数里定义的Self不能用引用里面的对象已经销毁了
    {
        Self tmp(*this);
        //Self tmp = new Self;这里是默认拷贝构造不能这么写而且要写也要是  //Self* tmp = new Self;而且Self没有默认构造编译报错
        _node = _node->_next;
        return tmp;
    }

    Self& operator--()
    {
        _node = _node->_prev;
        //_node->_prev;这里将成员变量--所以要_node = _node->_prev;
        return *this;
    }

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

    bool operator!=(const Self& it)//判断的是类是否不同
    {
        return _node != it._node;
    }

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

这里在类模板上写三个可变类型的作用主要是判断该迭代器是否为const迭代器还是普通迭代器

三个可变类型 T普通类型 Ref引用类型 Ptr指针类型

若是我们的类模板只有一个可变类型

我们想设计若是const迭代器的时候

调用运算符重载operator*普通迭代器与const迭代器对这个成员函数的区别只有返回类型一个是 返回引用一个是返回const引用其它地方相同(函数名和参数)在同一个类中会重定义若是我们的类模板迭代器只有一个可变类型我们只能在写一个类模板const迭代器徒增代码但是我们要是在类模板迭代器上设计三个可变变量当我们遇到的普通类型是非const的

这三个变量就会变为 T T&(Ref) T*(Ptr)

当我们遇到的T是const T类型时编译器就会匹配定义三个可变类型为

const T const T& const T*

所以我们看到有三个可变类型的模板是普通迭代器(类)还是const迭代器(类)取决于编译器编译时决定的这三个可变类型的类型这样写自动匹配类型就可以省去多写一个类模板的代码

这就是写list类最麻烦的地方

接下来讲解一下 _list_iteraotr类模板

首先将list_node类和_list_iteraotr<T, Ref, Ptr>类typedef为Node和Self代码里面要用书写方便

Node* _node;

只有一个成员变量 _node是节点指针(类指针)


c 复制代码
_list_iterator(Node* node)
    :_node(node)
{
}

这里是构造函数参数是list_node类指针

将该指针作为实参构造_list_iterator对象,函数内容为空直接在初始化列表调用

_node(node)即可实现该对象的构造


c 复制代码
        Ref operator* ()
        {
            return _node->_data;
        }

这里由于该对象是类类型不是指针解引用我们要调用运算符重载operator*返回的是该对象成员变量(_node(list_node指针))指向的元素_data

调用时实际上是这样的

it(_list_iteraotr类对象)

it*; 就会变为 it.operator*();返回的是一个T类型的变量


c 复制代码
 Ptr operator->()
 {
     return &_node->_data;
 }

当然也不能使用->因为它不是指针,它的作用是返回对象成员变量指向元素的地址(注意不是该类对象(迭代器)的指针)

调用

it->_data; 变为 it.operator->()_data再变为&_node->_data _data最后变为(Person* 即为真实数据的地址)(Person*)_data最后自动拼接为(Person*)->_data(这里要得到的是_data的具体数据(该元素的地址->_data得到的就是该_data的具体数据))

这里讲述一下->的用法

普通变量指针->可以调用该普通变量的值

对象指针->可以调用对象也可以调用对象的成员变量

接下来的这些代码就与这个类(迭代器)有关(Self(_list_iterator))

c 复制代码
 Self& operator++()
 {
     _node = _node->_next;
     return *this;
 }

 Self operator++(int)
     //Self& operator++(int)这里返回函数里定义的Self不能用引用里面的对象已经销毁了
 {
     Self tmp(*this);
     //Self tmp = new Self;这里是默认拷贝构造不能这么写而且要写也要是  //Self* tmp = new Self;而且Self没有默认构造编译报错
     _node = _node->_next;
     return tmp;
 }

 Self& operator--()
 {
     _node = _node->_prev;
     //_node->_prev;这里将成员变量--所以要_node = _node->_prev;
     return *this;
 }

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

 bool operator!=(const Self& it)//判断的是类是否不同
 {
     return _node != it._node;
 }

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

这里的运算符重载operator++()前置++就是改变对象的成员变量返回更新后的迭代器

c 复制代码
 Self& operator++()
 {
     _node = _node->_next;
     return *this;
 }

_node = _node->_next;将节点迭代器移向下一个

return *this;返回更新好的迭代器(类)由于返回类型是Self&所以要的是迭代器还要了迭代器的指针

c 复制代码
 Self operator++(int)
     //Self& operator++(int)这里返回函数里定义的Self不能用引用里面的对象已经销毁了
 {
     Self tmp(*this);
     //Self tmp = new Self;这里是默认拷贝构造不能这么写而且要写也要是  //Self* tmp = new Self;而且Self没有默认构造编译报错
     _node = _node->_next;
     return tmp;
 }

第二个后置++先利用默认拷贝构造拷贝对象

Self tmp(*this);因为Self是_list_iterator类所以调用的是构造函数tmp就是这个类的对象

_node = _node->_next;更新对象成员变量移向下一个节点

最后返回Self类的临时对象tmp 由于返回值为T没有&所以不会报错得到的只是这个迭代器

其它我就不赘述了,感兴趣的可以学习一下


文章到此就告一段落,希望对你有所帮助,感谢观看!

相关推荐
闻缺陷则喜何志丹1 小时前
vc入门宝典(九)
数据结构·list·mfc·存储·null·class
半拉老头3 小时前
用360安全浏览器插件图片助手批量下载图片
windows
不会就选b6 小时前
C++补充部分(一)--lambda 表达式
开发语言·c++
Vex2une13 小时前
Docker Desktop WSL2后端:2026年性能调优与避坑实战指南
windows·docker·desktop·gpu容器·容器开发·virtiofs·buildkit
丰色木夕14 小时前
Ubuntu22.04下源码编译安装USD-Brep
c++·ubuntu·nvidia·usd-brep
钱栈up14 小时前
搜狗中文模式打不出顿号?在 Windows 沙箱里探测输入法配置的踩坑实录
windows
白搞电子15 小时前
ZeroClaw 是什么?与 ESP-Claw 的区别及文件整理用途示例
windows·物联网
BeiGuTing_code16 小时前
Java动态数组(一)
java·数据结构·windows
衍玊16 小时前
多路转接原理与应用
c++