C++list全解析
1.list的说明与使用
1.1list的说明
附个链接:cplusplus.com/reference/list/list/#google_vignette,list其实与我们之前学过的string,vector非常类似,接下来详细介绍list常用的一些接口
1.2list构造函数
以下列举listC++98的构造函数:![]()
1.3list的iterator
list不支持operator[]这是因为如果list要实现,那么效率很低(要一个一个遍历)
函数名 描述 begin
返回指向容器第一个元素的迭代器 end
返回指向容器末尾(最后一个元素之后)的迭代器 rbegin
返回指向反向容器第一个元素的反向迭代器 rend
返回指向反向容器末尾的反向迭代器 cbegin
返回指向容器第一个元素的常量迭代器 cend
返回指向容器末尾的常量迭代器 crbegin
返回指向反向容器第一个元素的常量反向迭代器 crend
返回指向反向容器末尾的常量反向迭代器
void test_list01() {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++;}//可以用迭代器就可以用范围forfor (const auto& e : lt){cout << e << " ";}//迭代器不支持+/*it = lt.begin();lt.erase(it+3);*/ }
拓展:STL迭代器类别详解(性质由底层结构决定,并且决定了可以用哪些算法)
输入迭代器 (Input Iterator)
支持单向只读访问,适用于单遍扫描算法。操作包括前向递增(++
)、解引用(*
,仅右值)、成员访问(->
)以及相等性比较(==
/!=
)。典型应用如从输入流读取数据,示例容器为istream_iterator
。输出迭代器 (Output Iterator)
支持单向只写访问,适用于单遍扫描算法。操作仅包含前向递增(++
)和解引用(*
,仅左值),常用于写入数据到输出流,示例容器为ostream_iterator
。前向(单向)迭代器 (Forward Iterator)
扩展输入迭代器功能,支持读写操作和多遍扫描。允许重复解引用和移动,适用于需多次遍历的场景。示例容器包括std::forward_list
、std::unordered_map
和std::unordered_set
。双向迭代器 (Bidirectional Iterator)
在前向迭代器基础上增加双向移动能力,支持前置和后置递减(--
)。适用于需要逆向遍历的容器,如std::list
、std::map
和std::set
。随机访问迭代器 (Random Access Iterator)
功能最强大,支持随机访问和算术运算。额外操作包括:
- 偏移运算(
it + n
、it - n
、it += n
、it -= n
)- 距离计算(
it1 - it2
)- 下标访问(
it[n]
,等价于*(it + n)
)- 关系比较(
<
、>
、<=
、>=
)
典型容器为std::vector
、std::deque
、std::array
及普通指针(如int*
)。关键区别
- 访问方向:输入/输出迭代器仅单向,双向迭代器可逆向移动。
- 读写能力:输入迭代器只读,输出迭代器只写,其余均支持读写。
- 随机访问:仅随机访问迭代器支持直接跳转和算术运算。
能力强度:输入/输出迭代器 < 前向迭代器 < 双向迭代器 < 随机访问迭代器。
1.4list的容量相关成员函数
1.5list的元素访问成员函数
![]()
注意:
在调用
front()
或back()
之前,必须确保容器不为空空容器调用这些函数会导致未定义行为
建议先使用
empty()
检查容器状态reference就是引用
1.6修改器成员函数
assign
参数:
size_type n, const T& val
或InputIterator first, InputIterator last
返回值:void
时间复杂度:O(n)
用途:用指定数量的相同值或另一个序列中的元素替换链表当前内容。
push_front
参数:
const T& value
返回值:void
时间复杂度:O(1)
用途:在链表头部插入一个新元素,无需移动其他元素。
pop_front
参数:无
返回值:void
时间复杂度:O(1)
用途:删除链表头部的元素,链表不为空时调用。
push_back
参数:
const T& value
返回值:void
时间复杂度:O(1)
用途:在链表尾部追加一个新元素,无需移动其他元素。
pop_back
参数:无
返回值:void
时间复杂度:O(1)
用途:删除链表末尾的元素,链表不为空时调用。
insert
参数:
iterator position, const T& value
(多种重载)
返回值:iterator
时间复杂度:O(1)
用途:在指定位置插入一个或多个元素,返回指向新插入元素的迭代器。
erase
参数:
iterator position
或iterator first, iterator last
返回值:iterator
时间复杂度:O(1)
用途:删除指定位置或范围内的元素,返回指向被删除元素之后位置的迭代器。
swap
参数:
list& other
返回值:void
时间复杂度:O(1)
用途:高效交换两个链表的内容,仅交换内部指针。
resize
参数:
size_type n, const T& val = T()
返回值:void
时间复杂度:O(n)
用途:调整链表大小,若新大小超过当前大小,则用默认值或指定值填充;否则删除多余元素。
clear
参数:无
返回值:void
时间复杂度:O(n)
用途:清空链表,删除所有元素并释放内存。注意emplace系列现在这个阶段不会介绍,可以理解emplace类似push系列
1.7list 操作成员函数
2.list模拟实现
2.1list的push_back
void push_back(const T& x) {Node* newnode = new Node(x);Node* tail=_head->_prev;tail->_next = newnode;newnode->prev = tail;newnode->_next = _head;_head->_prev = newnode; }
2.2list的iterator(重点)
我们之前学过的string,vector底层都是顺序表,所以之前模拟实现的iterator,++进行迭代或者*解引用访问数据就可以是原生指针,但是如果这里还使用原生指针,就会出现问题:
- 解引用访问的是当前节点,而不是当前节点的数据
- 无法进行++迭代,因为节点地址不连续
这里就给出一个解决方案,封装一个迭代器类接着重载运算符实现++和解引用
ps:迭代器本身就是节点的指针,只是节点的指针不满足那些需求(++,*,...),所以用类去包装一层重载运算符,因此迭代器里面真正的数据还是节点的指针
iterator begin() {/*iterator it(_head->_next);return it;*///有名对象/*return iterator(_head->_next);*///匿名对象return _head->_next;//隐式类型转换 }iterator end()//end是数据的下一个位置,也就是_head {return _head; }
这里给了三种返回iterator的方法:
- 构造有名对象
- 构造匿名对象
- 隐式类型转换,将节点的指针转换为iterator
opreator->的实现
我们在用到结构的指针时会使用到->
struct AA {int _a1 = 1;int _a2 = 2; }; void test_list02() {list<AA> lta;lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator ita = lta.begin();while (ita != lta.end()){//cout << *ita << " ";err解决方法:1.给AA实现一个流插入2.重载->3.:如下面语句cout << (*ita)._a1 << (*ita)._a2 << endl;//特殊处理,本来是两个->,为了可读性,省略了一个cout << ita.operator->()->_a1 << ita->_a2 << endl;++ita;}cout << endl; }
法一:重载流插入
struct AA {int _a1 = 1;int _a2 = 2;// 重载流插入运算符friend ostream& operator<<(ostream& out, const AA& aa){out << "a1:" << aa._a1 << " a2:" << aa._a2;return out;} };
法二:重载->
T* operator->(){return &_node->_data;}
但这里可能有些读者会感到困惑,ita调用->,operator->返回T*(aa*)也就是_data的地址,那么aa*是怎么访问_a1和_a2的呢,实际上这里时两个->
- 第一个->是运算符重载,调用operator->返回aa*指针
- 第二个->是结构体指针的原生指针解引用
但编译器这里做了特殊处理(为了可读性两个->不方便)于是就省略了第二个箭头
方法三:直接访问成员,上面代码有演示。
const迭代器的实现
我们在实现普通迭代器之后,将之前写过的打印任意容器的函数粘贴过来
template<class Container> void print_container(const Container& con) {for (auto e : con){cout << e << " ";}cout << endl; }
在调用print_container(lta);发现程序报错,但是在这个函数外面使用范围for就正常,其实这里就涉及const迭代器问题
con
是const Container&
但
begin()
和end()
返回的是普通迭代器const 对象只能调用 const 成员函数
那么就要实现const的迭代器类。这里就要注意,之所以不直接使用const来修饰iterator,而是单独封装一个const_iterator类是因为
1.
const iterator
的含义(修饰的是iterator本身)
it
本身是 const(不能++it
)但
*it
仍然可以修改内容2.
const_iterator
的含义
it
本身可以移动(可以++it
)但
*it
是 const 的(不能修改指向的内容)
这里再提一下按需实例化的概念:
模板采用两阶段编译检查:
- 定义阶段:只检查不依赖模板参数的语法(如缺少分号、括号不匹配等)
- 实例化阶段:检查依赖模板参数的语义(如成员访问、运算符重载等)
如果Container的const_iterator解引用返回const引用:
- 在模板定义阶段不会报错(语法正确)
- 但在实例化阶段会报错:给常量赋值
这就是模板的"按需实例化"特性——错误检查延迟到实际使用时进行。
我们发现单独封装一个list_const_iterator类只有*,->不一样,太冗余了,这里还有其他的方式这里介绍一个模板共享的方式
通过共享同一个模板来减少代码重复,普通对象,和const对象,分别增加两个模板参数Ref和Ptr,Ref作为operaor*的返回值,Ptr作为operator->的返回值,但是这个其实与我们之前的方式没有本质区别,这里也是有两个类,之前的两个类是我们自己实现的两个类,而这里是实现了类模板给编译器,编译器实例化出来了两个类
list的迭代器失效问题
由于链表的空间不是连续的,在使用insert()进行插入操作不会引起迭代器失效的问题
我们发现在it前面执行插入10操作后对当前的迭代器执行+=100操作正常,所以insert就没有迭代器失效的问题。但是我们接着来看erase
在
erase(ito)
后,ito
指向的节点已经被删除,但ito
本身仍然指向那个已经被释放的内存地址。ito是野指针,于是+或者解引用程序崩溃,所以与之前一样,erase要实现返回下一个有效迭代器来更新迭代器//iterator erase(iterator pos) //{ // assert(pos != end()); // Node* prev = pos._node->_prev; // Node* next = pos._node->_next; // prev->_next = next; // next->_prev = prev; // // delete pos._node; // --_size; // return next; //} auto ito = lt.begin(); while (ito!=lt.end()) {if (*ito % 2 == 0)ito = lt.erase(ito);else++ito; } print_container(lt);
2.3list的insert,push_front,push_back
其实我们在实现完insert后就可以直接调用insert的逻辑来进行头插和尾插了
void insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;}void push_back(const T& x){insert(end(),x);}void push_front(const T& x){insert(begin(), x);}
2.4list的erase,pop_back,pop_front
注意在实现erase的时候,不能将哨兵位删除
void erase(iterator pos) {assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size; } void pop_back() {erase(--end());//end()返回的是_head所以要-- } void pop_front() {erase(begin()); }
2.5list的析构函数,clear
要实现链表的clear函数。我们要遍历链表,一个一个节点释放,析构函数则是在clear函数基础上将头节点释放
~list() {clear();delete _head;_head = nullptr; } void clear() {auto it = begin();while (it!=end()){it = erase(it);} }
2.6list的拷贝构造,空初始化函数,operator=
还是与之前一样当我们没有显示写拷贝构造或者赋值重载时,编译器自动生成的是浅拷贝,两个list指向同一块资源,就会出现问题。这里实现深拷贝
list(const list<T>& lt) {for (auto& e : lt){push_back(e);} }
这里还是有问题,因为调用push_back的前提是要有哨兵位,而且哨兵位的_next和_prev要指向自己形成最初的闭环结构,所以这里在实现一个空初始化函数
void empty_init() {_head = new Node(T());//这里是哨兵头节点,不能直接给0初始化这是因为T有可能是自定义类型_head->_next = _head;_head->_prev = _head;_size = 0; } //lt2(lt1) list(const list<T>& lt) {empty_init();for (auto& e : lt){push_back(e);} }
赋值重载实现:
void swap(list<int>& lt) {std::swap(_head, lt._head);std::swap(_size, lt._size); } //lt3 = lt1 list<T>& operator=(list<T>lt) {swap(lt);return *this; }
传值传参,形参lt是lt1的拷贝,然后将lt3的旧值与形参交换,则lt3就得到了新值,旧值给了形参出了作用域就销毁
3.模拟实现list完整代码
List.h
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include<assert.h>namespace name {template<class T>class list_node{public:T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data=T())//自定义类型调用默认构造:_data(data),_next(nullptr),_prev(nullptr){}};template<class T,class Ref,class Ptr>class list_iterator//也可以使用struct(默认为public){public:typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}Ref operator*()//返回引用支持修改{return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++()//返回自己类型的迭代器{_node = _node->_next;return *this;}Self& operator++(int)//后置++{Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--()//返回自己类型的迭代器{_node = _node->_prev;return *this;}Self& operator--(int)//后置--{Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s)const{return _node != s._node;}bool operator==(const Self& s)const{return _node == s._node;}};//template<class T>//class list_iterator//也可以使用struct(默认为public)//{//public:// typedef list_node<T> Node;// typedef list_iterator<T> Self;// Node* _node;// list_iterator(Node* node)// :_node(node)// {}// T& operator*()//返回引用支持修改// {// return _node->_data;// }// T* operator->()// {// return &_node->_data;// }// Self& operator++()//返回自己类型的迭代器// {// _node = _node->_next;// return *this;// }// Self& operator++(int)//后置++// {// Self tmp(*this);// _node = _node->_next;// return tmp;// }// Self& operator--()//返回自己类型的迭代器// {// _node = _node->_prev;// return *this;// }// Self& operator--(int)//后置--// {// Self tmp(*this);// _node = _node->_prev;// return tmp;// }// bool operator!=(const Self&s)const// {// return _node != s._node;// }// bool operator==(const Self& s)const// {// return _node == s._node;// }//};//template<class T>//class list_const_iterator//也可以使用struct(默认为public)//{//public:// typedef list_node<T> Node;// typedef list_const_iterator<T> Self;// Node* _node;// list_const_iterator(Node* node)// :_node(node)// {// }// const T& operator*()//返回const引用不支持修改// {// return _node->_data;// }// const T* operator->()// {// return &_node->_data;// }// Self& operator++()//返回自己类型的迭代器// {// _node = _node->_next;// return *this;// }// Self& operator++(int)//后置++// {// Self tmp(*this);// _node = _node->_next;// return tmp;// }// Self& operator--()//返回自己类型的迭代器// {// _node = _node->_prev;// return *this;// }// Self& operator--(int)//后置--// {// Self tmp(*this);// _node = _node->_prev;// return tmp;// }// bool operator!=(const Self& s)const// {// return _node != s._node;// }// bool operator==(const Self& s)const// {// return _node == s._node;// }//};template<class T>class list{typedef list_node<T> Node;public://typedef list_const_iterator<T> const_iterator;//typedef list_iterator<T> iterator;//注意typedef也是受到访问限定符的限制的,也就是说//iterator可以在外面使用而Node不行typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;iterator begin(){/*iterator it(_head->_next);return it;*///有名对象/*return iterator(_head->_next);*///匿名对象return _head->_next;}iterator end()//end是数据的下一个位置,也就是_head{return _head;}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}list(){_head = new Node(T());//这里是哨兵头节点,不能直接给0初始化这是因为T有可能是自定义类型_head->_next = _head;_head->_prev = _head;_size = 0;}list(initializer_list<T> il){empty_init();//给一个哨兵位节点for (auto& e : il){push_back(e);}}~list(){clear();delete _head;_head = nullptr;}void clear(){auto it = begin();while (it!=end()){it = erase(it);}}void empty_init(){_head = new Node(T());//这里是哨兵头节点,不能直接给0初始化这是因为T有可能是自定义类型_head->_next = _head;_head->_prev = _head;_size = 0;}//lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}void swap(list<int>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt3 = lt1list<T>& operator=(list<T>lt){swap(lt);return *this;}//void push_back(const T& x)//{// Node* newnode = new Node(x);// Node* tail=_head->_prev;// tail->_next = newnode;// newnode->_prev = tail;// newnode->_next = _head;// _head->_prev = newnode;// _size++;//}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}void push_back(const T& x){insert(end(),x);}void push_front(const T& x){insert(begin(), x);}iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return next;}void pop_back(){erase(--end());//end()返回的是_head所以要--}void pop_front(){erase(begin());}size_t size()const{return _size;}bool empty()const{//也可以/*return _head->next == _head;*/return _size == 0;}private:Node* _head;size_t _size;//为了防止遍历,这里加一个成员size记录list的大小};/*template<class Container>//声明void print_container(const Container& con);*/template<class Container>void print_container(const Container& con){//const iterator->迭代器本身不能修改//const_iterator->指向的内容不能修改typename Container::const_iterator cit = con.begin();//要加typename来取模板里面的内容//list<int>::const_iterator cit = con.begin();/*auto cit = con.begin();*/while (cit != con.end()){//*cit+=10;cout << *cit << " ";++cit;}}struct AA{int _a1 = 1;int _a2 = 2;};}
Test.cpp
#pragma once #include"List.h"namespace name {void test_list01(){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;//可以用迭代器就可以用范围forfor (const auto& e : lt){cout << e << " ";}cout << endl;print_container(lt);}void test_list02(){list<AA> lta;lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator ita = lta.begin();while (ita != lta.end()){//cout << *ita << " ";err解决方法:1.给AA实现一个流插入2.重载->3.:如下面语句cout << (*ita)._a1 << (*ita)._a2 << endl;//特殊处理,本来是两个->,为了可读性,省略了一个cout << ita.operator->()->_a1 << ita->_a2 << endl;++ita;}cout << endl;}void test_list03(){list<int> lt;lt.push_back(1);lt.push_back(2);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);cout << endl;//删除所有的偶数auto ito = lt.begin();while (ito!=lt.end()){if (*ito % 2 == 0)ito = lt.erase(ito);else++ito;}print_container(lt);}void test_list04(){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(lt1);print_container(lt2);list<int> lt3;lt3 = lt1;print_container(lt2);}void function(const list<int>& lt){cout<<"fuhction:"<< endl;print_container(lt);cout << endl;}void test_list05(){auto il = {10,20,30};//class std::initializer_list<int>cout<<typeid(il).name()<<endl;//initializer_list类里面有两个指针,一个指向列表开始位置,一个指向结束位置cout << sizeof(il) << endl;list<int> lt1 = { 1,2,3,4,5,6 };//隐式类型转换list<int> lt2({ 1,2,3,4,5,6 });//直接构造print_container(lt1);print_container(lt2);cout << endl;const list<int>& lt = {1,2,3,4,5,6};//引用临时对象function(lt1);function({ 1,2,3,4,5,6 });//单参数构造函数支持隐式类型转换} }int main() {//name::test_list01();//name::test_list02();//name::test_list03();//name::test_list04();name::test_list05(); }