• 【C++】list的模拟实现【完整理解版】


    目录

    一、list的概念引入

    1、vector与list的对比

     2、关于struct和class的使用

    3、list的迭代器失效问题 

     二、list的模拟实现

    1、list三个基本函数类

    2、list的结点类的实现

    3、list的迭代器类的实现 

    3.1 基本框架

    3.2构造函数 

    3.3 operator*

    3.4 operator->

    3.5 operator前置++和--与后置++和-- 

    3.5 operator==与operator!=

    4、list类的实现

     4.1 基本框架

    4.2 构造函数 

    4.2 begin()和end()

    4.3 拷贝构造

    4.4  clear

    4.5 operator=

    4.6  析构函数

     4.7 insert

    4.8 push_back 和 push_front

    4.9 erase

    4.10 pop_back 和 pop_front

    三、完整源代码:

    list.h:

     test.cpp:


    一、list的概念引入

    1、vector与list的对比

     两者的区别十分类似于顺序表和链表的区别

    STL中的list底层其实就是带哨兵位循环双向链表而已


     2、关于struct和class的使用

    C++中的structclass的唯一区别在于默认访问限定符(即你不写public、private这种访问限定符)不同,struct默认为公有(public),而class默认为私有(private),一般情况,成员部分私有,部分共有,就用class,所有成员都开放或共有,就用struct

    所以下文写的节点和迭代器类都用struct是因为,struct中的成员函数默认为公有了,也不用写public了,但是你用class就要写个public


    3、list的迭代器失效问题 

     list本质:带头双向循环链表 

    支持操作接口的角度分迭代器的类型:单向(forward_list)、双向(list)、随机(vector)

    从使用场景的角度分迭代器的类型:(正向迭代器,反向迭代器)+const迭代器

     迭代器失效本质内存空间的问题,失效原因:

    1、增容完还指向旧空间

    2、节点已经被释放(list里面)

    list的erase迭代器失效

    注意:删除不会报错,迭代器失效也不会报错,是删除以后迭代器失效了,是去访问失效的迭代器才会报错

    插入知识点(库里面的find实现,可不看


     二、list的模拟实现

    1、list三个基本函数类

    list本质是一个带头双向循环链表:

    模拟实现list,要实现下列三个类

    ①、模拟实现结点类
    ②、模拟实现迭代器的类
    ③、模拟list主要功能的类
    list的类的模拟实现其基本功能(增删等操作)要建立在迭代器类和结点类均已实现好的情况下才得以完成。

    2、list的结点类的实现

    因为list的本质为带头双向循环链表,所以其每个结点都要确保有下列成员:

    1. 前驱指针
    2. 后继指针
    3. data值存放数据

    而结点类的内部只需要实现一个构造函数即可。

    1. //1、结点类
    2. template<class T>
    3. struct __list_node//前面加__是命名习惯,一般这么写表示是给别人用的
    4. {
    5. //前驱指针和后驱指针
    6. __list_node* _next;//C++中可不写struct,直接用名定义,但注意这里还要加类型
    7. __list_node* _prev;
    8. T _data;//存节点的值
    9. //构造函数
    10. __list_node(const T& val = T())//给一个缺省值T()
    11. :_next(nullptr)
    12. , _prev(nullptr)
    13. , _data(val)
    14. {}
    15. };

    ①、为什么是__list_node

    首先,C++中用struct定义时可不加struct,重点是这里用了一个类模板,类模板的类名不是真正的类型且类模板不支持自动推类型,即__list_node不是真正的类型,定义变量时__list_node这种才是真正的类型,也就是用类模板定义变量时必须 指定对应的类型 。 注:结构体模板或类模板在定义时可以不加 ,但 使用时必须加


    3、list的迭代器类的实现 

    因为list其本质是带头双向循环链表,而链表的物理空间是不连续的,是通过结点的指针顺次链接,我们不能像先前的string和vector一样直接解引用去访问其数据,结点的指针解引用还是结点,结点指针++还是结点指针,而string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。

    为了能让list像vector一样解引用后访问对应节点中的值,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。 

    注:迭代器封装后是想模拟指针的行为 

    3.1 基本框架

    1. template<class T,class Ref,class Ptr>
    2. struct __list_iterator
    3. {
    4. typedef __list_node Node;
    5. typedef __list_iterator Self;
    6. Node* _node;
    7. }

    ①、迭代器类模板为什么要三个参数?

    若只有普通迭代器的话,一个class T参数就够了,但因为有const迭代器原因,需要加两个参数,两个参数名Ref(reference:引用)和Ptr(pointer:指针),名字怎么起都行,但这种有意义的名字是很推荐的,即这两个参数一个让你传引用,一个让你传指针,具体等下文讲到const迭代器再说

    ②、迭代器类到底是什么?

    迭代器类就一个节点的指针变量_node,但是因为我们要运算符重载等一系列操作,不得不把list的迭代器写成类,完成那些操作,list的迭代器才能正确的++到下一位置,解引用访问节点的值

    ③、节点指针和迭代器的区别?


    3.2构造函数 

    1. //迭代器的构造函数只需要一个指针构造
    2. __list_iterator (Node* node)
    3. :_node(node)
    4. { }

    3.3 operator*

    1. //*it(调用的是函数,返回节点中的值)
    2. Ref operator*()
    3. {//出了作用域还在,引用返回
    4. return _node->_data;
    5. }

    ①、 返回值为什么是Ref?

    Ref是模板参数,因为迭代器类的模板参数Ref传入的要么是T&要么是const T&,就是为了const迭代器和普通迭代器的同时实现,底层就是这么实现的,意义就是一个只读,一个可读可写 

    注:比如之前讲的vector的迭代器,*it(假设it是迭代器变量)就是拿到对应的值,那么list的迭代器也要同理,解引用迭代器就是为了访问对应位置的值,那么list只要通过迭代器返回对应节点的值就好了(*it,我们是就想要对应的值)


    3.4 operator->

    1. Ptr operator->()
    2. {
    3. return &_node->_data;
    4. }

    ①、为什么需要operator->?

    本质因为自定义类型需要,那需从list存的类型是个自定义类型说起,以Date类型为例

     若list存了个自定义类型的Date类,程序错误,因为我们并没有重载Date类的operator<<,若是内置类型的话才可以正常输出,那写一个operator<<重载吗?不,因为你无法确定要用哪些类,也不能每个类都写operator<<,那怎么办?我们访问Date类本质是想访问它内置类型(int)的_year、_month和_day吧,那我们不妨写个专属于自定义类型的operator->(因为内置类型只需要*it就可以直接输出了,但自定义类型不可以直接输出),利用operator->直接访问类的成员变量,而内置类型可以直接输出

    从根源上解决问题: 在迭代器中实现个operator->:

    (Ptr是迭代器的模板参数,我们用来作为T*或const T*的)


    3.5 operator前置++和--与后置++和-- 

    1. //++it;(迭代器++本质就是指针往后移,加完后还应是个迭代器)
    2. Self& operator++()
    3. {
    4. _node = _node->_next;
    5. return *this;
    6. }
    7. //it++;
    8. Self operator++(int)//加参数以便于区分前置++
    9. {
    10. Self tmp(*this);//拷贝构造tmp
    11. _node = _node->_next;//直接让自己指向下一个结点即可实现++
    12. return tmp;//注意返回tmp,才是后置++
    13. }
    14. //--it;
    15. Self& operator--()
    16. {
    17. _node = _node->_prev;//让_node指向上一个结点即可实现--
    18. return *this;
    19. }
    20. //it--;
    21. Self operator--(int)//记得传缺省值以区分前置--
    22. {
    23. Self tmp(*this);//拷贝构造tmp
    24. _node = _node->_prev;
    25. return tmp;
    26. }

    ①、迭代器++对于list是什么意思?

    迭代器++的意思就是想让其指向下一个节点,--正好相反,为了区分前置和后置++(--),我们会用函数重载,也就是多一个“没用的”参数:int,这个参数没什么用,只是为了区分++与--


    3.5 operator==与operator!=

    1. //it != end()
    2. bool operator!=(const Self& it)
    3. {
    4. //迭代器是否相等只要判断对应节点地址是否相等即可
    5. return _node != it._node;
    6. }
    7. bool operator==(const Self& it)
    8. {
    9. return _node == it._node;
    10. }

    ①、两个迭代器怎么比较的?

    迭代器中就一个成员变量_node,节点指针,只要比较当前的节点指针是否相同即可,这个操作在判断迭代器是否走到头有用(比如 it != s.end())


    问题:迭代器的拷贝构造和赋值和析构函数需我们自己实现吗?

     答:不需要

    因为迭代器存的就是一个节点指针,而节点是属于链表list的,那么它的释放应由list来实现,那么迭代器的析构也无需我们自己写了。且拷贝构造和赋值就是节点指针的拷贝和赋值,即使指向同一节点了,迭代器也不会析构,而list的析构到时候只会释放这个节点一次,不存在什么问题,即我们无需深拷贝,使用系统提供的浅拷贝即可。


    4、list类的实现

     在结点类和迭代器类都实现的前提下,就可实现list主要功能:增删等操作的实现

     4.1 基本框架

    1. //3、链表类
    2. template<class T>
    3. class list
    4. {
    5. typedef __list_node Node;
    6. public:
    7. typedef __list_iterator iterator;
    8. typedef __list_iteratorconst T&,const T*> const_iterator;
    9. private:
    10. Node* _head;//头结点
    11. }

    ①、const_iterator(const迭代器的介绍)

    我们知道const_iterator在begin()和end()中的返回值是需要用到的,其主要作用就是当迭代器只读时使用, 因为普通迭代器和const迭代器的实现区别仅仅在于内部成员函数的返回值不同,难道重写一遍吗?不用,我们模板参数多两个就好了,一个是引用class Ref(T&或const T&),一个是指针class Ptr(T*或const T*),当Ref时const T&就是const迭代器的调用,当Ref时T& 时就是普通迭代器的调用,这就利用模板实现了两个迭代器的同时实现


    4.2 构造函数 

    1. //带头双向循环链表的构造函数
    2. list()
    3. {
    4. _head = new Node;
    5. _head->_next = _head;
    6. _head->_prev = _head;
    7. }

    解释:我们开辟一个头结点,然后使其处于一个对应的初始状态即可


    4.2 begin()和end()

    1. iterator begin()
    2. {
    3. //第一个位置应该是头结点的下一个节点
    4. return iterator(_head->_next);//用匿名对象构造iterator类型的
    5. }
    6. iterator end()
    7. {
    8. //最后一个数据的下一个位置应该是第一个节点,即头结点
    9. return iterator(_head);
    10. }
    11. const_iterator begin()const
    12. {
    13. return const_iterator(_head->_next);
    14. }
    15. const_iterator end()const
    16. {
    17. return const_iterator(_head);
    18. }

    ①、关于匿名构造的理解

    比如 iterator(_head->_next); iterator是个类模板类型(它被typedef过的),那不应该实例化一个对象再构造吗?这里没有用是因为这里是匿名对象的构造,这里这么用比较方便


    4.3 拷贝构造

    1. //拷贝构造:传统写法
    2. list(const list& lt)
    3. {
    4. _head = new Node;//开辟一样的头结点
    5. _head->_next = _head;
    6. _head->_prev = _head;
    7. //1、用迭代器遍历
    8. /*const_iterator it = lt.begin();
    9. while (it != lt.end())
    10. {
    11. push_back(*it);
    12. ++it;
    13. }*/
    14. //2、范围for遍历
    15. //遍历lt1,把lt1的元素push_back到lt2里头
    16. for (auto e : lt)
    17. {
    18. push_back(e);//自动开辟新空间,完成深拷贝
    19. }
    20. }

     解释:list的拷贝构造跟vector不同,它的拷贝是拷贝一个个节点(因为不连续的物理空间), 那么我们可以用迭代器拿到list的一个个节点【上面是传统写法

    现代写法:

    1. template<class InputIterator>
    2. list(InputIterator first, InputIterator last) {
    3. _pHead = new Node();
    4. _pHead->_next = _pHead;
    5. _pHead->_prev = _pHead;
    6. while (first != last) {
    7. push_back(*first);
    8. first++;
    9. }
    10. }
    11. /* 拷贝构造(现代写法):L2(L1) */
    12. list(const list& L) {
    13. _pHead = new Node();
    14. _pHead->_next = _pHead;
    15. _pHead->_prev = _pHead;
    16. list tmp(L.begin(), L.end());
    17. swap(_pHead, tmp._pHead); // 交换两个结点的指针
    18. }

    ①、为什么有的拷贝构造需初始化,operator=不需要?

    以string的模拟实现为例

    这里为什么s2要初始化?

    是因为string的模拟实现有交换操作,而list的传统写法无需交换,开辟一个头结点即可

    因为s2是要被拷贝构造出来的,没被拷贝构造前还没存在,然后s2要跟tmp交换,如果也就是tmp得到s2的数据,如果s2之前没初始化,析构销毁就出问题了,因为没初始化是随机值

    但是赋值不一样,赋值是两个对象都存在,不存在随机值问题,所以不用一上来就初始化


    4.4  clear

    1. void clear()
    2. {//clear不删除头结点,因为万一删除了头结点你还想插入数据怎么办
    3. iterator it = begin();
    4. while (it != end())
    5. {
    6. //法一、通过返回值针对迭代器失效问题
    7. it = erase(it);
    8. //法二、直接++针对迭代器失效问题
    9. //erase(it++); //这里一定不能写为erase(it); it++;这样迭代器已经失效了,不能++
    10. }
    11. }

    ①、 it = erase(it)什么意思?

    防止迭代器失效,因为erase返回的是被删除位置的下一位置,比如删除pos位置的,pos位置被删除后,这个位置的迭代器就失效了,那就无法通过++找到后面的位置了,所以我们通过erase返回值来更新一下迭代器位置,即更新到被删除位置的下一位置


    4.5 operator=

    1. //赋值运算符重载(传统写法)
    2. //lt1 = lt3
    3. //list& operator=(const list& lt)
    4. //{
    5. // if (this != <)
    6. // {
    7. // clear();//先释放lt1
    8. // for (auto e : lt)
    9. // push_back(e);
    10. // }
    11. // return *this;
    12. //}
    13. //赋值运算符重载(现代写法)
    14. //lt1 = lt3
    15. list& operator=(list lt)//套用传值传参去拷贝构造完成深拷贝
    16. {
    17. swap(_head,lt._head);//交换两个list的头结点即可
    18. //lt出了作用域,析构函数销毁lt1原来的链表,一举两得
    19. //swap(lt);
    20. return *this;
    21. }

    注:传统写法要先把被赋值的对象先释放,然后利用push_bak尾插,push_back在下文说明


    4.6  析构函数

    1. //析构函数
    2. ~list()
    3. {
    4. clear();//删除除头结点以外的节点
    5. delete _head;//删去哨兵位头结点
    6. _head = nullptr;
    7. }

     4.7 insert

    1. //insert,插入pos位置之前
    2. iterator insert(iterator pos, const T& x)
    3. {
    4. Node* newnode = new Node(x);//创建新的结点
    5. Node* cur = pos._node; //迭代器pos处的结点指针
    6. Node* prev = cur->_prev;
    7. //prev newnode cur
    8. //链接prev和newnode
    9. prev->_next = newnode;
    10. newnode->_prev = prev;
    11. //链接newnode和cur
    12. newnode->_next = cur;
    13. cur->_prev = newnode;
    14. //返回新插入元素的迭代器位置
    15. return iterator(newnode);
    16. }

    ①、返回参数为什么是iterator?

    本质是为了防止迭代器失效问题

    注:insert指的是插入到指定位置的前面


    4.8 push_back 和 push_front

    1. //尾插
    2. void push_back(const T& x)
    3. {
    4. Node* tail = _head->_prev;//找尾
    5. Node* newnode = new Node(x);//创建一个新的结点
    6. //_head tail newnode
    7. //使tail和newnode构成循环
    8. tail->_next = newnode;
    9. newnode->_prev = tail;
    10. //使newnode和头结点_head构成循环
    11. newnode->_next = _head;
    12. _head->_prev = newnode;
    13. }
    14. //头插
    15. void push_front(const T& x)
    16. {
    17. insert(begin(), x);
    18. }

    4.9 erase

    1. //erase
    2. iterator erase(iterator pos)
    3. {
    4. assert(pos != end());
    5. Node* cur = pos._node;
    6. Node* prev = cur->_prev;
    7. Node* next = cur->_next;
    8. //prev cur next
    9. //链接prev和next
    10. prev->_next = next;
    11. next->_prev = prev;
    12. //delete删去结点,因为每一个节点都是动态开辟出来的
    13. delete cur;
    14. //返回被删除元素后一个元素的迭代器位置
    15. //return next;
    16. return iterator(next);
    17. }

    ①、 返回值问题

    erase的返回值,返回的是被删除位置的下一位置,库里面这么规定的,本质是因为删除元素一定会导致此位置的迭代器失效,故返回被删除位置的下一次位置可以更新迭代器


    4.10 pop_back 和 pop_front

    1. //尾删
    2. void pop_back()
    3. {
    4. erase(--end());
    5. //erase(iterator(_head->prev));//构造个匿名对象
    6. }
    7. //头删
    8. void pop_front()
    9. {
    10. erase(begin());
    11. }

    最后, list的排序意义不大,因为实际生活中我们都是对数组等排序

    三、完整源代码:

    list.h:

    1. #pragma once
    2. namespace mz
    3. {
    4. //1、结点类
    5. template<class T>
    6. struct __list_node//前面加__是命名习惯,一般这么写表示是给别人用的
    7. {
    8. //前驱指针和后驱指针
    9. __list_node* _next;//C++中可不写struct,直接用名定义,但注意这里还要加类型
    10. __list_node* _prev;
    11. T _data;//存节点的值
    12. //构造函数
    13. __list_node(const T& val = T())//给一个缺省值T()
    14. :_next(nullptr)
    15. , _prev(nullptr)
    16. , _data(val)
    17. {}
    18. };
    19. //2、迭代器类
    20. //__list_iterator -> iterator
    21. //__list_iterator -> const_iterator
    22. template<class T,class Ref,class Ptr>
    23. struct __list_iterator
    24. {
    25. typedef __list_node Node;
    26. typedef __list_iterator Self;
    27. Node* _node;
    28. //迭代器的构造函数只需要一个指针构造
    29. __list_iterator (Node* node)
    30. :_node(node)
    31. { }
    32. //*it(调用的是函数,返回节点中的值)
    33. Ref operator*()
    34. {//出了作用域还在,引用返回
    35. return _node->_data;
    36. }
    37. Ptr operator->()
    38. {
    39. return &_node->_data;
    40. }
    41. //++it;(迭代器++本质就是指针往后移,加完后还应是个迭代器)
    42. Self& operator++()
    43. {
    44. _node = _node->_next;
    45. return *this;
    46. }
    47. //it++;
    48. Self operator++(int)//加参数以便于区分前置++
    49. {
    50. Self tmp(*this);//拷贝构造tmp
    51. _node = _node->_next;//直接让自己指向下一个结点即可实现++
    52. return tmp;//注意返回tmp,才是后置++
    53. }
    54. //--it;
    55. Self& operator--()
    56. {
    57. _node = _node->_prev;//让_node指向上一个结点即可实现--
    58. return *this;
    59. }
    60. //it--;
    61. Self operator--(int)//记得传缺省值以区分前置--
    62. {
    63. Self tmp(*this);//拷贝构造tmp
    64. _node = _node->_prev;
    65. return tmp;
    66. }
    67. //it != end()
    68. bool operator!=(const Self& it)
    69. {
    70. //迭代器是否相等只要判断对应节点地址是否相等即可
    71. return _node != it._node;
    72. }
    73. bool operator==(const Self& it)
    74. {
    75. return _node == it._node;
    76. }
    77. };
    78. //3、链表类
    79. template<class T>
    80. class list
    81. {
    82. typedef __list_node Node;
    83. public:
    84. typedef __list_iterator iterator;
    85. typedef __list_iteratorconst T&,const T*> const_iterator;
    86. iterator begin()
    87. {
    88. //第一个位置应该是头结点的下一个节点
    89. return iterator(_head->_next);//用匿名对象构造iterator类型的
    90. }
    91. iterator end()
    92. {
    93. //最后一个数据的下一个位置应该是第一个节点,即头结点
    94. return iterator(_head);
    95. }
    96. const_iterator begin()const
    97. {
    98. return const_iterator(_head->_next);
    99. }
    100. const_iterator end()const
    101. {
    102. return const_iterator(_head);
    103. }
    104. //带头双向循环链表的构造函数
    105. list()
    106. {
    107. _head = new Node;
    108. _head->_next = _head;
    109. _head->_prev = _head;
    110. }
    111. //拷贝构造:传统写法
    112. list(const list& lt)
    113. {
    114. _head = new Node;//开辟一样的头结点
    115. _head->_next = _head;
    116. _head->_prev = _head;
    117. //1、用迭代器遍历
    118. /*const_iterator it = lt.begin();
    119. while (it != lt.end())
    120. {
    121. push_back(*it);
    122. ++it;
    123. }*/
    124. //2、范围for遍历
    125. //遍历lt1,把lt1的元素push_back到lt2里头
    126. for (auto e : lt)
    127. {
    128. push_back(e);//自动开辟新空间,完成深拷贝
    129. }
    130. }
    131. //赋值运算符重载(传统写法)
    132. //lt1 = lt3
    133. //list& operator=(const list& lt)
    134. //{
    135. // if (this != <)
    136. // {
    137. // clear();//先释放lt1
    138. // for (auto e : lt)
    139. // push_back(e);
    140. // }
    141. // return *this;
    142. //}
    143. //赋值运算符重载(现代写法)
    144. //lt1 = lt3
    145. list& operator=(list lt)//套用传值传参去拷贝构造完成深拷贝
    146. {
    147. swap(_head,lt._head);//交换两个list的头结点即可
    148. //lt出了作用域,析构函数销毁lt1原来的链表,一举两得
    149. //swap(lt);
    150. return *this;
    151. }
    152. //析构函数
    153. ~list()
    154. {
    155. clear();//删除除头结点以外的节点
    156. delete _head;//删去哨兵位头结点
    157. _head = nullptr;
    158. }
    159. void clear()
    160. {//clear不删除头结点,因为万一删除了头结点你还想插入数据怎么办
    161. iterator it = begin();
    162. while (it != end())
    163. {
    164. it = erase(it);
    165. }
    166. }
    167. //尾插
    168. void push_back(const T& x)
    169. {
    170. Node* tail = _head->_prev;//找尾
    171. Node* newnode = new Node(x);//创建一个新的结点
    172. //_head tail newnode
    173. //使tail和newnode构成循环
    174. tail->_next = newnode;
    175. newnode->_prev = tail;
    176. //使newnode和头结点_head构成循环
    177. newnode->_next = _head;
    178. _head->_prev = newnode;
    179. }
    180. //头插
    181. void push_front(const T& x)
    182. {
    183. insert(begin(), x);
    184. }
    185. //insert,插入pos位置之前
    186. iterator insert(iterator pos, const T& x)
    187. {
    188. Node* newnode = new Node(x);//创建新的结点
    189. Node* cur = pos._node; //迭代器pos处的结点指针
    190. Node* prev = cur->_prev;
    191. //prev newnode cur
    192. //链接prev和newnode
    193. prev->_next = newnode;
    194. newnode->_prev = prev;
    195. //链接newnode和cur
    196. newnode->_next = cur;
    197. cur->_prev = newnode;
    198. //返回新插入元素的迭代器位置
    199. return iterator(newnode);
    200. }
    201. //erase
    202. iterator erase(iterator pos)
    203. {
    204. assert(pos != end());
    205. Node* cur = pos._node;
    206. Node* prev = cur->_prev;
    207. Node* next = cur->_next;
    208. //prev cur next
    209. //链接prev和next
    210. prev->_next = next;
    211. next->_prev = prev;
    212. //delete删去结点,因为每一个节点都是动态开辟出来的
    213. delete cur;
    214. //返回被删除元素后一个元素的迭代器位置
    215. //return next;
    216. return iterator(next);
    217. }
    218. //尾删
    219. void pop_back()
    220. {
    221. erase(--end());
    222. //erase(iterator(_head->prev));//构造个匿名对象
    223. }
    224. //头删
    225. void pop_front()
    226. {
    227. erase(begin());
    228. }
    229. private:
    230. Node* _head;//头结点
    231. };
    232. void test1()
    233. {
    234. list<int>lt;
    235. lt.push_back(1);
    236. lt.push_back(2);
    237. lt.push_back(3);
    238. lt.push_back(4);
    239. //类外访问迭代器需要指定类域,类内访问可直接访问
    240. list<int>::iterator it = lt.begin();
    241. while (it != lt.end())
    242. {
    243. cout << *it << " ";
    244. ++it;
    245. }
    246. cout << endl;
    247. }
    248. struct Date
    249. {
    250. int _year = 0;
    251. int _month = 1;
    252. int _day = 1;
    253. };
    254. void test2()
    255. {
    256. Date* p2 = new Date;
    257. *p2;//取到的是Date
    258. p2->_year;//取到的是Date类中的成员变量
    259. listlt;
    260. lt.push_back(Date());
    261. lt.push_back(Date());
    262. //list存了个日期类(自定义类型)的类型
    263. list::iterator it = lt.begin();
    264. while (it != lt.end())
    265. {
    266. //cout << *it << " ";
    267. cout << it->_year << "-" << it->_month << "-" << it->_day << endl;
    268. ++it;
    269. }
    270. cout << endl;
    271. }
    272. void print_list(const list<int>& lt)
    273. {
    274. list<int>::const_iterator it = lt.begin();
    275. while (it != lt.end())
    276. {
    277. cout << *it << " ";
    278. ++it;
    279. }
    280. cout << endl;
    281. }
    282. void test3()
    283. {
    284. list<int>lt1;
    285. lt1.push_back(1);
    286. lt1.push_back(2);
    287. lt1.push_back(3);
    288. lt1.push_back(4);
    289. list<int> lt2(lt1);
    290. print_list(lt2);
    291. list<int>lt3;
    292. lt3.push_back(10);
    293. lt3.push_back(20);
    294. lt3.push_back(30);
    295. lt3.push_back(40);
    296. lt1 = lt3;
    297. print_list(lt1);
    298. }
    299. }

     test.cpp:

    1. #include
    2. #include
    3. using namespace std;
    4. #include"list.h"
    5. int main()
    6. {
    7. //mz::test1();
    8. mz::test3();
    9. return 0;
    10. }

  • 相关阅读:
    【pytorch08】拼接与拆分
    iOS打包错误The operation couldn’t be completed. (AppThinning.StubError error 1.)
    【Linux】安装mysql
    蓝桥杯每日一题2023.11.19
    基于GeoToolkit/INT实现二维等值线图绘制示例
    火山引擎 RTC 自研音频编码器 NICO 实践之路
    【深度学习】(五)目标检测——下篇
    五眼联盟指的是什么?台湾社会中的“特助”指的是什么?计算机组成原理人工智能
    MIPI CSI-2笔记(7) -- Low Level Protocol(C-PHY物理层校验和生成,包间隔)
    【C++】C++面向对象编程三大特性之一——继承
  • 原文地址:https://blog.csdn.net/m0_74044018/article/details/132780271