• 【C++】list基本接口+手撕 list(详解迭代器)


    父母就像迭代器,封装了他们的脆弱...... 

    手撕list目录:

    一、list的常用接口及其使用

    1.1list 构造函数与增删查改

    1.2list 特殊接口

    1.3list 排序性能分析

    二、list 迭代器实现(重点+难点)

    关于迭代器的引入知识:

    2.1迭代器的分类

    2.2 list 迭代器失效问题(和vector有差异)

    2.3list 迭代器源码模板

    2.4list 整体基本框架

    三、手撕list迭代器

    3.1重载operator*()

    3.2重载++、–、!=

    3.3 利用类模板优化

    四、增删查改

    4.1 insert(参数必须加引用,担心非内置类型)和erase

    4.2 push_back和push_front

    4.3  pop_back和pop_front

    五、list 构造+赋值重载

    5.1默认构造+迭代器区间构造+拷贝构造

    5.2 赋值重载现代写法

    5.3 类名和类型的问题(C++的一个坑)

    六、list和vector的对比(重点)

    七、源码合集


    一、list的常用接口及其使用

    1.1list 构造函数与增删查改

    list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,其底层是带头双向循环链表;list 常用接口的使用和 string、vector 系列容器的接口使用一样,这里我不详细介绍,请看我们的老朋友:cplusplus.com - The C++ Resources Network

    构造函数:

    构造函数(constructor)接口说明
    list (size_type n, const value_type& val = value_type())构造的 list 中包含n个值为val的元素
    list()构造空的 list
    构造空的 list拷贝构造函数
    list (InputIterator first, InputIterator last)用 [first, last) 区间中的元素构造 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中的有效元素

    注意:

    1、由于 list 的物理结构是非连续的 – 前一个节点地址和后一个节点地址的位置关系是随机的,所以 list 不支持随机访问,自然也就不支持 [ ] 操作

    2、list 不支持reserve操作,因为 list 的节点是使用时开辟,使用完销毁,不能预留空间;

    (从这个特点也容易看出来,如果需要一直插入删除元素,利用list更好

    1.2list 特殊接口

    除了上述 STL 容器基本都有的一般接口外,list 还提供一些独有的特殊操作接口,如下:

    函数声明接口说明
    splice将 list1 中的元素转移到 list2 中
    remove移除 list 中的指定元素
    unique链表去重(sort之后才可以用)
    merge合并两个链表
    sort链表排序(探究为什么list自己写sort
    reverse链表逆置

    题外话: 为什么list需要自己实现sort接口??难道说库中的封装性不好?效率不高?

     我们先使用库中自己的sort函数:

    我们使用算法库中的sort函数:

    1. void test_sort()
    2. {
    3. list<int> l1{ 5,6,4,8,9,2,7 };//C++ 11写法
    4. sort(l1.begin(),l1.end());
    5. for(auto l : l1)
    6. {
    7. cout << l << " ";
    8. }
    9. cout << endl;
    10. }

    报错了(意外之中,如果不报错我还写这个知识点干啥 doge) 报错原因说没有-迭代器

    让我们看看sort源码~

    这一切的一切都是因为sort的迭代器引起的!! 

    注意:

    1、链表排序只能使用 list 提供的 sort 接口,而不能使用 algorithm 提供的 sort 接口,因为链表物理地址不连续,迭代器为双向迭代器,不支持 + - 操作而算法库中的 sort 函数需要支持 + - 的随机迭代器

    2、链表去重之前必须保证链表有序,否则去重不完全;

    3、两个有序链表合并之后仍然保存有序;

     最后,虽然 list 提供了这些具有特殊功能的接口,它们也确实有一定的作用,但是实际上这些特殊接口使用频率非常低,包括 sort 接口 (链表排序的效率太低)。

    1.3list 排序性能分析

    虽然链表排序只能使用 list 提供的 sort 接口,而不能使用 algorithm 提供的 sort 接口,但是其使用频率仍然非常低,这是由于链表排序的效率太低了,我们可以通过对比两组测试数据来直观的感受链表排序的效率。

    测试一:vector 排序与 list 排序性能对比

    1. //vector sort 和 list sort 性能对比 -- release 版本下
    2. void test_op1() {
    3. srand((size_t)time(0));
    4. const int N = 1000000; //100万个数据
    5. vector<int> v;
    6. v.reserve(N);
    7. list<int> lt;
    8. for (int i = 0; i < N; ++i)
    9. {
    10. auto e = rand();
    11. v.push_back(e);
    12. lt.push_back(e);
    13. }
    14. //vector sort
    15. int begin1 = clock();
    16. sort(v.begin(), v.end());
    17. int end1 = clock();
    18. //list sort
    19. int begin2 = clock();
    20. lt.sort();
    21. int end2 = clock();
    22. printf("vector sort:%d\n", end1 - begin1);
    23. printf("list sort:%d\n", end2 - begin2);
    24. }

    测试二:list 直接进行排序与将数据拷贝到 vector 中使用 vector 排序后再将数据拷回 list 中性能对比

    1. //list sort 与 将数据转移到 vector 中进行排序后拷贝回来性能对比 -- release 版本下
    2. void test_op2()
    3. {
    4. srand(time(0));
    5. const int N = 1000000; //100万个数据
    6. list<int> lt1;
    7. list<int> lt2;
    8. for (int i = 0; i < N; ++i)
    9. {
    10. auto e = rand();
    11. lt1.push_back(e);
    12. lt2.push_back(e);
    13. }
    14. //list sort -- lt1
    15. int begin1 = clock();
    16. lt1.sort();
    17. int end1 = clock();
    18. // 将数据拷贝到vector中排序,排完以后再拷贝回来 -- lt2
    19. int begin2 = clock();
    20. vector<int> v;
    21. v.reserve(N);
    22. for (auto e : lt2) //拷贝
    23. {
    24. v.push_back(e);
    25. }
    26. sort(v.begin(), v.end()); //排序
    27. lt2.assign(v.begin(), v.end()); //拷贝
    28. int end2 = clock();
    29. printf("list1 sort:%d\n", end1 - begin1);
    30. printf("list2 sort:%d\n", end2 - begin2);
    31. }

     可以看到,list sort 的效率远低于 vector sort,甚至于说,直接使用 list sort 的效率都不如先将数据拷贝到 vector 中,然后使用 vector sort,排序之后再将数据拷贝回 list 中快;所以list中的sort接口是很挫的!!


    二、list 迭代器实现(重点+难点)

    关于迭代器的引入知识:

    迭代器的价值在于封装底层的实现,不具体暴露底层的实现细节,提供统一的访问方式

    iterator只是代言人!!真正的牛逼大佬其实是_list_iterator

    为什么在 list 中将迭代器搞成指针这招不好用了呢??

    在数组中,*指针就是元素,指针++就是 +sizeof(T) 对象大小,没办法,谁叫他们物理空间连续,结构NB,所以对于vector和string类而言,物理空间是连续的,原生的指针就是迭代器了,解引用就是数据了但是对于这里的list而言,空间是不连续的

    解决方法:

    此时如果解引用是拿不到数据的(空间不连续),更不用说++指向下一个结点了。所以,对于list的迭代器,原生指针已经不符合我们的需求了,我们需要去进行特殊处理:进行类的封装。我们可以通过类的封装以及运算符重载支持,这样就可以实现像内置类型一样的运算符

    迭代器的俩个特征:

    1.解引用2.++ / --

    运算符重载的大任务:

    实现解引用operator*()和++函数

    2.1迭代器的分类

    按照迭代器的功能,迭代器一共可以分为以下三类:

    • 单向迭代器 – 迭代器仅仅支持 ++ 和解引用操作(单链表,哈希)

    • 双向迭代器 – 迭代器支持 ++、-- 和解引用操作,但不支持 +、- 操作(list 双向链表)

    • 随机迭代器 – 迭代器不仅支持 ++、-- 和解引用操作,还支持 +、- 操作,即迭代器能够随机访问(string,vector)

    这也充分说明,vector和string是可以用库中的sort函数的


    迭代器还可以分成普通迭代器和const迭代器俩类:

    1. //1.const T* p1
    2. list<int>::const_iterator cit = lt.begin();
    3. //2.T* const p2
    4. const list<int>::iterator cit = lt.begin();
    5. //不符合const迭代器的行为,因为保护迭代器本身不能修改,那么我们也就不能++迭代器

    灵魂拷问:const迭代器是p1还是p2?p1

    const迭代器类似p1的行为,保护指向的对象不被修改,迭代器本身可以修改

    2.2 list 迭代器失效问题(和vector有差异)

    vector迭代器失效:insert扩容+erase的时候会失效

    和 vector 不同,list 进行 insert 操作后并不会产生迭代器失效问题,因为 list 插入的新节点是动态开辟的,同时由于 list 每个节点的物理地址是不相关的,所以插入的新节点并不会影响原来其他节点的地址

    但是 list erase 之后会发生迭代器失效,因为 list 删除节点会直接将该节点释放掉,此时我们再访问该节点就会造成越界访问

    2.3list 迭代器源码模板

    我们知道,迭代器是类似于指针一样的东西,即迭代器要能够实现指针相关的全部或部分操作 – ++、–、*、+、-;对我们之前 string 和 vector 的迭代器来说,迭代器就是原生指针,所以它天然的就支持上述操作;

    但是对于 list 来说,list 的节点是一个结构体,同时 list 每个节点的物理地址是不连续的,如果此时我们还简单将节点的指针 typedef 为迭代器的话,那么显然它是不能够实现解引用、++ 等操作的,所以我们需要用结构体/类来对迭代器进行封装,再配合运算符重载等操作让迭代器能够实现解引用、++、-- 等操作

    框架代码如下:

    1. //节点定义
    2. template <class T>
    3. struct __list_node {
    4. typedef void* void_pointer;
    5. void_pointer next;
    6. void_pointer prev;
    7. T data;
    8. };
    9. //迭代器定义
    10. typedef __list_iterator iterator;
    11. typedef __list_iteratorconst T&, const T*> const_iterator;
    12. //迭代器类
    13. template<class T, class Ref, class Ptr>
    14. struct __list_iterator {
    15. typedef __list_iterator self;
    16. typedef __list_node* link_type; //节点的指针
    17. link_type node; //类成员变量
    18. __list_iterator(link_type x) : node(x) {} //将节点指针构造为类对象
    19. //... 使用运算符重载支持迭代器的各种行为
    20. self& operator++() {...}
    21. self& operator--() {...}
    22. Ref operator*() const {...}
    23. };

    2.4list 整体基本框架

    1. namespace lzy
    2. {
    3. //结点
    4. template<class T>
    5. struct list_node
    6. {
    7. list_node* _next;
    8. list_node* _prev;
    9. T _data;
    10. list_node(const T& x)//节点的构造函数及初始化列表
    11. :_next(nullptr)
    12. , _prev(nullptr)
    13. , _data(x)
    14. {}
    15. };
    16. template<class T>
    17. class list
    18. {
    19. typedef list_node node;
    20. public:
    21. //迭代器
    22. typedef __list_iterator iterator;
    23. typedef __list_const_iterator const_iterator;
    24. //构造
    25. list()
    26. {
    27. _head = new node(T());
    28. _head->_next = _head;
    29. _head->_prev = _head;
    30. }
    31. private:
    32. node* _head;
    33. size_t _size;
    34. };
    35. }

    三、手撕list迭代器

    迭代器的实现我们需要去考虑普通迭代器和const迭代器。这两种迭代器的不同,也会带来不同的接口。我们可以分别单独去进行实现,我们先来看一看简单的构造迭代器,只需要提供一个结点即可,看一看实现的基本框架:

    1. template<class T>
    2. struct __list_iterator
    3. {
    4. typedef list_node node;
    5. node* _pnode;
    6. __list_iterator(node* p)
    7. :_pnode(p)
    8. {}
    9. }

    为什么迭代器不写拷贝构造函数?浅拷贝真的可以吗?

    对于迭代器的拷贝构造和赋值重载我们并不需要自己去手动实现,编译器默认生成的就是浅拷贝,而我们需要的就是浅拷贝,这也说明了,并不是说如果有指针就需要我们去实现深拷贝,而且迭代器不需要写析构函数,所以说不需要深拷贝

    为什么聊这个问题?因为list::iterator it=v.begin() 这就是一个拷贝构造

    3.1重载operator*()

    这个比较简单,就是要获取迭代器指向的数据,并且返回数据的引用:

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

     3.2重载++、–、!=

    1. __list_iterator& operator++()
    2. {
    3. _pnode = _pnode->_next;
    4. return *this;
    5. }
    6. __list_iterator& operator--()
    7. {
    8. _pnode = _pnode->_prev;
    9. return *this;
    10. }
    11. bool operator!=(const __list_iterator& it)
    12. {
    13. return _pnode != it._pnode;
    14. }

     如果按照上面的做法,我们在来看看此时普通迭代器和const迭代器的区别:

    1. //typedef __list_iterator iterator;
    2. //typedef __list_const_iterator const_iterator;
    3. template<class T>
    4. struct __list_iterator
    5. {
    6. typedef list_node node;
    7. node* _pnode;
    8. __list_iterator(node* p)
    9. :_pnode(p)
    10. {}
    11. T& operator*()
    12. {
    13. return _pnode->_data;
    14. }
    15. __list_iterator& operator++()
    16. {
    17. _pnode = _pnode->_next;
    18. return *this;
    19. }
    20. __list_iterator& operator--()
    21. {
    22. _pnode = _pnode->_prev;
    23. return *this;
    24. }
    25. bool operator!=(const __list_iterator& it)
    26. {
    27. return _pnode != it._pnode;
    28. }
    29. };
    30. //跟普通迭代器的区别:遍历,不能用*it修改数据
    31. template<class T>
    32. struct __list_const_iterator
    33. {
    34. typedef list_node node;
    35. node* _pnode;
    36. __list_const_iterator(node* p)
    37. :_pnode(p)
    38. {}
    39. const T& operator*()
    40. {
    41. return _pnode->_data;
    42. }
    43. __list_const_iterator& operator++()
    44. {
    45. _pnode = _pnode->_next;
    46. return *this;
    47. }
    48. __list_const_iterator& operator--()
    49. {
    50. _pnode = _pnode->_prev;
    51. return *this;
    52. }
    53. bool operator!=(const __list_const_iterator& it)
    54. {
    55. return _pnode != it._pnode;
    56. }
    57. };

    代码冗余!!!代码冗余!!!代码冗余!!!

    如果是这样子去实现的话,我们就会发现,这两个迭代器的实现并没有多大的区别,唯一的区别就在于operator*的不同。const迭代器和普通迭代器的唯一区别就是普通迭代器返回T&,可读可写,const迭代器返回const T&,可读不可写。我们可以参考源码的实现:类模板参数解决这个问题,这也是迭代器的强大之处

    3.3 利用类模板优化

    1. template <class T,class Ref,class Ptr>
    2. //typedef __list_iterator iterator;
    3. //typedef __list_iterator const_iterator;

    利用类模板参数修正之后的代码:

    1. // typedef __list_iterator iterator;
    2. // typedef __list_iterator const_iterator;
    3. template<class T, class Ref, class Ptr>
    4. struct __list_iterator
    5. {
    6. typedef list_node node;
    7. typedef __list_iterator Self;
    8. node* _pnode;
    9. __list_iterator(node*p)
    10. :_pnode(p)
    11. {
    12. }
    13. //返回数据的指针
    14. Ptr operator->()
    15. {
    16. return &_pnode->_data;
    17. }
    18. //模板参数做返回值
    19. Ref operator *()
    20. {
    21. return _pnode->_data;
    22. }
    23. //++it
    24. Self& operator ++()
    25. {
    26. _pnode = _pnode->_next;
    27. return *this;
    28. }
    29. //it++
    30. Self operator ++(int)
    31. {
    32. Self tmp(*this);
    33. _pnode = _pnode->_next;
    34. return tmp;
    35. }
    36. Self& operator--()
    37. {
    38. _pnode = _pnode->_prev;
    39. return *this;
    40. }
    41. Self operator--(int)
    42. {
    43. Self tmp(*this);
    44. _pnode = _pnode->_prev;
    45. return tmp;
    46. }
    47. bool operator !=(const Self& it)const
    48. {
    49. return _pnode != it._pnode;
    50. }
    51. bool operator ==(const Self& it)const
    52. {
    53. return _pnode == it._pnode;
    54. }
    55. };

    同一个类模板,此时我们传递不同的参数实例化成不同的迭代器了!!!这解决了我们刚刚所说的代码冗余问题


    四、增删查改

    4.1 insert(参数必须加引用,担心非内置类型)和erase

    insert:在pos位置上一个插入,返回插入位置的迭代器,对于list的insert迭代器不会失效,vector失效是因为扩容导致pos位置造成野指针问题

    1. iterator insert(iterator pos,const T& x)
    2. {
    3. node* newnode = new node(x);
    4. node* cur = pos._pnode;
    5. node* prev = cur->_prev;
    6. newnode->_prev = prev;
    7. prev->_next = newnode;
    8. newnode->_next = cur;
    9. cur->_prev = newnode;
    10. ++_size;
    11. return iterator(newnode);
    12. }

     erase:这里的带头(哨兵位)头结点不可删除,返回值是删除位置的下一个,对于list的erase迭代器是失效的

    1. iterator erase(iterator pos)
    2. {
    3. assert(pos != end());
    4. node* prev = pos._pnode->_prev;
    5. node* next = pos._pnode->_next;
    6. prev->_next = next;
    7. next->_prev = prev;
    8. delete pos._pnode;
    9. --_size;
    10. return iterator(next);
    11. }

    4.2 push_back和push_front

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

    注意!list的begin和end的位置

    同时这个问题还可以延伸出另一个问题:为什么迭代器访问元素的时候要这样写?

     在vector中,物理地址是连续的,这么写还情有可原,分析过list的begin和end之后,你还敢这么写吗??

     直接就报错了,所以正确的应该是!=,而不是 <

    1. void test3()
    2. {
    3. vector<int> vv={1,5,7,8,9,3,4};
    4. list<int> l={1,5,6,7};
    5. vector<int>::iterator it1=vv.begin();
    6. list<int>::iterator it2=l.begin();
    7. while(it1 < vv.end())
    8. {
    9. cout << *it1 << " ";
    10. it1++;
    11. }
    12. cout << endl;
    13. // while(it2 < l.end())
    14. // {
    15. // cout << *it2 << " ";
    16. // it2++;
    17. // }
    18. while(it2 != l.end())
    19. {
    20. cout << *it2 << " ";
    21. it2++;
    22. }
    23. cout << endl;
    24. }

    4.3  pop_back和pop_front

    尾删和头删,复用erase即可

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

    这里的尾删刚好用上了我们的重载


    五、list 构造+赋值重载

    5.1默认构造+迭代器区间构造+拷贝构造

    默认构造:

    1. list()
    2. {
    3. _head = new node(T());
    4. _head->_next = _head;
    5. _head->_prev = _head;
    6. _size = 0;
    7. }

    我们可以用empty_initialize()来封装初始化,方便复用,不用每次都写:

    1. void empty_initialize()
    2. {
    3. _head = new node(T());
    4. _head->_next = _head;
    5. _head->_prev = _head;
    6. _size = 0;
    7. }

    迭代器区间构造:

    1. //迭代器区间构造
    2. template <class InputIterator>
    3. list(InputIterator first, InputIterator last)
    4. {
    5. empty_initialize();
    6. while (first != last)
    7. {
    8. push_back(*first);
    9. ++first;
    10. }
    11. }

    拷贝构造:

     传统:
     

    1. list(const list& lt)
    2. {
    3. empty_initialize();
    4. for (const auto& e : lt)
    5. {
    6. push_back(e);
    7. }
    8. }

     用范围for进行尾插,但是要注意要加上&,范围for是*it赋值给给e,又是一个拷贝,e是T类型对象,依次取得容器中的数据,T如果是string类型,不断拷贝,push_back之后又销毁

    现代:

    1. void swap(list& lt)
    2. {
    3. std::swap(_head, lt._head);
    4. std::swap(_size, lt._size);
    5. }
    6. list(const list& lt)
    7. {
    8. empty_initialize();
    9. list tmp(lt.begin(), lt.end());
    10. swap(tmp);
    11. }

    5.2 赋值重载现代写法

    1. list& operator=(list lt)
    2. {
    3. swap(lt);
    4. return *this;
    5. }

    5.3 类名和类型的问题(C++的一个坑)

    查看官方文档,我们可以看到list没有类型:

    list& operator=(list lt)
    list& operator=(list lt) 

    对于普通类:类名等价于类型

    对于类模板:类名不等价于类型(如list模板,类名:list 类型:list)

    类模板里面可以用类名代表类型,但是并不建议,在类外面则必须要带模板参数list


    六、list和vector的对比(重点)

    vectorlist
    底层结构动态顺序表,一段连续空间带头结点的双向循环链表
    随机访问支持随机访问,访问某个元素效率 O(1)不支持随机访问
    插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为 O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为 O(1)
    空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
    迭代器原生态指针对原生态指针 (节点指针) 进行封装
    迭代器失效插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
    使用场景在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效大量插入和删除操作,不关心随机访问

    七、源码合集

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. namespace lzy {
    6. template<class T>
    7. struct list_node //list 节点结构定义
    8. {
    9. list_node* _next;//不加也没错,但是写上好一些
    10. list_node* _prev;
    11. T _data;
    12. list_node(const T& x)//构造
    13. :_next(nullptr)
    14. , _prev(nullptr)
    15. , _data(x)
    16. {}
    17. };
    18. //迭代器最终版
    19. //const 迭代器 -- 增加模板参数,解决 operator*() 返回值与 operator->() 返回值问题
    20. //typedef __list_iterator iterator;
    21. //typedef __list_iterator const_iterator;
    22. //STL源码中大佬的写法,利用多个模板参数来避免副本造成的代码冗余问题
    23. template<class T, class Ref, class Ptr>
    24. struct __list_iterator //迭代器类
    25. {
    26. typedef list_node node; //重命名list节点
    27. typedef __list_iterator Self; //这里进行重命名是为了后续再添加模板参数时只用修改这一个地方
    28. node* _pnode; //节点指针作为类的唯一成员变量
    29. __list_iterator(node* p)
    30. :_pnode(p)
    31. {}
    32. Ref operator*() //解引用
    33. {
    34. return _pnode->_data;
    35. }
    36. Ptr operator->() //->
    37. {
    38. return &_pnode->_data;
    39. }
    40. Self& operator++() //前置++
    41. {
    42. _pnode = _pnode->_next;
    43. return *this;
    44. }
    45. Self& operator++(int) //后置++
    46. {
    47. Self it(*this);
    48. _pnode = _pnode->_next;
    49. return it;
    50. }
    51. Self& operator--() //前置--
    52. {
    53. _pnode = _pnode->_prev;
    54. return *this;
    55. }
    56. Self& operator--(int) //后置--
    57. {
    58. Self it(*this);
    59. _pnode = _pnode->_prev;
    60. return it;
    61. }
    62. bool operator!=(const Self& it) const //!=
    63. {
    64. return _pnode != it._pnode;
    65. }
    66. bool operator==(const Self& it) const //==
    67. {
    68. return _pnode == it._pnode;
    69. }
    70. };
    71. //list 类
    72. template<class T>
    73. class list
    74. {
    75. typedef list_node node; //list 的节点
    76. public:
    77. typedef __list_iterator iterator; //迭代器
    78. typedef __list_iteratorconst T&, const T*> const_iterator; //const 迭代器
    79. //迭代器
    80. iterator begin() {
    81. return iterator(_head->_next);
    82. }
    83. iterator end() {
    84. //iterator it(_head);
    85. //return it;
    86. //直接利用匿名对象更为便捷
    87. return iterator(_head);
    88. }
    89. const_iterator begin() const {
    90. return const_iterator(_head->_next);
    91. }
    92. const_iterator end() const {
    93. return const_iterator(_head);
    94. }
    95. void empty_initialize() { //初始化 -- 哨兵位头结点
    96. _head = new node(T());
    97. _head->_next = _head;
    98. _head->_prev = _head;
    99. _size = 0; //空间换时间,用于标记节点个数
    100. }
    101. list() { //构造,不是list的原因:构造函数函数名和类名相同,而list是类型
    102. empty_initialize();
    103. }
    104. //迭代器区间构造
    105. template <class InputIterator>
    106. list(InputIterator first, InputIterator last) {
    107. empty_initialize();
    108. while (first != last)
    109. {
    110. push_back(*first);
    111. ++first;
    112. //first++;
    113. }
    114. }
    115. //拷贝构造传统写法
    116. //list(const list& lt) {
    117. // empty_initialize();
    118. // for (const auto& e : lt)
    119. // {
    120. // push_back(e);
    121. // }
    122. //}
    123. // 拷贝构造的现代写法
    124. //list(const list& lt) 官方库是这样写的,这是由于在类内类名等价于类型,但不建议自己这样写
    125. list(const list& lt) {
    126. empty_initialize(); //初始化头结点,防止交换后tmp野指针不能正常的调用析构
    127. list tmp(lt.begin(), lt.end());
    128. swap(tmp);
    129. }
    130. //赋值重载传统写法
    131. //list& operator=(const list& lt) {
    132. // if (this != <)
    133. // {
    134. // clear();
    135. // for (const auto& e : lt)
    136. // {
    137. // push_back(e);
    138. // }
    139. // }
    140. // return *this;
    141. //}
    142. //赋值重载现代写法
    143. //list& operator=(list lt)
    144. list& operator=(list lt) { //不能加引用,lt是调用拷贝构造生成的
    145. swap(lt);
    146. return *this;
    147. }
    148. ~list() { //析构
    149. clear();
    150. delete _head;
    151. _head = nullptr;
    152. }
    153. void swap(list& lt) { //交换两个链表,本质上是交换两个链表的头结点
    154. std::swap(_head, lt._head);
    155. std::swap(_size, lt._size);
    156. }
    157. size_t size() const { //增加一个计数的成员,以空间换时间
    158. return _size;
    159. }
    160. bool empty() { //判空
    161. return _size == 0;
    162. }
    163. void clear() {
    164. iterator it = begin();
    165. while (it != end()) {
    166. it = erase(it);
    167. }
    168. _size = 0;
    169. }
    170. void push_back(const T& x) {
    171. //node* newnode = new node(x);
    172. //node* tail = _head->_prev;
    173. //tail->_next = newnode;
    174. //newnode->_prev = tail;
    175. //newnode->_next = _head;
    176. //_head->_prev = newnode;
    177. insert(end(), x); //复用
    178. }
    179. void push_front(const T& x) {
    180. insert(begin(), x); //复用
    181. }
    182. void pop_front() {
    183. erase(begin());
    184. }
    185. void pop_back() {
    186. erase(--end());
    187. }
    188. iterator insert(iterator pos, const T& x) {
    189. node* newnode = new node(x);
    190. node* cur = pos._pnode;
    191. node* prev = cur->_prev;
    192. prev->_next = newnode;
    193. newnode->_prev = prev;
    194. cur->_prev = newnode;
    195. newnode->_next = cur;
    196. ++_size;
    197. return iterator(pos);
    198. }
    199. iterator erase(iterator pos) {
    200. assert(pos != end());
    201. node* prev = pos._pnode->_prev;
    202. node* next = pos._pnode->_next;
    203. prev->_next = next;
    204. next->_prev = prev;
    205. delete pos._pnode;
    206. --_size;
    207. return iterator(next);
    208. }
    209. private:
    210. node* _head;
    211. size_t _size;
    212. };
    213. }

     

    完结撒花~

  • 相关阅读:
    UniApp项目实践HelloUni
    蓝牙耳机什么牌子音质最好?高音质蓝牙耳机盘点
    centos环境用docker安装jenkins相关命令
    Python基础快速入门
    2、kubeadm安装单master集群
    临时记录一下
    MES生产管理系统是什么?有ERP系统了为什么还要上
    【51单片机】7-LED点阵
    基于javaweb的酒店客房预订管理系统
    Oracle 排查慢SQL
  • 原文地址:https://blog.csdn.net/weixin_62985813/article/details/133713234