list(c++)

[复制链接]
发表于 2025-7-30 07:45:01 | 显示全部楼层 |阅读模式


前言

这里我们学习的是gcc下STL版本的list。STL里的list容器底层是一个双向带头节点的一个链表,不再是单链表,单链表现实运用很少,更多的是双向带头链表。

正文

list利用

默认成员函数

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














迭代器

函数声明接口说明
begin + end返回第一个元素的迭代器+返回末了一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回末了一个元素下一个位置的 reverse_iterator,即begin位置










容量

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个








增删查改

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中末了一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap互换两个list中的元素
clear清空list中的有效元素


















代码演示

这里list利用很简朴,我就不一个一个演示了。就用一个测试案例测试下吧
  1. void Print(const li::list<int>& lt)
  2. {
  3.         li::list<int>::const_iterator it = lt.begin();
  4.         while (it != lt.end())
  5.         {
  6.                 //(*it)++;
  7.                 cout << *it << " ";
  8.                 ++it;
  9.         }
  10. }
  11. void list_test01()
  12. {
  13.         li::list<int> lt;
  14.         lt.push_back(1);
  15.         lt.push_back(2);
  16.         lt.push_back(3);
  17.         lt.push_back(4);
  18.         Print(lt);
  19.         cout << endl;
  20.         //这里是拷贝构造 内置类型 浅拷贝
  21.         li::list<int>::iterator it = lt.begin();
  22.         while (it != lt.end())
  23.         {
  24.                 (*it)++;
  25.                 cout << *it << " ";
  26.                 ++it;
  27.         }
  28. }
复制代码
这里我就简朴测试下,必要注意的是,erase之后迭代器被开释失效,记得用返回值担当。clear这里底层上就是开释掉了所有的节点,只留下一个头节点。因为链表结构决定了它是可以部分删除容量的,不像连续地址的顺序表。因此,list的clear就直接删除节点了,而不是仅仅清除数据。

list模仿实现

成员变量
  1. template<class T>
  2. struct list_node
  3. {
  4.         list_node<T>* _next;
  5.         list_node<T>* _prev;
  6.         T _val;
  7.         list_node(const T& x = T())
  8.                 :_next(nullptr),
  9.                 _prev(nullptr),
  10.                 _val(x)
  11.         {
  12.         }
  13. };
  14. template<class T>
  15. class list
  16. {
  17.         typedef list_node<T> Node;
  18.     //...
  19.     //
  20.     private:
  21.         Node* _head;
  22.         size_t _size;
  23. }
复制代码
从链表开始,就有点复杂了,利用许多封装和重命名。这里我们必要先实现一个结构体(结点),
这里必要重名命下这个类模板,这是为啥呢?这是一个风俗问题。祖师爷在研究STL的时候,他本身也是从c语言过度来的,虽然类模板也是它老人家研究出来的。但是在声明一个变量的时候,加上一个模板,很容易忘记,尤其风俗了c语言的命名变量的语法。以是,这里重命名下是一种风俗问题,并且也可以简化代码。这也是一个好风俗,因为你的模板可不肯定只有一个,如果你的leader突然让你改一下这个容器,增加一个模板,你难不成一个一个该吗?这时候重命名就香许多了,只必要改一下。
这里还有一个size,有这个参数我们就可以很直观的观察链表,和判定链表是否为空。有利于我们更直观的利用链表和调试。

默认成员函数
  1.                 void empty_init()
  2.                 {
  3.                         _head = new Node;
  4.                         _head->_next = _head;
  5.                         _head->_prev = _head;
  6.                         _size = 0;
  7.                 }
  8.                 list()
  9.                 {
  10.                         empty_init();
  11.                 }
  12.                 //不能用const引用
  13.                 void swap(list<T>& lt)
  14.                 {
  15.                         std::swap(_head, lt._head);
  16.                         std::swap(_size, lt._size);
  17.                 }
  18.                 list<T>& operator=(list<T> lt)
  19.                 {
  20.                         swap(lt);
  21.                         return *this;
  22.                 }
  23.                 list(const list<T>& lt)
  24.                 {
  25.                         empty_init();
  26.                         for (auto& e : lt)
  27.                         {
  28.                                 push_back(e);
  29.                         }
  30.                 }
  31.         ~list()
  32.         {
  33.                 clear();
  34.                 delete _head;
  35.                 _head = nullptr;
  36.         }
复制代码
这里构造函数里面套了一个函数初始化,这也是向库里版本靠齐。这里的赋值重载是用的现代写法,直接swap互换,传统的写法是直接手搓一遍(不推荐)。这里的拷贝构造函数里面复用了push_back,析构函数复用了clear(),这也体现list的高度封装性,其实库里的比这里还复杂。动态内存分配也不是new而是封装了一个空间配置器,就是效率高点。我在vector的博客也提及过。我们学习STL的代码,强烈不发起一行一行看,否则你会直接想放弃的。这里我们先知道个大要逻辑。到下文会解释clear和push_back()这两个函数,其实push_back里面也是套用了insert的。就像套娃一样,这里的代码。
迭代器

重点 重点 重点 重要的事说三遍,list的迭代器可谓是学习list的重点也是一个难点,先问一下,如何设计list的迭代器?可以像vector里那样吗?直接指针吗?可是这里是一个自定义类型呀?解引用也不是结点的值呀?因此,这里肯定是要对*赋值重载的。以是,就可以先否定直接指针了,这肯定不行的!既然要赋值重载,那我们就肯定要自定义类型呀?对,这里的迭代器就是一个自定义类型,里面包含着一个结点指针,还有构造函数,完成初始化。这里不介绍反向迭代器,因为反向迭代器的设计和正向有点不一样,这里我们先学习正向迭代器,正向迭代器就够喝一壶了。
  1. template<class T,class Ref,class Ptr>
  2. struct _list_iterator
  3. {
  4.         typedef list_node<T> Node;
  5.         typedef _list_iterator<T,Ref,Ptr> self;
  6.         Node* _node;
  7.         _list_iterator(Node* node)
  8.                 :_node(node)
  9.         {}
  10.         Ref operator*()
  11.         {
  12.                 return _node->_val;
  13.         }
  14.         Ptr operator->()
  15.         {
  16.                 return &(_node->_val);
  17.         }
  18.         self& operator++()
  19.         {
  20.                 _node = _node->_next;
  21.                 return *this;
  22.         }
  23.         self& operator++(int)
  24.         {
  25.                 self tmp(*this);
  26.                 _node = _node->_next;
  27.                 return tmp;
  28.         }
  29.         self& operator--()
  30.         {
  31.                 _node = _node->_prev;
  32.                 return *this;
  33.         }
  34.         self& operator--(int)
  35.         {
  36.                 self tmp(*this);
  37.                 _node = _node->_prev;
  38.                 return tmp;
  39.         }
  40.         bool operator!=(const self& it) const
  41.         {
  42.                 return _node != it._node;
  43.         }
  44.         bool operator==(const self& it) const
  45.         {
  46.                 return _node == it._node;
  47.         }
  48. };
复制代码
这里先给大家看一下完整版的迭代器设计,是不是很蒙,发现和我想的有点不一样呀!
  1. struct _list_iterator
  2. {
  3. typedef list_node<T> Node;
  4. Node* _node;
  5. _list_iterator(Node* node)
  6.                 :_node(node)
  7.         {}
  8. };
复制代码
这里先看我上面这个代码,是不是发现又看懂了。这里我们学习迭代器必要一步一步拆开学,他的设计其实有点难理解。我们上面讲了,迭代器必要设计成一个自定义类型,必要包装一下结点的指针,以便我们访问。这里的代码是一个雏形,有了这个雏形,对于begin(),end(),我们就完成的差不多了。但还必要大大的完善。这里还要对*,++等运算符赋值重载。那为啥有三个模板呢?这里是根据需求来设定的。首先*赋值重载返回的就是数据本身,以是数据的类型模板,我们必要你上传,直接引用,纵然结构体的数据还是什么其他自定义类型数据,都可以返回精确的值。末了一个模板为啥要传一个指针呢?迭代器模仿的是指针的举动?既然是指针那么就可以用箭头访问结构体。以是,这里的迭代器也满意了这种要求。利用箭头访问结点的元素。同理,本身上传一个指针,这样后续更改代码,我们只用修改这部分代码。这里是如何设计const修饰的迭代器呢?这时候我们的模板就香许多了,我们这必要在上传模板的时候加上一个 const,这样子你必要什么我就返回什么。这也证实我们为啥要搞三个模板,就是因为,后面的代码设计是必要利用的。其实刚开始学这里的迭代器的时候,我也很蒙蔽,但是学到后面,你才发现祖师爷的这套设计有多厉害!这里的命名也是向库里靠拢的,发起大家以后命名也尽量向库里靠拢。
  1.         typedef list_node<T> Node;
  2. public:
  3.         typedef _list_iterator<T,T&,T*> iterator;
  4.         typedef _list_iterator<T, const T&,const T*> const_iterator;
  5.         iterator begin()
  6.         {
  7.                 return _head->_next; //单参数的构造函数支持隐式类型转换
  8.                 //return iterator(_head->_next);
  9.         }
  10.         iterator end()
  11.         {
  12.                 return _head;
  13.         }
  14.         const_iterator begin() const
  15.         {
  16.                 return _head->_next;
  17.                
  18.         }
  19.         const_iterator end() const
  20.         {
  21.                 return _head;
  22.         }
复制代码
虽然这里链表里的迭代器看起来很简朴,和vector差不多。但底层是很复杂的,这里也体现了迭代器的重要。为啥我们要用迭代器遍历数据,祖师爷为啥要研究出个迭代器。就是因为,有了迭代器,我们可以统一用法访问任何容器的任何形式数据。不像数组就要用下标,链表就要用指针。不方便程序员的开发。有了迭代器,STL里的任何容器都可以用迭代器访问。从list开始,迭代器才真正开始上难度,而不是简朴的复用指针,这里我们学习list的迭代器的时候,不仅仅要懂list迭代器是如何实现,更要体会的是祖师爷设计的理念和头脑。更重要的是人家这种头脑,极大进步了编程简化。其实学习c++,c++意义不仅仅在利用c++开发软件和编写底层体系上,而且c++的设计的语法和理念为其他语言提供了模板,许多语言都有c++的影子,尤其像java语言,可以说是一个接近满分的抄作业选手。

容量
  1.         bool empty()
  2.         {
  3.                 return _size == 0;
  4.         }
  5.         size_t size()
  6.         {
  7.                 return _size;
  8.         }
复制代码
这里就是简朴返回,没啥好说的。
增删查改
  1. void clear()
  2. {
  3.         iterator it = begin();
  4.         while (it != end())
  5.         {
  6.                 it = erase(it);
  7.         }
  8.         _size = 0;
  9. }
  10. void push_back(const T& x)
  11. {
  12.         /*Node* tail = _head->_prev;
  13.         Node* newNode = new Node(x);
  14.         tail->_next = newNode;
  15.         newNode->_prev = tail;
  16.         newNode->_next = _head;
  17.         _head->_prev = newNode;*/
  18.         insert(end(), x);
  19. }
  20. void push_front(const T& x)
  21. {
  22.         insert(begin(), x);
  23. }
  24. void pop_back()
  25. {
  26.         erase(--end());
  27. }
  28. void pop_front()
  29. {
  30.         erase(begin());
  31. }
  32. iterator insert(iterator pos, const T& x)
  33. {
  34.         Node* cur = pos._node;
  35.         Node* prev = cur->_prev;
  36.         Node* next = cur->_next;
  37.         Node* newNode = new Node(x);
  38.         prev->_next = newNode;
  39.         newNode->_prev = prev;
  40.         newNode->_next = cur;
  41.         cur->_prev = newNode;
  42.         _size++;
  43.         return newNode;
  44. }
  45. iterator erase(iterator pos)
  46. {
  47.         assert(pos != end());
  48.         Node* cur = pos._node;
  49.         Node* prev = cur->_prev;
  50.         Node* next = cur->_next;
  51.         prev->_next = next;
  52.         next->_prev = prev;
  53.         delete cur;
  54.         _size--;
  55.         return next;
  56. }
  57. void swap(list<T>& lt)
  58. {
  59.         std::swap(_head, lt._head);
  60.         std::swap(_size, lt._size);
  61. }
复制代码
这里要说一下,push_back和pop_back分别内部调用了insert和erase, 这里增删查改不消一个一个写,库里就是把大量重复的代码,封装起来。以后利用到这段代码就调用这个封装的函数,险些全是封装。现实上,底层还是那几个指针互换位置。
源码
  1. #pragma once
  2. #include <assert.h>
  3. #include <iostream>
  4. namespace li
  5. {
  6.         template<class T>
  7.         struct list_node
  8.         {
  9.                 list_node<T>* _next;
  10.                 list_node<T>* _prev;
  11.                 T _val;
  12.                 list_node(const T& x = T())
  13.                         :_next(nullptr),
  14.                         _prev(nullptr),
  15.                         _val(x)
  16.                 {
  17.                 }
  18.         };
  19.         template<class T,class Ref,class Ptr>
  20.         struct _list_iterator
  21.         {
  22.                 typedef list_node<T> Node;
  23.                 typedef _list_iterator<T,Ref,Ptr> self;
  24.                 Node* _node;
  25.                 _list_iterator(Node* node)
  26.                         :_node(node)
  27.                 {}
  28.                 Ref operator*()
  29.                 {
  30.                         return _node->_val;
  31.                 }
  32.                 Ptr operator->()
  33.                 {
  34.                         return &(_node->_val);
  35.                 }
  36.                 self& operator++()
  37.                 {
  38.                         _node = _node->_next;
  39.                         return *this;
  40.                 }
  41.                 self& operator++(int)
  42.                 {
  43.                         self tmp(*this);
  44.                         _node = _node->_next;
  45.                         return tmp;
  46.                 }
  47.                 self& operator--()
  48.                 {
  49.                         _node = _node->_prev;
  50.                         return *this;
  51.                 }
  52.                 self& operator--(int)
  53.                 {
  54.                         self tmp(*this);
  55.                         _node = _node->_prev;
  56.                         return tmp;
  57.                 }
  58.                 bool operator!=(const self& it) const
  59.                 {
  60.                         return _node != it._node;
  61.                 }
  62.                 bool operator==(const self& it) const
  63.                 {
  64.                         return _node == it._node;
  65.                 }
  66.         };
  67.         template<class T>
  68.         class list
  69.         {
  70.                 typedef list_node<T> Node;
  71.         public:
  72.                 typedef _list_iterator<T,T&,T*> iterator;
  73.                 typedef _list_iterator<T, const T&,const T*> const_iterator;
  74.                 iterator begin()
  75.                 {
  76.                         return _head->_next; //单参数的构造函数支持隐式类型转换
  77.                         //return iterator(_head->_next);
  78.                 }
  79.                 iterator end()
  80.                 {
  81.                         return _head;
  82.                 }
  83.                 const_iterator begin() const
  84.                 {
  85.                         return _head->_next;
  86.                        
  87.                 }
  88.                 const_iterator end() const
  89.                 {
  90.                         return _head;
  91.                 }
  92.                 void empty_init()
  93.                 {
  94.                         _head = new Node;
  95.                         _head->_next = _head;
  96.                         _head->_prev = _head;
  97.                         _size = 0;
  98.                 }
  99.                 list()
  100.                 {
  101.                         empty_init();
  102.                 }
  103.                 //不能用const引用
  104.                 void swap(list<T>& lt)
  105.                 {
  106.                         std::swap(_head, lt._head);
  107.                         std::swap(_size, lt._size);
  108.                 }
  109.                 list<T>& operator=(list<T> lt)
  110.                 {
  111.                         swap(lt);
  112.                         return *this;
  113.                 }
  114.                 list(const list<T>& lt)
  115.                 {
  116.                         empty_init();
  117.                         for (auto& e : lt)
  118.                         {
  119.                                 push_back(e);
  120.                         }
  121.                 }
  122.                 ~list()
  123.                 {
  124.                         clear();
  125.                         delete _head;
  126.                         _head = nullptr;
  127.                 }
  128.                 bool empty()
  129.                 {
  130.                         return _size == 0;
  131.                 }
  132.                 size_t size()
  133.                 {
  134.                         return _size;
  135.                 }
  136.                 void clear()
  137.                 {
  138.                         iterator it = begin();
  139.                         while (it != end())
  140.                         {
  141.                                 it = erase(it);
  142.                         }
  143.                         _size = 0;
  144.                 }
  145.                 void push_back(const T& x)
  146.                 {
  147.                         /*Node* tail = _head->_prev;
  148.                         Node* newNode = new Node(x);
  149.                         tail->_next = newNode;
  150.                         newNode->_prev = tail;
  151.                         newNode->_next = _head;
  152.                         _head->_prev = newNode;*/
  153.                         insert(end(), x);
  154.                 }
  155.                 void push_front(const T& x)
  156.                 {
  157.                         insert(begin(), x);
  158.                 }
  159.                 void pop_back()
  160.                 {
  161.                         erase(--end());
  162.                 }
  163.                 void pop_front()
  164.                 {
  165.                         erase(begin());
  166.                 }
  167.                 iterator insert(iterator pos, const T& x)
  168.                 {
  169.                         Node* cur = pos._node;
  170.                         Node* prev = cur->_prev;
  171.                         Node* next = cur->_next;
  172.                         Node* newNode = new Node(x);
  173.                         prev->_next = newNode;
  174.                         newNode->_prev = prev;
  175.                         newNode->_next = cur;
  176.                         cur->_prev = newNode;
  177.                         _size++;
  178.                         return newNode;
  179.                 }
  180.                 iterator erase(iterator pos)
  181.                 {
  182.                         assert(pos != end());
  183.                         Node* cur = pos._node;
  184.                         Node* prev = cur->_prev;
  185.                         Node* next = cur->_next;
  186.                         prev->_next = next;
  187.                         next->_prev = prev;
  188.                         delete cur;
  189.                         _size--;
  190.                         return next;
  191.                 }
  192.         private:
  193.                 Node* _head;
  194.                 size_t _size;
  195.         };
  196. }
复制代码
总结

学习list,学会利用是其次,更重要的是理解list底层的编写的理念和设计。这才是最终要的,关于list的学习,此中最重要的就是他的迭代器的设计,这里是最难的。其他部分都是高度的重复,像增删查改就是简朴的链表编写。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表