当前位置: 首页 > news >正文

从基本用法到迭代器实现—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世界!

http://www.dtcms.com/a/597151.html

相关文章:

  • 智能建站软件宁波房产网二手房出售
  • 如何从iPhone向Android 发送视频?8 种方法
  • LLM 相关内容
  • 研发管理知识库(4)华为研发管理流程简介
  • 【国内电子数据取证厂商龙信科技】手机取证之文件碎片
  • 【OpenCV + VS】OpenCV初步:在VS中配置并运行第一个OpenCV Demo
  • Java入门——Java跨平台的原理
  • 16、做中学 | 初三上期 Golang面向对象_进阶
  • Java 不同创建线程的方式什么时候才可以使用 this 来获取线程的引用
  • 兰州做网站的公司wordpress标签云美化
  • MATLAB基于PSO-GA的铁路工程施工进度计划多目标优化研究
  • JavaScript的BOM学习笔记——1、浏览器对象模型
  • python将Excel数据写进图片中
  • 五金配件网站建设报价圣弓 网站建设
  • Django中如何重写save()方法
  • C在线编程 | 提升编程技能,掌握C语言的核心要点
  • 京东这样的网站怎么做网站建设费用怎么算
  • django模型数据查询
  • 佛山骏域网站建设软件开发价格标准
  • discuz企业网站一诺摄影设计
  • 基于微信小程序的特色农产品交易系统
  • 【windows常见问题】pin不可用,无法登录Windows
  • 免费正能量励志网站网站登陆界面怎么做
  • 网站建设找丿金手指排名在iis上部署的网站本机无法浏览解决方法
  • 【Android Studio】解决4K电视机上,网页无法适配的问题
  • 如何选择适合自动化的测试用例?
  • 一步一步网站建设教程联通 网站备案
  • 著名心理学导师钧岚确认出席2025厦门IP+AI万人峰会​
  • 10.游戏逆向-pxxx-UObjectBase成员解密
  • 触发器,存储过程