• C++ list使用与模拟


    写在前面

    到这里我们就知道知道STL的具体的框架了,里面的一些函数你会发现用法都一样。这个博客主要谈一下迭代器的分封装,前面我们蹙额的string和vector的迭代器都是原生指针,但是今天的却不一样。在这里你会发现既然我们的string和vector都可以支持下标访问,为何还要存在迭代器?今天的list你就会发现它不支持下标,我们的STL为了统一性,都支持了迭代器。

    list认识

    我们先来看一下list里面的函数,最关键的是里面的构造函数,这里面我们先看用法,先不用关心里面的原理.关于那些比较熟悉的函数这里我们就不谈了,后面实现的时候都会谈到的.

    image-20220816204657688

    构造函数

    这里面存在4个构造函数,其中我们吧比较常用的拿出来就可以了.

    image-20220816210417647

    构造函数接口说明
    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

    先测试一下构造的list中包含n个值为val的元素.

    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    	list<int> l(10,1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20220816205915515

    再看一个不带参的构造函数.

    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    	list<int> l;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20220816210031147

    其余的我们这里都不演示了,前面我们学习的时候都用过,没必要浪费大家的时间了.

    迭代器

    这里我们还是首先首先把迭代器给大家分享了,这里还是正向迭代器等等那四种,用法上是没有新意的,这里我们就还是演示一种吧.

    image-20220816210449555

    int main()
    {
    	list<int> l(10, 1);
    	list<int>::iterator it = l.begin();
    	while (it != l.end())
    	{
    		cout << *it << " ";
    		++it;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20220816210642282

    reverse()

    这是一个逆置函数,就像我们之前实现的字符串翻转,我们这里来看一下就可以了。

    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    	list<int> l;
    	for (int i = 0; i < 10; i++)
    	{
    		l.push_back(i);
    	}
    	l.reverse();
    	for (int& val : l)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    image-20220817094403231

    sort()

    list他自己内置了一个sort函数,具体的用法倒是比较简单的,我们先使用一下,里面的比较器是对于自定类型的,这个到后面我们在谈.

    image-20220817094802615

    #include 
    #include 
    #include 
    
    using namespace std;
    int main()
    {
    	srand((unsigned int)time(NULL));
    
    	list<int> l;
    	for (int i = 0; i < 20; i++)
    	{
    		int ret = rand() % 100;
    		l.push_back(ret);
    	}
    	for (int& val : l)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	l.sort();
    	for (int& val : l)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image-20220817095615377

    list的sort函数的性能有点低,一般我们是不使用的,这博客面我会专门分享一下这个函数.

    merge()

    merge(()的本质就是合并两个有序链表,记住是有序,也就是我们在合并之前最好把这两个链表拍一下序.

    image-20220817101122366

    int main()
    {
    	srand((unsigned int)time(NULL));
    
    	list<int> l1;
    	list<int> l2;
    	for (int i = 0; i < 20; i++)
    	{
    		int ret = rand() % 100;
    		l1.push_back(ret);
    	}
    	for (int i = 0; i < 20; i++)
    	{
    		int ret = rand() % 100;
    		l2.push_back(ret);
    	}
    	
    	l1.sort();
    	l2.sort();
    	l1.merge(l2);
    	for (int& val : l1)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    image-20220817101048194

    注意,当我们时候用merge函数,最为参数的哪个list已经被清空了,这一点知道就可以了.

    int main()
    {
    	srand((unsigned int)time(NULL));
    
    	list<int> l1;
    	list<int> l2;
    	for (int i = 0; i < 20; i++)
    	{
    		int ret = rand() % 100;
    		l1.push_back(ret);
    	}
    	for (int i = 0; i < 20; i++)
    	{
    		int ret = rand() % 100;
    		l2.push_back(ret);
    	}
    
    	l1.sort();
    	l2.sort();
    	l1.merge(l2);
    	cout << l2.size() << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    image-20220817101610685

    unique()

    这个函数是去重函数,不过我们还是需要把list排序,简单的去重这里就演示了,看看这个不排序的的情况.这个可以理解为我们只是去一个区域的重.

    int main()
    {
    	
    	list<int> l;
    	l.push_back(1);
    	l.push_back(1);
    	l.push_back(2);
    
    	l.push_back(1);
    	l.push_back(3);
        for (int& val : l)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	l.unique();
    	for (int& val : l)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    image-20220817102859784

    sort性能

    list里面的sort本质是归并排序,list的排序数据比较多的就是一个废物,我们在想一件事,我们STL里面的算法库里面不是存在一个排序算法吗,这里为何list还要内置一个.我们先来看看算法库里面.

    image-20220817112547226

    #include 
    
    int main()
    {
    	srand((unsigned int)time(NULL));
    
    	list<int> l;
    	for (int i = 0; i < 20; i++)
    	{
    		int ret = rand() % 100;
    		l.push_back(ret);
    	}
    	for (int& val : l)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	std::sort(l.begin(),l.end());
    	for (int& val : l)
    	{
    		cout << val << " ";
    	}
    	cout << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    image-20220817112833778

    迭代器分类

    我们发现上面报了一堆的错误,这里也不要让大家思考了,我们这里可以观察到事情,我们把它称为随机迭代器,这里我们就有点疑惑了,难道迭代器还会分类吗?我们好象从没有见过,主要是我们之前淡化了了这个知识点.

    image-20220817114726897

    我们们观察一下前面学的string和vector的迭代器种类,顺便把迭代器的种类拿出来.

    image-20220817143943562

    迭代器分类

    • 单项迭代器
    • 双向迭代器
    • 随机迭代器

    单项迭代器

    我们先来谈一下这个迭代器,单项迭代器是只能够++,其中这个的stl对应的是下面的几个.

    image-20220817144829684

    双向迭代器

    这里的我就不在截图了,双向迭代器支持++或者–,这种还是比较多的,比如list,map,set.一般情况下,如果是底层的物理结构不连续的话就是这样子的.

    随机迭代器

    随机迭代器是既支持++和–,又支持+,-.我们学的vector,string,加上我们后面的要学的适配器deque他们都是随机迭代器.

    sort性能

    从这里我们就可以看出,算法里面的sort是随机迭代器,但是我们的list只是双向迭代器,这是不适合的,我们可以把随机迭代器传给双向迭代器,但是反过来却不行,,这也是我们list内置sort的原因.

    我们需要测试下sort的性能,某种意义上,list里面的sort就是废物.我们先来测试vector和list的排序比较.

    int main()
    {
    	srand((unsigned int)time(NULL));
    	const int N = 1024*1024;
    	list<int> l;
    	vector<int> v;
    	for (int i = 0; i < N; i++)
    	{
    		int val = rand();
    		l.push_back(val);
    		v.push_back(val);
    	}
    
    	int begin1 = clock();
    	l.sort();
    	int end1 = clock();
    
    	int begin2 = clock();
    	std::sort(v.begin(), v.end());
    	int end2 = clock();
    	cout << "list : " << (end1 - begin1) << endl;
    	cout << "sort: " << (end2 - begin2) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image-20220817152218557

    我们发现如果数据量在1w以内,这样的话两者的性能还是相差不大的,但是随着数据量的扩大,两者的差异越来越大.

    我们再测试一下,如果我们再vector中排序,后面在导list中去,看看他们他们的性能怎么样.你会发现我借助vector排序,排好了我在倒回去都比你厉害,那么请问你不是废物吗.

    int main()
    {
    	srand((unsigned int)time(NULL));
    	const int N = 1024 * 1024;
    	list<int> l1;
    	list<int> l2;
    	vector<int> v;
    	for (int i = 0; i < N; i++)
    	{
    		int val = rand();
    		l1.push_back(val);
    		l2.push_back(val);
    	}
    	// 排序 l1
    	int begin1 = clock();
    	l1.sort();
    	int end1 = clock();
    
    	// 先排序 再移动
    	int begin2 = clock();
    	v.reserve(N);
    	for (int& val : l2)
    	{
    		v.push_back(val);
    	}
    
    	std::sort(v.begin(), v.end());
    	// 重新填回去
    	l2.clear();
    	for (int& val : v)
    	{
    		l2.push_back(val);
    	}
    	int end2 = clock();
    	cout << "list   : " << (end1 - begin1) << endl;
    	cout << "vector : " << (end2 - begin2) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    image-20220817152945930

    迭代器封装

    到这里我们就可以看看迭代器的封装了,在之前我们都是用的原生指针,本质原因就是他们的物理空间是连续的,这里list可不是所谓的连续空间,他们可以把一个个节点.那么++和–就出现了问题,所以这里我们需要把迭代器单独拿出来.

    我们先来把简陋的迭代器给写好,后面再完善.

    template<class T>
    struct _list_iterator 
    {
        typedef _list_node<T> Node;
        typedef _list_iterator<T> self; // 这个是需要的,后面我们会 修改这个,如果tepdef就只可以修改这一个就可以了
        Node* _node; // 我们给指针 ,节点的空间可能 比较大
    
        _list_iterator(Node* node)
            :_node(node)
        	{
            }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们来想一下,所谓的++或者–不就是当前指针的节点指向 next或者prev吗,对应出的迭代器对象是不变的,只是里面的内容变了.

    int main()
    {
    	list<int> l;
    	l.push_back(1);
    	l.push_back(2);
    	l.push_back(3);
    	l.push_front(10);
    
    	list<int>::iterator it = l.begin();
    	while (it != l.end())
    	{
    		cout << &it << endl;
    		it++;
    	}
    	cout << l.size() << endl;
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image-20220817185533563

    迭代器实现

    我们的大致结构就是下面的,我们把第一个元素当作begin,哨兵位当作end,至于为何这么做还是存在一定的理由,不过要到后面的一个博客分析了,这涉及到反向迭代器.

    image-20220818085312378

    class list
    {
    public:
        typedef _list_node<T> Node;
        typedef _list_iterator<T> iterator;
    public:
        list()
        {
            // 首先 new 一个头节点
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }
    private:
        Node* _head; // 哨兵位
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    begin() & end()

    我们先把普通的给实现出来,后面const修饰的害需要单独讨论.

    iterator begin()
    {
        return iterator(_head->_next);
    }
    iterator end()
    {
        return iterator(_head);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    operator!= & operator==

    到这里我们就知道了我们只是改变迭代器里面的指针指向,具体的不在说了.我们先把==和!=重载出来.这个只需要比较一下里面的指针就可以了.

    bool operator!=(const self& t) const
    {
        return _node != t._node;
    }
    bool operator==(const self& t) const
    {
        return !(*this != t);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    operator++

    所谓得++就是把里面得指针指向下一个地址,本质上也没有什么看点

    // 前置
    self& operator++()    
    {    
        _node = _node->_next;    
        return *this;    
    } 
    
    // 后置
    self operator++(int)
    {
        self cur = *this; // 调用  的 是拷贝构造
        _node = _node->_next;
        return cur;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    operator–

    既然++都是实现出来了,那么–也是比较容易的.

    // 前置
    self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }
    // 后置
    self operator--(int)
    {
        self cur(*this);
        _node = _node->_prev;
        return cur;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    operator*

    到这里就开始出现问题了,这个运算符是解引用,也就是得到节点里面的数据,我们这里使用T作为返回值,或者是突破类域,其中这个突破类域的方法我们后面再谈,这是标准库里面的做法.

    T operator*()
    {
          return _node -> _data;
    }
    
    • 1
    • 2
    • 3
    • 4

    operator->

    既然我们提出了一个解决方法,这里先把运算符都写出来,后面遇到问题我们再修改.operator->算是编译器优化了一下,本来该&(iterator._node->_date)这样的.

    T* operator->()
    {
        return &(_node->_data);
    }
    
    • 1
    • 2
    • 3
    • 4

    测试迭代器

    现在我们就可以看看自己的写的迭代器怎么样了,这里我们把情况给测试一下,这里面最大的问题就是后面两个.

    我们先来测试一下普通的迭代器,后面的const迭代器是存在些问题的.

    
    int main()
    {
      bit::list<int> l;
      l.push_back(1);
      l.push_back(2);
      l.push_back(3);
      l.push_back(4);
      l.push_back(5);
      l.push_back(6);
      l.push_back(7);
      l.push_front(10);
      
      bit::list<int>::iterator it = l.begin();
      while(it != l.end())
      {
       cout <<*it << endl;
       it++;
      }
    
    
    
      return 0;
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image-20220820105018402

    struct A
    {
      A(int a = 0,int b= 0)
      {
        a1 = a;
        a2 = b;
      }
    
      int a1;
      int a2;
    };
    
    int main()                                                                                                               
    {
      bit::list<A> l;
      l.push_back(A(1,2));
      l.push_back(A(1,2));
      l.push_back(A(1,2));
      bit::list<A>::iterator it = l.begin();
      while(it != l.end())
      {
        cout << it->a1 <<  " " << it->a2 <<endl;
        it++;
      }
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    image-20220820104522063

    现在我们已经确定了普通的迭代器是正常使用的,我们现在需要看看看const修饰的迭代器了.这里我们存在两种方法,第一种是我们重新写一个类 ,不过这个方法重复造轮子了.我们不推荐.剩下的两种我们先暂时放一放,先来看卡我们遇到的问题.

    #include 
    namespace bit
    {
          这个 是 list 节点
        template<class T>
        struct _list_node
        {
            _list_node<T>* _next;
            _list_node<T>* _prev;
            T _data;
    
            // 先看看  构造函数 要不要写
            _list_node(const T& t = T())
                :_next(nullptr)
                , _prev(nullptr)
                , _data(t)
            {
            }
        };
    
    
        template<class T>
    
        struct _list_iterator
        {
            typedef _list_node<T> Node;
            typedef _list_iterator<T> self;
    
    
            Node* _node;
    
            _list_iterator(Node* node)
                :_node(node)
            {
            }
    
            // 析构  拷贝  都不需要
    
            bool operator==(const self& t) const
            {
                return !(*this != t);
            }
    
    
            T* operator->()
            {
                return &_node->_data;
            }
    
            T operator*()
            {
                return _node->_data;
            }
            bool operator!=(const self& t) const
            {
                return _node != t._node;
            }
            //前置
            self& operator++()
            {
                _node = _node->_next;
                return *this;
            }
    
            self& operator--()
            {
                _node = _node->_prev;
                return *this;
            }
            // 后置
            self operator++(int)
            {
                self cur = *this;
                _node = _node->_next;
                return cur;
            }
    
            self operator--(int)
            {
                self cur(*this);
                _node = _node->_prev;
                return cur;
            }
    
        };
    
        template<class T> //  这个 就是一个模板
        class list
        {
        public:
            typedef _list_node<T> Node;
    
     
            typedef _list_iterator<T> iterator;
            typedef _list_iterator<const T> const_iterator;
    
            //  要有 构造函数  作为 一个  标记位
    
            list()
            {
                // 首先 new 一个头节点
                _head = new Node;
                _head->_next = _head;
                _head->_prev = _head;
            }
    
    
            // 插入  不做说明的话 我们 插入 节点的前面
            iterator insert(iterator pos, const T& val)
            {
                // 记录 迭代器 对应的 节点指针
                Node* ppos = pos._node;
                Node* cur = new Node(val);
    
                // 记录 前一个 节点
                Node* prev = ppos->_prev;
    
                cur->_next = ppos;
                ppos->_prev = cur;
                cur->_prev = prev;
                prev->_next = cur;
    
                return iterator(cur);
            }
    
            void push_back(const T& val)
            {
                insert(end(), val);
            }
            // 迭代器
            iterator begin()
            {
                return iterator(_head->_next);
            }
    
            const_iterator begin() const
            {
                return const_iterator(_head->_next);
            }
    
            const_iterator end() const
            {
                return const_iterator(_head);
            }
            iterator end()
            {
                return iterator(_head);
            }
    
        private:
    
            Node* _head;
        };
    }
    void func(const bit::list<int>& l)
    {
    	bit::list<int>::const_iterator it = l.begin();
    	while (it != l.end())
    	{
    		cout << *it << endl;
    		it++;
    	}
    }
    int main()
    {
    	bit::list<int> l;
    	l.push_back(1);
    	l.push_back(2);
    	l.push_back(3);
    	l.push_back(4);
    	l.push_back(5);
    	l.push_back(6);
    	l.push_back(7);
    
    	func(l);
    
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180

    image-20220820114516282

    image-20220820114942184

    我们学到了模板,这里的编译器报的错误就会变多了,这里我们看看高手是怎么控制的.高手给他们重新添加了一个模板,主要就是返回值作用.

     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* node)
                :_node(node)
            {
            }
    
    
            bool operator==(const self& t) const
            {
                return !(*this != t);
            }
    
            Ptr operator->()
            {
                return &_node->_data;
            }
    
            Ref operator*()
            {
                return _node->_data;
            }
    
          
        };
    class list
        {
        public:
            typedef _list_node<T> Node;
    
            typedef _list_iterator<T, T&, T*> iterator;
           
            typedef _list_iterator<T, const T&, const T*> const_iterator;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    image-20220820115046143

    这样就可以了.关于list的迭代器我们到这里就放一段落,后面等我们谈过stack,在和大家谈反向迭代器

    list实现

    我们在C语言过程中学过部分数据结构,学了顺序表,也就是我们的vector,那么今天我们分享的本质就是链表,而且是带哨兵位的双向循环链表.我们先把这个节点给定义出来,一般这种节点我们都是用struct.

    //  这个 是 list 节点
    template<class T>
    struct _list_node
      {
        _list_node<T>* _next;
        _list_node<T>* _prev;
        T _data;
    
        // 先看看  构造函数 要不要写
        _list_node(const T& t = T())
          :_next(nullptr)
           ,_prev(nullptr)
           ,_data(t)
        {
        }
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里我们先把框架搭出来,我们最好把节点typedef一下.

    template<class T> 
    class list
    {
    public:  
        typedef _list_node<T> Node;
    public:
        list()
        {
          // 首先 new 一个头节点
          _head = new Node;
          _head->_next = _head;
          _head->_prev = _head;
        }
    private:
        Node* _head; // 哨兵位
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    insert()

    我们先把insert给实现出来,后面的尾插和头插我们都可以复用这个代码,就不重复造轮子了.这里的迭代器大家行不用关心,后面我们会专门谈到这个迭代器封装,大家现在只需要记住,迭代器里面存在一个节点指针,这个指针就是当前节点地址.其中这里的返回值是插入元素的第一个节点的迭代器器,我是按照标准库来的

    iterator insert(iterator pos, const T& val)    
    {                                                                                                           
        // 记录 迭代器 对应的 节点指针    
        Node* ppos = pos._node;    
        Node* cur = new Node(val);    
    
        // 记录 前一个 节点    
        Node* prev = ppos->_prev;    
    
        cur->_next = ppos;    
        ppos->_prev = cur;    
        cur->_prev = prev;    
        prev->_next = cur;    
    
       return iterator(cur);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    push_back() & push_front()

    代码复用就可以了.

    void push_back(const T& val)
    {
        insert(end(), val);
    }
    
    void push_front(const T& val)
    {
        insert(begin(), val);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里面需要测试一下,这样的话也可以证明前面的insrt是正确的.

    int main()    
    {    
      bit::list<int> l;    
      l.push_back(1);    
      l.push_back(2);    
      l.push_back(3);    
      l.push_back(4);    
      l.push_back(5);    
      l.push_back(6);    
      l.push_back(7);                                                     
      l.push_front(10);    
        
      bit::list<int>::iterator it = l.begin();    
      while(it != l.end())    
      {    
        cout << *it << " ";    
        ++it;    
      }    
        
      cout << endl;    
      return 0;    
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    image-20220817180340850

    erase()

    我们最后把删除函数给实现一下,其他的函数就不管了,主要是太简单了,没有什么挑战性,如果大家想看,等下我们把仓库的链接附在后面,里面又更多的函数实现.

    iterator erase(iterator pos)                                                           
    {                                                                                      
        Node* ppos = pos._node;                                                              
        // 不能 删哨兵位                                                                     
        assert(pos != _head);                                                                
        Node* prev = ppos->_prev;                                                            
        Node* next = ppos->_next;                                                            
    
        prev->_next = next;                                                                                       
        next->_prev = prev;
        delete ppos;
        return iterator(next);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    pop_back() & pop_front

    我们这里还是复用一下吧.

    void pop_back()
    {
        erase(--end());
    }
    
    void pop_front()
    {
        erase(begin());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意,删除元素会有迭代器失效的情况,这里我们先用erase测试,像头删和尾删都会存在问题,标准库里面也是.

    int main()
    {
    	bit::list<int> l;
    	l.push_back(1);
    	l.push_back(2);
    	l.push_back(3);
    	l.push_back(4);
    	l.push_back(5);
    	l.push_back(6);
    	l.push_back(7);
    	l.push_front(10);
    
    	bit::list<int>::iterator it = l.begin();
    	while (it != l.end())
    	{
    		it = l.erase(it);
    	}
    	cout << l.size() << endl;
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20220817182607323

    迭代器失效

    我们测试一下list中迭代器失效的问题,这里比较简单.我们先来看一下insert,这个是没有问题了的.

    int main()
    {
    	::list<int> l;
    	l.push_back(1);
    	l.push_back(2);
    	l.push_back(2);
    	l.push_back(4);
    	l.push_back(4);
    	l.push_back(6);
    	l.push_back(7);
    	auto it = l.begin();
    	while (it != l.end())
    	{
    		if (*it % 2 == 0)
    		{
    			l.insert(it, *it * 10);
    		}
    		it++;
    	}
    
    	for (int val : l)
    	{
    		cout << val << " ";
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    image-20220820135412807

    但是erase就会变成迭代器失效的问题,我们很容易理解erase,一般等我们删除数据这个就会变成一个野指针.

    int main()
    {
    	::list<int> l;
    	l.push_back(1);
    	l.push_back(2);
    	l.push_back(2);
    	l.push_back(4);
    	l.push_back(4);
    	l.push_back(6);
    	l.push_back(7);
    	auto it = l.begin();
    	l.erase(it);
    	
    	*it;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20220820135714455

  • 相关阅读:
    计算机毕设(附源码)JAVA-SSM连锁便民超市前端系统
    【数据结构】链表
    205.同构字符串
    Mysql主从复制数据一致性校验
    力扣:669. 修剪二叉搜索树,今日份快乐
    【必知 - 软件版本号如何定义及使用】
    Java多线程编程-线程间协作wait/notify
    创建Storageclass存储类-基于csi-nfs-driver
    代码随想录49——动态规划:121买卖股票的最佳时机、122买卖股票的最佳时机II
    【精讲】vue组件开发基础、多层嵌套(内含详细注释)、vuecomponent构造函数
  • 原文地址:https://blog.csdn.net/m0_61334618/article/details/126442326