cplusplus.com/reference/list/list/?kw=list

- list 是可以在常数时间 O(1) 内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list 的底层是带头双向循环链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
- list 与 forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,不支持尾插、尾删,对比双向链表的唯一优势就是每个节点少存一个指针。但是forward_list 在实际中用的很少,单链表尾插和尾删效率太低,尤其是尾删,不实用。
![]()
- 与其他的序列式容器相比(array,vector,deque),list 在容器内任意位置进行插入、删除、移动元素方面的执行效率通常表现更好。
- 与其他序列式容器相比,list 和 forward_list 最大的缺陷是不支持任意位置的随机访问,比如:要访问 list 的第 6 个元素,必须从已知的位置(比如头部 / 尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list 还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大 list 来说这可能是一个重要的因素)。

list 中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为 list 中一些常见的重要接口。

- // list的构造
- void TestL1()
- {
- list<int> l1; // 构造空的l1
- list<int> l2(4, 100); // l2中放4个值为100的元素
- list<int> l3(l2.begin(), l2.end()); // 用l2的[begin(), end())左闭右开的区间构造l3
- list<int> l4(l3); // 用l3拷贝构造l4
-
- // 以数组为迭代器区间构造l5
- int array[] = {16, 2, 77, 29};
- list<int> l5(array, array + sizeof(array) / sizeof(int));
-
- // 列表格式初始化C++11
- list<int> l6{1, 2, 3, 4, 5};
- }
可以暂时将迭代器理解成一个指针,该指针指向 list 中的某个节点。


- // list迭代器的使用
- void PrintList(const list<int>& l)
- {
- // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
- for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
- {
- cout << *it << " ";
- // *it = 10; // 不能改变该值 -- 否则编译不通过
- }
- cout << endl;
- }
- void TestL2()
- {
- int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
- list<int> l(array, array + sizeof(array) / sizeof(array[0]));
-
- // 使用正向迭代器正向list中的元素
- // list
::iterator it = l.begin(); // C++98中语法 - auto it = l.begin(); // C++11之后推荐写法
- while (it != l.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
-
- // 使用反向迭代器逆向打印list中的元素
- // list
::reverse_iterator rit = l.rbegin(); - auto rit = l.rbegin();
- while (rit != l.rend())
- {
- cout << *rit << " ";
- ++rit;
- }
- cout << endl;
-
- // 以数组为迭代器区间构造l5
- list<int> l(array, array + sizeof(array) / sizeof(int));
-
- // 用迭代器方式打印元素
- list<int>::iterator it = l.begin();
- while (it != l.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
-
- // C++11范围for的方式遍历
- for (auto& e : l)
- {
- cout << e << " ";
- }
- cout << endl;
- }
遍历链表只能用迭代器和范围for。
注意 :
- begin 与 end 为正向迭代器,对迭代器执行 ++ 操作,迭代器向后移动。
- rbegin(end) 与 rend(begin) 为反向迭代器,对迭代器执行 ++ 操作,迭代器向前移动。
cplusplus.com/reference/list/list/empty/
cplusplus.com/reference/list/list/size/



list 不支持 [] 运算符,因为 list 容器物理地址不是连续的。
cplusplus.com/reference/list/list/front/
cplusplus.com/reference/list/list/back/




注意:list 可以在任意位置进行 O(1) 的插入和删除操作。
- // list插入和删除
- // push_back/pop_back/push_front/pop_front
- void TestL3()
- {
- int array[] = {1, 2, 3};
- list<int> L(array, array + sizeof(array) / sizeof(array[0]));
-
- L.push_back(4); // 在list的尾部插入4
- L.push_front(0); // 在list的头部插入0
- PrintList(L);
-
- L.pop_back(); // 删除list尾部节点
- L.pop_front(); // 删除list头部节点
- PrintList(L);
- }
- // insert /erase
- void TestL4()
- {
- int array1[] = {1, 2, 3};
- list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));
-
- auto pos = ++L.begin(); // 获取链表中第二个节点
- cout << *pos << endl;
-
- L.insert(pos, 4); // 在pos前插入值为4的元素
- PrintList(L);
-
- L.insert(pos, 5, 5); // 在pos前插入5个值为5的元素
- PrintList(L);
-
- vector<int> v{7, 8, 9};
- L.insert(pos, v.begin(), v.end()); // 在pos前插入[v.begin(), v.end)区间中的元素
- PrintList(L);
-
- L.erase(pos); // 删除pos位置上的元素
- PrintList(L);
-
- L.erase(L.begin(), L.end()); // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
- PrintList(L);
- }
为什么 C++98 建议使用各自容器里的 swap,而不建议使用算法里的 swap?
可以看到算法里 swap 的 C++98 的实现,无论是 string、vector、list 使用它都会涉及深拷贝问题,而且这里的深拷贝代价极大,需要深拷贝 3 次 —— 当 l1 和 l2 交换,这里会把 l1 拷贝构造一份 c,然后把 l2 赋值于 l1,c 赋值于 l2,完成交换。
而如果是容器里的 swap,需要交换 l1 和 l2,只需要头指针交换即可。假设是 vector,只要把 l1 和 l2 对应的 _start、_finish、_endofstorage 交换即可。相比算法里的 C++98 里的 swap,这里可以认为没有任何代价。
- // resize/swap/clear
- void TestL5()
- {
- // 用数组来构造list
- int array1[] = {1, 2, 3};
- list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
- PrintList(l1);
-
- list<int> l2;
- l1.swap(l2); // 交换l1和l2中的元素
- PrintList(l1);
- PrintList(l2);
-
- l2.clear(); // 将l2中的元素清空
- cout << l2.size() << endl;
- }
先将迭代器暂时理解成类似于指针, 迭代器失效即迭代器所指向的节点无效,即该节点被删除了 。因为 list 的底层结构为带头结点的双向循环链表,因此在 list 中进行 插入 时是 不会 导致 list 的迭代器失效的,只有在 删除 时才 会 失效,并且 失效的只是指向被删除节点的迭代器 ,其他迭代器不会受到影响。

- void TestListIterator()
- {
- int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
- list<int> l(array, array+sizeof(array)/sizeof(array[0]));
- auto it = l.begin();
- while (it != l.end())
- {
- // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
- l.erase(it);
- ++it;
- }
- }
-
- // 改正
- void TestListIterator()
- {
- int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
- list<int> l(array, array+sizeof(array)/sizeof(array[0]));
- auto it = l.begin();
- while (it != l.end())
- {
- l.erase(it++); // it = l.erase(it);
- }
- }

容器迭代器的分类:
- 使用功能的角度可分为:正向 / 反向 + const。
- 容器底层结构的角度可分为:单向迭代器、双向迭代器、随机访问迭代器。
单链表 forward_list、哈希表 unordered_set/map 迭代器就是单向(Unidirectional Iterator),特征是能 ++,不能 --;
双向链表 list、红黑树 set/map 迭代器就是双向(Bidirectional Iterator),特征是能 ++ / --;
string、vector、deque 迭代器就是随机迭代器(Random Access Iterator),特征是能可以++ / -- / + / -,一般随机迭代器底层都是一个连续的空间。
迭代器的本质是:
在不暴露容器底层实现细节的情况下,提供了一种统一的方式来访问 / 修改容器中存储的数据。
容器、迭代器、算法间的关系:
算法不能直接访问和修改容器中存储的数据,而迭代器就是容器和算法之间的胶合剂。

stl_iterator 源码中对于迭代器的分类:

迭代器有两种实现方式,具体应根据容器底层数据结构的实现来确定。
因为每个特定的容器,都有特定的内部内存结构,也就意味着遍历 / 迭代过程的细节是不一样的。
方式一:前提条件:原生指针指向的空间物理地址是连续的,比如 string、vector。
所以让原生指针 ++ 可以直接走到下一个位置,-- 可以直接回到上一个位置,* 解引用可以直接得到这个位置的数据。比如 vector:
此时的原生指针就是一个天然的迭代器(但并不是原生指针厉害,而是因为它指向的空间物理地址是连续的)。
方式二:原生指针指向的空间物理地址不连续,比如:list、map / set。
所以让原生指针 ++ 不能直接走到下一个位置,-- 不能直接回到上一个位置,* 解引用不能直接得到这个位置的数据,只能得到一个结构体(结构体中包含数据和指针域)。比如 list:
此时原生指针不是一个天然的迭代器,所以我们需要用一个类封装原生指针,重载相关运算符,控制对象使用这些运算符的行为,让这个类的对象,用起来像指针一样。
为了让迭代器的使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
- 指针可以解引用,则迭代器的类中必须重载 operator*()。
- 指针可以通过 -> 访问其所指空间成员,则迭代器类中必须重载 oprator->()。
- 指针可以 ++ 向后移动,则迭代器类中必须重载 operator++() 与 operator++(int) ,至于 operator–() / operator–(int) 是否需要重载,要根据容器具体的底层结构来抉择,比如 list 双向链表可以向前移动,所以需要重载 operator--() 和 operator--(int),如果是 forward_list 就不需要重载 --。
- 迭代器需要进行是否相等的比较,因此还需要重载 operator==() 与 operator!=()。
cplusplus.com/reference/list/list/sort/

等价元素的结果顺序是稳定的:即等价元素保留它们在调用之前的相对顺序。
整个操作不涉及任何元素对象的构造、销毁或复制。元素在容器内移动。
sort() 对 list 容器中的元素进行排序,效率很低,链表排序也没有太大的作用和价值。
函数默认是排升序(<),如果想要排降序(>),需要传仿函数。
- void test()
- {
- int arr[] = { 10, 2, 5 };
- list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
-
- // > 排降序
- // 写法一:
- greater<int> g; // 类模板,在
头文件中 - lt.sort(g);
-
- // 写法二:
- lt.sort(greater<int>()); // 匿名对象
- // lt: 10 5 2
- }
cplusplus.com/reference/list/list/unique/

去重,必须要先排序,才能去完重复值。
- void test()
- {
- int arr[] = { 2, 4, 3, 2, 4 };
- list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
- lt.sort(); // 去重前,必须要先排序
- lt.unique(); // 去除重复值
- // lt: 2 3 4
- }
cplusplus.com/reference/list/list/remove/

从容器中移除所有值等于 val 的元素。
- void test()
- {
- int arr[] = { 1, 2, 3, 3, 4 };
- list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
- lt.remove(3); // lt: 1 2 4
- }
cplusplus.com/reference/list/list/reverse/

反转容器中元素的顺序。
- void test()
- {
- int arr[] = { 1, 2, 3, 4, 5 };
- list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
- lt.reverse(); // rlt: 5 4 3 2 1
- }
cplusplus.com/reference/list/list/merge/

合并排序序列(合并前两个 list 容器都要排好序)。

SGI 版本 STL 中 list 部分源码:
- // 链表节点结构
- template <class T>
- struct __list_node {
- typedef void* void_pointer;
- void_pointer next;
- void_pointer prev;
- T data;
- };
-
- // 迭代器 -- 一个类封装了节点指针
- template<class T, class Ref, class Ptr>
- struct __list_iterator {
- typedef __list_iterator
iterator; - typedef __list_iterator
const T&, const T*> const_iterator; - typedef __list_iterator
self; - // ...
-
- typedef Ptr pointer;
- typedef Ref reference;
- // ...
-
- typedef __list_node
* link_type; -
- link_type node; // 节点指针
- // ...
- };
-
- // 带头双向循环链表结构
- template <class T, class Alloc = alloc>
- class list {
- protected:
- typedef __list_node
list_node; // 节点 - // ...
-
- public:
- typedef T value_type;
- typedef list_node* link_type; // 节点指针
- // ...
-
- public:
- // 迭代器(这里的iterator和const_iterator是定义在list类域中的内嵌类型)
- typedef __list_iterator
iterator; // 传了T,T&,T*模板参数 - typedef __list_iterator
const T&, const T*> const_iterator; // 传了T,const T&,const T*模板参数 - // ...
-
- protected:
- link_type node; // 成员变量,节点指针
- // ...
- };
双向循环链表节点结构:一个数据域,两个指针。
- namespace xyl
- {
- // List的节点类
- template<class T>
- struct list_node
- {
- T _data; // 数据域
- list_node
* _prev; // 前驱指针 - list_node
* _next; // 后继指针 -
- // 默认构造函数,T()是缺省值(T类型的默认构造函数)
- list_node(const T& x= T())
- :_data(x)
- ,_prev(nullptr)
- ,_next(nullptr)
- {}
- };
- }

前面学习的 string 和 vector 容器的正向迭代器其实就是原生指针,++ 可以直接走到下一个元素,而 list 的正向迭代器是用一个类,把节点指针封装起来,然后通过实现运算符重载函数,让迭代器具有像指针一样的行为,比如:重载了 * 运算符,实现了 *it 解引用返回节点中的存储的数据的引用,重载了 ++ 运算符,实现了 it++ 直接走到下一个元素。
list 正向迭代器的结构:
- namespace xyl
- {
- // T:节点中数据的类型 Ref:节点中数据的引用 Ptr:节点中数据的指针(地址)
- template<class T, class Ref, class Ptr>
- struct __list_iterator
- {
- typedef list_node
Node; // 节点 - typedef __list_iterator
self; // 自己这个类型 -
- Node* _node; // 节点指针
-
- // 构造函数
- __list_iterator(Node* node) // 用节点指针来构造
- :_node(node)
- {}
-
- // 让迭代器具有像指针一样的行为
- Ref operator*() // *it 本质是:it.operator*()
- {
- return _node->_val; // 返回节点中数据(对象)的引用/const常引用,具体返回什么取决于在list类域中定义迭代器时传给Ref的参数是什么
- // 我们想要得到的是节点中存储的数据(对象),而不是整个节点(节点中包含数据和2个指针)
- }
- Ptr operator->() // it-> 本质是:it.operator->()
- {
- return &(operator*()); // 返回节点中数据(对象)的指针/const常指针,具体返回什么取决于在list类域中定义迭代器时传给Ptr的参数是什么
- }
-
- // 让迭代器可以移动(双向)
- // ++it 本质是:it.operator++()
- Self& operator++()
- {
- _node = _node->_next;
- return *this;
- }
- // it++ 本质是:it.operator++(0)
- Self operator++(int) // 不能用引用返回
- {
- Self tmp(*this);
- _node = _node->_next;
- return tmp;
- }
- // --it 本质是:it.operator--()
- Self& operator--()
- {
- _node = _node->_prev;
- return *this;
- }
- // it-- 本质是:it.operator--(0)
- Self operator--(int) // 不能用引用返回
- {
- Self tmp(*this);
- _node = _node->_prev;
- return tmp;
- }
-
- // 让两个迭代器可以进行比较,判断两个迭代器是否相等,其实判断的是节点指针是否相等
- bool operator!=(const Self& l)const
- {
- return _node != l._node;
- }
- bool operator==(const Self& l)const
- {
- return _node != l._node;
- }
- };
- }
迭代器有两种实现方式,具体应根据容器底层数据结构实现:
- 原生态指针,比如:vector。
- 将原生态指针进行封装,因迭代器使用形式与指针完全相同。
因此在自定义的类中必须实现以下方法:
- 指针可以解引用,迭代器的类中必须重载 operator*() 。
- 指针可以通过 -> 访问其所指空间成员,迭代器类中必须重载 oprator->() 。
- 指针可以 ++ 向后移动,迭代器类中必须重载 operator++() 与 operator++(int) 。
- 至于operator--() / operator--(int) 释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是 forward_list 就不需要重载 -- 。
- 迭代器需要进行是否相等的比较,因此还需要重载 operator==() 与 operator!=() 。
string 和 vector 的迭代器就是一个原生指针,我们给原生指针加上 const 就可以变成 const 迭代器,比如:
// T: 容器中存储的数据的类型 typedef T* iterator; typedef const T* const_iterator;
但 list 的迭代器不是一个原生指针,而是一个类里面封装了原生指针,那如何实现 list 的 const 迭代器呢?
const 迭代器,顾名思义,就是不能改变的迭代器,是不能通过 * 和 -> 修改指向成员的,所以我们把 operator*() 和 operator->() 函数的返回值改成常引用即可。比如:
const T& operator*() ; // 返回节点中数据(对象)的const常引用
那我们需要实现一个 __list_const_iterator 的类吗?在类中重载一个返回值为常引用的 operator*() 函数。
普通写法需要这样,因为 T& operator*(); 和 const T& operator*(); 这两个函数只是返回值不同,无法放在一个类里面构成重载。
SGI 版本 stl_list 源码中是怎么样实现的:
通过增加模板参数来控制,普通迭代器传过来的参数就是 T&,const 迭代器传过来的参数就是 const T&。

如果迭代器管理的节点中的数据是内置类型:
- void test()
- {
- int arr[] = { 1, 2, 3, 4, 5 };
- list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
-
- list<int>::iterator it = lt.begin();
- while (it != lt.end())
- {
- // 对指向当前节点的迭代器解引用 *it 可以得到当前节点中存储的数据
- cout << *it << " "; // it.operator*()
- ++it;
- }
-
- }
如果迭代器管理的节点中的数据是自定义类型 / 结构体:
- struct TreeNode
- {
- int _val;
- struct TreeNode* _left;
- struct TreeNode* _right;
-
- TreeNode(int val = -1)
- :_val(val)
- ,_left(nullptr)
- ,_right(nullptr)
- {}
- };
-
- void test()
- {
- list
lt; - lt.push_back(TreeNode(1));
- lt.push_back(TreeNode(2));
- lt.push_back(TreeNode(3));
-
- list
::iterator it = lt.begin(); - while (it != lt.end())
- {
- /* 错误写法:
- * 对指向当前节点的迭代器解引用*it可以得到当前节点中存储的TreeNode结构体
- * TreeNode结构体中存储的数据_val是不能直接输出出来的
- * 除非重载了专门针对输出TreeNode结构体中数据的流插入运算符,比如:ostream& operator<<(ostream& out, const TreeNode node);
- */
- // cout << *it << " "; // error!!!
-
- /* 正确写法1:
- * 对指向当前节点的迭代器解引用*it得到当前节点中存储的TreeNode结构体
- * 然后用'.'再去访问 TreeNode 结构体中存储的数据_val
- */
- cout << (*it)._val << " ";
-
- /* 正确写法2:
- * 调用 operator->() 函数
- * 迭代器->,返回迭代器指向节点中存储的TreeNode结构体的地址(指针):TreeNode*
- * 然后该指针再使用'->'访问结构体中存储的数据_val
- * 代码为:it->->_val,但可读性太差,编译器进行了特殊处理,省略掉了一个箭头,保持了程序的可读性
- */
- // 注意:一般结构体的指针才会使用'->'来访问成员
- // 当迭代器管理的节点中的数据是结构体的时候,就可以用'->'
- cout << it->_val << " "; // 这种写法常用
- it++;
- }
- }
不需要,默认生成的就可以,而且也不需要我们去实现。虽然迭代器中封装了节点指针,但节点是归链表管理的,链表 list 中的节点也不需要迭代器去释放,list 自己会去释放。
它们的大小都是一样的(32 位下),vector 的迭代器是一个原生指针,大小为 4 字节;list 的迭代器是一个类封装了一个节点指针,大小也是 4 字节,物理上没什么区别。
list 的迭代器之所以要定义成自定义类型,是因为原生指针无法直接做到像指针一样的行为,所以需要用自定义的类把原生指针封装起来,然后在类中定义一些运算符重载函数,使得这个自定义类型的对象通过调用这些函数,从而可以做到像指针一样的行为。
我们在物理上可以看到,都是存了一个指针,但在运行逻辑上完全不同。
- vector 迭代器 ++,是一个指针直接 ++ 到下一个位置;
- list 迭代器 ++,是一个自定义类型的对象调用了 operator++() 函数,在函数内算出下一个位置。
带头双向循环链表结构:
- // 带头双向循环链表结构
- namespace xyl
- {
- template<class T> // T: 节点中数据的类型
- class list
- {
- public:
- typedef __list_node
Node; // 节点 -
- public:
- // 迭代器
- // iterator是内嵌类型,在list类域里面定义的类型(类中类)
- // 为啥要typedef呢?为了统一规范名称,所有容器迭代器都叫iterator,用起来更方便
- // 普通迭代器,指明模板参数为 T, T&, T*
- typedef __list_iterator
iterator; -
- // const迭代器,指明模板参数为 T, const T&, const T*
- typedef __list_iterator
const T&, const T*> const_iterator; -
- iterator begin(); // 返回指向第一个有效节点的迭代器
- iterator end(); // 返回指向头节点的迭代器
- const_iterator begin() const;
- const_iterator end() const;
-
- private:
- Node* _head; // 头节点指针
-
- public:
- // 默认成员函数
- list(); // 默认构造函数
- template<class InputIterator>
- list(InputIterator first, InputIterator last); // 带参构造函数
- list(const list
& lt); // 拷贝构造函数(深拷贝) - list
& operator=(list lt) // 赋值运算符重载函数(深拷贝) - ~list(); // 析构函数
-
- // 容量操作
- size_t size(); // 有效元素个数
- bool empty(); // 判空
-
- // 修改操作
- iterator insert(iterator pos, const T& data); // 指定迭代器位置之前插入元素
- iterator erase(iterator pos); // 删除指定迭代器位置的元素
- void push_back(const T& data); // 尾插
- void push_front(const T& data); // 头插
- void pop_back(); // 尾删
- void pop_front(); // 头删
- void clear(); // 清空有效元素
- };
- }
注意:list 类域中的 __list_iterator
和 __list_iterator 是定义在类内部的类(嵌套类型)。 用 typedef 重命名为 iterator 和 const_iterator ,目的是为了统一规范迭代器的名称,所有容器的迭代器都叫 iterator 等,用起来更方便。否则每个容器都定义迭代器,如果每个容器迭代器的名字都不一样,那用户的使用成本就太高了。
- // 普通迭代器,指明模板参数为 T, T&, T*
- typedef __list_iterator
iterator; -
- // const迭代器,指明模板参数为 T, const T&, const T*
- typedef __list_iterator
const T&, const T*> const_iterator;
其中 T 、 T& 、 T* 都是类型参数。当 list 的类型确定了,T 、 T& 、 T* 的类型自动确定,那么 iterator 的类型同时也自动确定了,这看起来就像 iterator 和 list 是共生的一样。有什么样的螺丝就需要什么样的螺丝刀,否则螺丝刀再漂亮,没有对号的螺丝也是没用的。
- // 默认构造函数
- list()
- {
- // 构造头节点,自己指向自己
- _head = new Node;
- _head->_prev = _head;
- _head->_next = _head;
- }
-
- // 用迭代器区间初始化[first,last)
- template<class InputIterator>
- list(InputIterator first, InputIterator last)
- :_head(new Node) // 当前对象是一个正在构造的对象,成员变量是随机值,需要进行初始化
- {
- // 建立头节点自身的链接
- _head->_prev = _head;
- _head->_next = _head;
-
- // 用迭代器遍历元素,尾插到新容器中
- while (first != last)
- {
- push_back(*first);
- first++;
- }
- }
- //拷贝构造函数(深拷贝--传统写法)
- // lt2(lt1)
- list(const list
& lt) - :_head(new Node) // 当前对象是一个正在构造的对象,成员变量是随机值,需要进行初始化
- {
- // 建立头节点自身的链接
- _head->_prev = _head;
- _head->_next = _head;
-
- // 把lt中所有节点拷贝插入到新容器中
- for (const auto& e : lt)
- {
- push_back(e);
- }
- }
-
- // 拷贝构造函数(深拷贝--现代写法)
- // lt2(lt1)
- list(const list
& lt) - :_head(new Node) // 当前对象是一个正在构造的对象,成员变量是随机值,需要进行初始化
- {
- // 建立头节点自身的链接
- _head->_prev = _head;
- _head->_next = _head;
-
- list
tmp(lt.begin(), lt.end()) ; - std::swap(_head, tmp._head); // 交换两个容器的内容
- }
- list
& operator=(const list& lt) - {
- if (this != <) // 防止自己给自己赋值
- {
- // 清除当前容器所有有效元素
- clear();
- // 把lt中所有节点拷贝插入到当前容器中
- for (const auto& e : lt)
- {
- push_back(e);
- }
- }
- return *this; // 返回当前容器
- }
- list
& operator=(list lt) // 传值传参,拷贝构造出临时对象 - {
- std::swap(_head, lt._head); // 交换两个容器的内容
- return *this; // 返回当前容器
- }
- ~list()
- {
- /*
- Node* cur = _head->_next; // 从第一个有效节点开始删除
- while (cur != _head)
- {
- Node* next = cur->_next; // 先记录下一个节点
- delete cur; // 释放当前节点
- cur = next; // 遍历下一个节点
- }
- delete _head; // 释放头节点
- _head = nullptr;
- */
-
- clear();
- delete _head;
- _head = nullptr;
- }
- // O(N)
- size_t size()
- {
- size_t n = 0; // 遍历整个链表,统计有效元素个数
- iterator it = begin();
- while (it != end())
- {
- n++;
- it++;
- }
- return n;
- }
- bool empty()
- {
- return begin() == end();
- }

- iterator insert(iterator pos, const T& data)
- {
- Node* cur = pos._node; // 当前节点(pos中封装了节点的指针)
- Node* prev = cur->_prev; // 前驱节点
- Node* newnode = new Node(data); // 新节点
-
- // 建立前驱节点和新节点的链接
- prev->_next = newnode;
- newnode->_prev = prev;
-
- // 建立新节点与当前节点的链接
- newnode->_next = cur;
- cur->_prev = newnode;
-
- // 返回指向第一个新插入元素的迭代器
- return iterator(newnode); // 注意:此处调用iterator的构造函数构造出一个匿名对象
- // return newnode; // 也可以这样写,因为单参数的构造函数支持隐式类型的转换
- }
- iterator erase(iterator pos)
- {
- assert(pos != end()); // 不能删除头节点
- Node* cur = pos._node; // 当前节点
- Node* prev = cur->_prev; // 前驱节点
- Node* next = cur->_next; // 后继节点
-
- // 建立前驱节点与后继节点的链接
- prev->_next = next;
- next->_prev = prev;
-
- // 删除当前节点
- delete cur;
-
- // 返回指向被删除节点的下一个节点的迭代器
- return iterator(next); // 注意:此处调用iterator的构造函数构造出一个匿名对象
- }
- void push_back(const T& data) // 尾插
- {
- /*
- Node* newNode = new Node(data); // 构造新节点
- Node* tail = _head->_prev; // 尾节点
- // 建立尾节点和新节点的链接
- tail->_next = newNode;
- newNode->_prev = tail;
- // 建立新节点和头节点的链接
- newNode->_next = _head;
- _head->_prev = newNode;
- */
-
- insert(end(), data); // 在头节点的前面插入,相当于是尾插
- }
-
- void push_front(const T& data) // 头插
- {
- insert(begin(), data); // 在第一个有效节点前面插入,相当于是头插
- }
- void pop_back() // 尾删
- {
- erase(--end());
- }
- void pop_front() // 头删
- {
- erase(begin());
- }
- void clear()
- {
- /*
- iterator it = begin();
- while (it != end())
- {
- Node* cur = it._node; // 记录当前节点
- it++; // 遍历下一个节点
- delete cur; // 删除当前节点
- }
- // 建立头节点自身的链接
- _head->_next = _head;
- _head->_prev = _head;
- */
-
- iterator it = begin();
- while (it != end())
- {
- it = erase(it); // 删除当前节点,返回指向下一个节点的迭代器
- }
- }
编译器进行类模板推演,实例化出一个 list
类,然后再实例化出这个 lt 对象。
- 类模板中的成员函数都是函数模板。
- 类模板成员函数的实例化,是按需实例化,只有当程序用到它时才进行实例化。
- 一个模板,如果没有实例化,编译器是不会去检查它内部的语法的,也不会报错。
- void test()
- {
- list<int> lt;
- lt.push_back(1);
- lt.push_back(2);
- lt.push_back(3);
- lt.clear();
- }
vector 与 list 都是 STL 中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下: