从基本用法到迭代器实现—list重难点突破
目录
一、前言
二、list相关接口
1、push_back
2、迭代器
3、范围for
4、emplace_back
5、insert
6、erase
7、sort
8、reverse
9、merge
10、unique
11、splice
三、list实现
1、文件
2、结点实现
3、list成员变量
4、empty_init
5、构造函数
6、size
7、empty
8、迭代器模板实现
(1)实现原理
(2)构造
(3)operator++()
(4)operator--()
(5)operator++(int)
(6)operator--(int)
(7)operator==
(8)operator!=
(9)operator*
(10)operator->
9、迭代器相关接口实现
(1)迭代器实现
(2)begin、end
10、insert
11、push_back
12、print_container
13、operator->测试
14、push_front
15、erase
16、pop_back
17、pop_front
18、clear
19、析构
20、拷贝构造
21、swap
22、operator=
23、初始化列表
四、结语
一、前言
本文将围绕C++ STL的list容器展开介绍,list底层以数据结构的带头双向循环链表为结构基础,相比STL中的其他容器,如string、vector,在接口用法上大体类似,其接口也是在其底层结构基础上进行一系列的增删改查等操作,与string、vector有所不同的是由于string、vector在底层结构上内存地址是连续的,而list基于链表为基本结构,结点之间的地址并不是连续的,因此list迭代器的实现方式相比string、vector会有所不同,其实现方式也较为复杂,本文将从list接口的基本用法出发,在此基础上进一步实现list相关接口,其迭代器的实现是list的一大重难点,涉及模板、运算符重载等核心方法,实现要求能力较高,实现起来也较为复杂。
二、list相关接口
1、push_back
include<iostream>
#include<list>
using namespace std;
void test_list1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);
}
list与vector类似,在STL中实现为模板类型,使用时也需进行实例化,如list<int> lt,实例化了一个结点存放数据类型为int的带头循环双向链表,push_back实现在链表后尾插结点,如lt.push_back(1),从而实现链表数据的尾插。
2、迭代器
#include<iostream>
#include<list>
using namespace std;
void test_list1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;
}
list迭代器的用法与vector保持一致,使用迭代器遍历数据时也需进行实例化,如list<int>::iterator it=lt.begin(),begin()指向list第1个有效结点的位置,end()指向list最后一个结点的下一个位置,通过while循环即可实现list的遍历,结果如(1)所示。

(1)
3、范围for
#include<iostream>
#include<list>
using namespace std;
void test_list1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto e : lt){cout << e << " ";}cout << endl;
}
通过范围for也可遍历list,范围for底层为迭代器,本质也是通过迭代器来遍历list,结果如(2)所示。

(2)
4、emplace_back
void test_list2()
{list<int> lt;lt.emplace_back(1);lt.emplace_back(2);lt.emplace_back(3);lt.emplace_back(4);for (auto e : lt){cout << e << " ";}cout << endl;
}
emplace_back功能与push_back相似,都可以在链表后尾插数据,如上所示,lt调用emplace接口尾插数据1、2、3、4,结果如(3)所示。

(3)
struct K
{
public:K(int k1=1,int k2=1):_k1(k1),_k2(k2){cout << "K(int k1=1,int k2=1)" << endl;}K(const K& kk):_k1(kk._k1),_k2(kk._k2){cout << "K(const K& kk)" << endl;}int _k1;int _k2;
};
void test_list2()
{list<K> lt;K k1(1, 1);lt.push_back(k1);lt.push_back(K(2,2));//lt.push_back(3, 3);不支持构造参数直接构造lt.emplace_back(k1);lt.emplace_back(K(2, 2));lt.emplace_back(3, 3);//支持构造参数直接构造
}
与push_back不同的是,push_back是将已构造的对象添加到容器末尾,并不接受通过传构造参数直接构造,而emplace_back支持通过构造参数直接构造,如lt.emplace_back(3,3),emplace可以通过构造参数(3,3)直接构造对象K,通常情况下,两者性能差异并不大,当需要通过构造参数直接构造对象时,考虑用emplace_back,emplace_back相比push_back能够减少不必要的拷贝。
5、insert
void test_list3()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);//lt.insert(lt.begin() + 3, 30);list迭代器不支持+、-操作auto it = lt.begin();int k = 3;while (k--){it++;}lt.insert(it, 30);for (auto e : lt){cout << e << " ";}cout << endl;
}
insert实现在list指定位置插入数据,与string、vector有所不同的是,由于string、vector内存地址是连续的,因此string、vector迭代器支持+、-操作,而list各个结点的地址并不是连续的,因此list迭代器不支持+、-操作,仅支持++、--,若想在list某个位置插入数据,就只能通过迭代器的++、--来实现,例如在list的第3个结点后插入一个数据30,可以通过while循环、迭代器的++来实现,结果为1,2,3,30,4,5,6,如(4)所示。

(4)
6、erase
void test_list3()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);auto it = lt.begin();lt.erase(it);for (auto e : lt){cout << e << " ";}cout << endl;
}
erase实现在list指定位置删除元素,如auto it=lt.begin(),lt.erase(it),删除list的首元素,结果应为2,3,4,5,6,如(5)所示。

(5)
7、sort
void test_list4()
{list<int> lt;lt.push_back(1);lt.push_back(3);lt.push_back(2);lt.push_back(5);lt.push_back(4);lt.push_back(6);lt.sort();for (auto e : lt){cout << e << " ";}cout << endl;
}
sort实现对list数据的排序,lt.sort()默认进行升序排序,故结果为1,2,3,4,5,6,如(6)所示。

(6)
若想进行降序排序,可传greater<int>对象,就可进行降序排序。
void test_list4()
{list<int> lt;lt.push_back(1);lt.push_back(3);lt.push_back(2);lt.push_back(5);lt.push_back(4);lt.push_back(6);lt.sort(greater<int>());for (auto e : lt){cout << e << " ";}cout << endl;
}
lt.sort(greater<int>()),这里通过传greater<int>的匿名对象来实现对lt的降序排序,结果为6,5,4,3,2,1,如(7)所示。

(7)
8、reverse
void test_list4()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);lt.reverse();for (auto e : lt){cout << e << " ";}cout << endl;
}
reverse实现list数据的逆置,lt.reverse(),逆置后结果为6,5,4,3,2,1,如(8)所示。

(8)
9、merge
void test_list5()
{std::list<double> lt1, lt2;lt1.push_back(5.5);lt1.push_back(2.2);lt1.push_back(3.3);lt2.push_back(4.4);lt2.push_back(1.1);lt2.push_back(6.6);lt1.sort();lt2.sort();lt1.merge(lt2);for (auto e : lt1){cout << e << " ";}cout << endl;for (auto e : lt2){cout << e << " ";}cout << endl;
}
merge实现两个list的合并,将一个链表尾插到另一个链表后,形成一个新的链表,lt1.sort(),lt2.sort(),先对lt1、lt2进行升序排序,lt1.merge(lt2),再将lt2尾插到lt1后,lt1数据个数为两链表的数据个数之和,结果为1.1,2.2,3.3,4.4,5.5,6.6,lt2的数据个数为0,如(9)所示。

(9)
10、unique
void test_list6()
{list<int> lt;lt.push_back(1);lt.push_back(20);lt.push_back(3);lt.push_back(5);lt.push_back(5);lt.push_back(4);lt.push_back(5);lt.push_back(6);lt.sort();for (auto e : lt){cout << e << " ";}cout << endl;lt.unique();for (auto e : lt){cout << e << " ";}cout << endl;
}
unique用于list元素的去重,要求list元素原始为有序,先对lt进行排序,调用sort,lt.sort(),再调用unique,对lt元素进行去重,则lt结果为1,3,4,5,6,20,如(10)所示。

(10)
11、splice
void test_list7()
{std::list<int> lt1, lt2;std::list<int>::iterator it;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);it = lt1.begin();it++;lt1.splice(it, lt2);for (auto e : lt1){cout << e << " ";}cout << endl;for (auto e : lt2){cout << e << " ";}
}
splice与merge类似,实现在链表指定位置插入链表,it指向链表第2个元素位置,lt1.splice(it,lt2),在it位置插入lt2,其功能类似剪切,故lt1结果为1,10,20,30,2,3,4,lt2中没有元素,如(11)所示。

(11)
void test_list7()
{std::list<int> lt1, lt2;std::list<int>::iterator it;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);it = lt1.begin();it++;lt1.splice(lt1.begin(), lt1, it);for (auto e : lt1){cout << e << " ";}cout << endl;
}
此外,splice还支持在一条链表内进行操作,it指向链表第2个结点,lt1.splice(lt1.begin(),lt1,it),相当于将链表第2个元素剪切复制到链表的首元素,故lt1结果为2,1,3,4,如(12)所示。

(12)
splice同时也支持区间形式:
void test_list7()
{std::list<int> lt1;std::list<int>::iterator it;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);it = lt1.begin();it++;lt1.splice(lt1.begin(), lt1, it, lt1.end());for (auto e : lt1){cout << e << " ";}cout << endl;
}
lt1.splice(lt1.begin(),lt1,it,lt1.end()),将it位置到lt1.end()的数据剪切拷贝到begin()位置之前,故lt1的结果为2,3,4,1,如(13)所示。

(13)
三、list实现
1、文件
list实现文件包括:list.h头文件,负责list相关接口声明和实现,test.cpp测试文件对list.h实现的接口进行测试,如(14)所示。

(14)
2、结点实现
实现list带头双向循环链表结构,首先需实现链表结点结构,这里都实现为模板类型:
#pragma once
#include<iostream>
#include<assert.h>
#include<list>
using namespace std;
namespace YZK
{template<class K>struct list_node{K _data;list_node<K>* _next;list_node<K>* _prev;list_node(const K& data = K()):_data(data), _next(nullptr), _prev(nullptr){}};
}
结点list_node中存放数据_data,以及指向下一个结点的指针list_node<K>*_next,指向前一个结点的指针list_node<K>*_prev,其构造函数数据默认初始化为该类型的默认构造K(),_next,_prev初始化为nullptr,从而实现结点的默认构造。
3、list成员变量
template<class K>class list{typedef list_node<K> Node;private:Node* _head;size_t _size;};
实现了结点结构,就可以声明list的成员变量了,可先typedef将结点实例化list_node<K>list为Node,方便使用,list为带头双向循环链表,故指向哨兵位头结点的指针可声明为成员变量,_size用于记录list的结点个数。
4、empty_init
template<class K>class list{typedef list_node<K> Node;void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}private:Node* _head;size_t _size;};
empty_init用于list的初始化,list为带头双向循环链表,故初始化即对头结点进行初始化,先通过new申请一个结点空间,该结点即为头结点,初始化list时,list中没有其他结点,故头结点的_next、_prev指针初始化均指向自身,_size初始化为0,就完成了对list的初始化。
5、构造函数
list(){empty_init();}
实现list构造函数,就只需调用已实现的empty_init即可完成对list的初始化。
6、size
size_t size() const{return _size;}
size用于返回list的结点个数,即返回_size。
7、empty
bool empty() const{return _size == 0;}
empty用于判断list是否没有结点,可通过_size是否为0来判断,即返回_size==0。
8、迭代器模板实现
(1)实现原理
list迭代器实现与string、vector会有所不同,string、vector迭代器可通过原生指针来实现,而list迭代器则不能简单地通过原生指针来实现,这是由于list结点之间地址并不连续,则++it就不一定是下一个结点的地址,此外,*it表示的是it指向的结点,并不是表示该结点的数据,因此需重载*、++等操作符,需要将迭代器进行封装,此外,这里可以利用模板一并实现iterator、const_iterator迭代器,list迭代器的实现是list的一大难点,综合了模板、运算符重载等方法,实现要求能力较高。
(2)构造
template<class K,class Ref,class Ptr>struct list_iterator{typedef list_node<K> Node;typedef list_iterator<K,Ref,Ptr> Self;Node* _PNode;list_iterator(Node* PNode):_PNode(PNode){}};
list迭代器构造还是通过结点指针来构造,成员变量为结点指针_PNode,typedef结点list_node<K>为Node,list_iterator<K,Ref,Ptr>迭代器为self,方便使用,其构造函数通过传一个结点指针PNode来进行构造。
(3)operator++()
Self& operator++(){_PNode = _PNode->_next;return *this;}
operator++()实现前置++,返回指向下一个结点的指针,即_PNode=_PNode->_next,return *this,非局部变量返回引用可减少拷贝。
(4)operator--()
Self& operator--(){_PNode = _PNode->_prev;return *this;}
operator--()实现前置--,返回指向前一个结点的指针,即_PNode=_PNode->_prev,return *this,同理返回引用可减少拷贝。
(5)operator++(int)
Self operator++(int){Self tmp(*this);_PNode = _PNode->_next;return tmp;}
operator++(int)实现后置++,返回原始值,故先构造tmp保存原始值,再进行++操作,即_PNode=_PNode->_next,最后返回tmp即可,由于tmp为局部变量,出作用域就被销毁,故不能返回引用,只能传值返回。
(6)operator--(int)
Self operator--(int){Self tmp(*this);_PNode = _PNode->_prev;return tmp;}
operator--(int)实现后置--,与operator++(int)类似,同样需先构造tmp保存原始值,再进行--操作,_PNode=_PNode->_prev,最后返回tmp即可,这里也需传值返回,不能引用返回。
(7)operator==
bool operator==(const Self& s) const{return _PNode == s._PNode;}
operator==用于判断两个迭代器是否相等,可通过其结点指针是否相等来进行判断,即return _PNode==s._PNode。
(8)operator!=
bool operator!=(const Self& s) const{return _PNode != s._PNode;}
operator!=则判断两个迭代器不相等,与operator==类似,可通过其结点指针判断,即return _PNode!=s._PNode。
(9)operator*
template<class K,class Ref,class Ptr>struct list_iterator{typedef list_node<K> Node;typedef list_iterator<K,Ref,Ptr> Self;Node* _PNode;list_iterator(Node* PNode):_PNode(PNode){}Ref operator*(){return _PNode->_data;}
operator*重载用于访问当前结点的数据,即return _PNode->_data,这里返回类型为模板参数Ref,这是考虑到普通迭代器和const修饰迭代器二者返回类型的差异,对于普通迭代器,返回类型为K&,而对于const修饰的迭代器返回类型为const K&,故将返回类型作为模板参数Ref,可根据具体迭代器设置Ref的值,从而做到一个迭代器模板可实现两个不同类型的迭代器,简化了迭代器不必要的重复实现,这也是list迭代器实现的一个核心思想和难点所在。
(10)operator->
template<class K,class Ref,class Ptr>struct list_iterator{typedef list_node<K> Node;typedef list_iterator<K,Ref,Ptr> Self;Node* _PNode;list_iterator(Node* PNode):_PNode(PNode){}Ref operator*(){return _PNode->_data;}Ptr operator->(){return &_PNode->_data;}}
operator->重载用于访问结点元素的地址,即结点元素的指针,即return &_PNode->_data,返回类型为模板参数ptr,也是基于普通迭代器和const修饰迭代器返回类型的不同,普通迭代器返回类型为K*,而const修饰的迭代器返回类型为const K*,故模板参数Ptr可根据具体迭代器来设置,从而做到一个迭代器模板既能实现普通迭代器,也能实现const修饰的迭代器,从而简化了两个迭代器实现不必要的重复。
9、迭代器相关接口实现
(1)迭代器实现
template<class K>class list{typedef list_node<K> Node;public:typedef list_iterator<K, K&, K*> iterator;typedef list_iterator<K, const K&, const K*> const_iterator;private:Node* _head;size_t _size;};
实现了迭代器模板,就可以借助模板实现迭代器了,只需传相应的模板参数即可,可知list_iterator<K,K&,K*>即为普通迭代器,typedef为iterator,list_iterator<K,const K&,const K*>为const修饰的迭代器,typedef为const_iterator,这样就借助迭代器模板实现了iterator和const_iterator两个不同类型的迭代器。
(2)begin、end
template<class K>class list{typedef list_node<K> Node;public:typedef list_iterator<K, K&, K*> iterator;typedef list_iterator<K, const K&, const K*> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}private:Node* _head;size_t _size;};
begin用于返回list第1个有效结点的迭代器,即头结点的下一个结点的迭代器,即return _head->_next,_head->_next会根据迭代器的具体类型隐式类型转化为相应的迭代器类型,转化为iterator或者const_iterator。end返回list最后一个结点下一个位置的迭代器,由于list为带头双向循环链表,可知最后一个结点下一个位置的迭代器即为头结点的迭代器,即return _head,同理_head会隐式类型转化为相应的迭代器类型,iterator或const_iterator。
10、insert
insert实现在list指定位置pos之前插入一个新结点,list中插入新结点只需处理结点之间_prev、_next指针的连接问题,例如在链表1、2、4中在4之前插入一个新结点3,则需处理2与3,3与4结点的指针连接问题,如下图所示:

需改变2和4结点_prev、_next指针的指向,以及新结点3_prev、_next指针的指向,2的_next指针指向新结点,3的_prev指针指向2,4的_prev指针指向3,3的_next指针指向4,从而完成新结点的插入。
iterator insert(iterator pos, const K& x){Node* cur = pos._PNode;Node* prev = cur->_prev;Node* newnode = new Node(x);newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}
insert在pos位置之前插入新结点,pos._PNode获取该位置的结点指针cur,cur->_prev为pos位置的前一个结点指针prev,再通过new开辟并初始化新结点newnode,剩下的就是处理prev、newnode、cur三者指针的指向问题,与上面类似,prev的_next指针指向newnode,newnode的_prev指针指向prev,newnode的_next指向cur,cur的_prev指针指向newnode,++_size,最后返回该位置的迭代器,即新结点位置的迭代器newnode。
这里需要注意的是,list的insert并不会导致迭代器失效,因为list的insert只需处理结点之间的_next、_prev指针指向问题,并没有发生扩容,因此结点的迭代器并没有发生改变,迭代器不失效。
11、push_back
void push_back(const K& x){insert(end(), x);}
实现了insert,就可以借助insert来实现push_back,push_back实现list的尾插,即在end位置前插入新结点,即insert(end(),x),就实现了push_back。
12、print_container
template<class container>void print_container(const container& con){typename container::const_iterator it = con.begin();while (it != con.end()){cout << *it << " ";++it;}cout << endl;}
print_container实现为函数模板,用于遍历输出容器数据,通过迭代器、while循环即可遍历输出容器数据,需要注意的是未实例化的类container,引用其成员const_iterator迭代器时,需加上typename关键字,否则编译器无法区分是类型还是成员变量。
测试(test.cpp)
void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;print_container(lt);}
对上面实现的迭代器、push_back、print_container进行测试,输出结果为1,2,3,4,如(15)所示,结果正确,测试通过。

(15)
13、operator->测试
struct kk{int _k1 = 1;int _k2 = 1;};void test_list1(){list<kk> ltk;ltk.push_back(kk());ltk.push_back(kk());ltk.push_back(kk());ltk.push_back(kk());list<kk>::iterator itk = ltk.begin();while (itk != ltk.end()){//cout<<(*itk)._k1<<":"<<(*itk)._k2<<endl;cout << itk->_k1 << ":" << itk->_k2 << endl;//cout << itk.operator->()->_k1 << ":" << itk.operator->()->_k2 << endl;++itk;}cout << endl;}
operator->重载比较少用,主要使用在list结点元素为结构体时,这时operator->获取的就是该结点结构体元素的指针,如上所示,ltk结点元素为结构体kk,这时operator->获取的就是结构体kk的地址,这时结构体kk的指针再进一步使用->操作符,就可访问kk的数据,具体表示为itk.operator->()->_k1,itk.operator->()->_k2,这样写起来比较麻烦,也不美观,对此编译器进行了优化,省略了一个->,直接表示为itk->_k1,itk->_k2,此外,除了用operator->表示,也可用operator*重载表示,即(*itk)._k1,(*itk)._k2,*itk即表示结构体kk,再使用.操作符就可访问_k1、_k2,结果应为1:1,如(16)所示,结果正确,测试通过。

(16)
14、push_front
void push_front(const K& x){insert(begin(), x);}
push_front实现list的头插,可借助insert来实现,即在begin位置之前插入一个新结点,即insert(begin(),x),就实现了push_front。
15、erase
erase用于删除list指定位置的元素,与insert一致,erase也需处理结点之间的_prev、_next指针的指向问题,可参考下图所示:

删除pos位置的结点3,需处理结点2、结点4的_prev、_next的指针问题,结点2的_next指针指向4,结点4的_prev指针指向2,同时也要释放结点3的空间,就完成了list结点的删除。
iterator erase(iterator pos){assert(pos != end());Node* prev = pos._PNode->_prev;Node* next = pos._PNode->_next;prev->_next = next;next->_prev = prev;delete pos._PNode;--_size;return next;}
erase删除指定位置元素,end为list头结点位置的迭代器,因此指定位置迭代器pos不能为end,接下来就只需获取pos前一个结点指针和后一个结点指针,即pos._PNode->_prev为pos的前一个结点指针,pos._PNode->_next为pos的后一个结点指针,与上面类似,再将prev的_next指针指向next,next的_prev指针指向prev,最后通过delete释放该结点,--_size,返回pos下一个结点的迭代器next,就实现了erase。
erase与insert不同的是,erase会导致迭代器的失效,这是因为erase释放了结点空间,使得该结点的迭代器变为野指针而失效,其他结点的迭代器并不失效,pos位置的迭代器失效,因此erase后需更新迭代器的值才能访问。
测试(test.cpp)
void test_list2(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();lt.insert(it, 10);*it += 100;print_container(lt);it = lt.begin();while (it != lt.end()){if (*it % 2 == 0){it=lt.erase(it);}else{++it;}}print_container(lt);}
对insert、erase进行测试,lt.insert(it,10),lt头插10,*it+=100,可知结果为10,101,2,3,4,接着通过while循环删除偶数,erase需更新迭代器的值,即it=lt.erase(it),可知结果101,3,如(17)所示,结果正确,测试通过。

(17)
16、pop_back
void pop_back(){erase(--end());}
pop_back实现list结点的尾删,可借助erase来实现,即删除end的前一个位置的结点,即erase(--end()),就实现了pop_back。
17、pop_front
void pop_front(){erase(begin());}
pop_front实现list结点的头删,也可借助erase来实现,即删除begin迭代器位置的结点,即erase(begin()),就实现了pop_front。
18、clear
void clear(){auto it = begin();while (it != end()){it = erase(it);}}
clear用于清空list的结点,可通过erase、while循环来实现结点的删除,需要注意的是erase删除时需更新迭代器的值,才能继续访问。
19、析构
~list(){clear();delete _head;_head = nullptr;}
list的析构实现可先调用clear,清空list的结点,再释放头结点空间,最后将头结点置为nullptr,就完成了list的析构。
20、拷贝构造
list(const list<K>& lt){empty_init();for (auto& e : lt){push_back(e);}}
实现list的拷贝构造,先调用empty_init完成头结点的初始化,再通过范围for将lt的数据逐个尾插,就实现了list的拷贝构造。
21、swap
void swap(list<K>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}
swap函数用于两个list对象的交换,可通过调用std库的swap函数交换两个list的_head,_size,这样就实现了两个list的交换。
22、operator=
list<K>& operator=(list<K> lt){swap(lt);return *this;}
operator=重载用于list的赋值,可借助swap函数实现,传赋值对象的拷贝lt,再通过swap交换lt与被赋值对象,这样被赋值对象就完成了赋值,lt出作用域会自动被析构,返回*this即可,引用返回可减少拷贝,这样就通过swap间接实现了operator=。
测试(test.cpp)
void test_list3(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);print_container(lt2);list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt1 = lt3;print_container(lt1);}
对拷贝构造、operator=进行测试,将lt1拷贝构造给lt2,则lt2结果为1,2,3,4,将lt3赋值给lt1,则lt1结果为10,20,30,40,如(18)所示,结果正确,测试通过。

(18)
23、初始化列表
除了通过构造、拷贝构造来初始化list之外,C++11还引入了初始化列表来初始化list,即initializer_list,用{ }表示,{ }用于存放数据,例如{1,2,3,4,5,6},实现方式与拷贝构造实现类似。
list(initializer_list<K> il){empty_init();for (auto& e : il){push_back(e);}}
即先通过empty_init初始化头结点,再通过范围for将il的数据逐个尾插,就实现了通过初始化列表来初始化list。
测试(test.cpp)
void test_list4(){list<int> lt1({ 1,2,3,4,5,6 });list<int> lt2 = { 1,2,3,4,5,6,7};print_container(lt1);print_container(lt2);}
对list初始化列表进行测试,lt1是通过传初始化列表{1,2,3,4,5,6}来构造lt1,lt2则是通过隐式类型转换来进行构造,则lt1结果为1,2,3,4,5,6,lt2结果为1,2,3,4,5,6,7,如(19)所示,结果正确,测试通过。

(19)
四、结语
本文围绕STL的list容器展开介绍,list底层以带头双向循环链表为结构基础,其接口用法上与string、vector类似,着重介绍了list的常用接口和用法,以及进一步实现了list的相关接口,需要注意的是,与string、vector不同的是,list的insert不会导致迭代器的失效,这是由于list的结点插入只涉及各结点的prev、next指针,并不涉及指向结点的指针,因此结点的迭代器并没有发生变化,迭代器并不失效,而erase涉及删除结点空间的释放,删除结点的迭代器就为野指针了,因此删除结点的迭代器失效,其他结点的迭代器不失效,删除结点后由于删除结点的迭代器失效,因此需要更新迭代器的值才能进行访问。此外,list迭代器的实现是list实现的一大难点,由于string、vector底层内存地址是连续的,因此可直接借助原生指针实现迭代器,而list基于链表为基本结构,各个结点之间不一定是连续的,因此不能直接通过原生指针来实现,对此需要通过对list迭代器进行封装,以及对operator*、operator->进行重载,从而实现迭代器的功能,通过模板,可以一并实现普通迭代器以及const修饰的迭代器,减少不必要的重复实现,list迭代器的实现也体现了化繁为简、转化等核心思想,这些思想和方法也是学习其他容器的核心方法,从list容器出发,以小见大,可以窥见更为浩瀚的STL世界!
