目录
一、前言
二、 什么是 list ?
? list 的存储结构
? list 容器的优点
? list 容器的缺点
三、list 容器的定义
四、list 容器常用接口的使用
?list 的常见构造(初始化)
?list 的遍历及迭代器的操作
① 迭代器
② 范围for
?list 容器的常见容量操作
① size
② resize
③ empty
④ clear
?list 容量的常见访问操作
① front()——访问list头元素
② back()——访问list尾元素
?list 容器的常见修改操作
① push_back()——添加元素(list尾部)
② pop_back()——移除list元素(尾部)
③ pop_front()——删除list元素(头部)
④ push_front()——添加元素(list头部)
⑤ insert()——添加元素(任意位置)
⑥ erase()——删除元素(任意位置)
⑦ swap()——交换元素
?list 容器的常见操作函数
① splice()——从另一个list中移动元素
② remove()——移除特定值的元素
③ remove_if()——移除满足特定标准的元素
④ unique()——删除连续的重复元素
⑤ sort()——对元素进行排序
⑥ merge()——合并list
⑦ reverse()——将该链表的所有元素的顺序反转 【C++11】
⑧ assign()——将值赋给容器
五、vector 与 list 的对比
六、共勉
一、前言
最近在刷 leetcode 的时候,发现 list 都还没弄明白吗,但是 STL 的强大是众所周知滴,早晚都是要解决滴,因此专门写下这篇文章,以供自己复习和各位老铁使用,快速的回忆 list 的用法,让你找回自信,不用再竞赛的时候颜面尽失。
本次博客主要讲解 list 容器的基本操作、常用接口做一个系统的整理,结合具体案例熟悉自定义内部排序方法的使用,请大家持续关注我O!!
二、 什么是 list ?
list是C++的一个序列容器,插入和删除元素的效率较高,时间复杂度为常数级别,list容器的底层数据结构为带头双向循环链表,这使得 list的元素可以存储在非相邻的内存中,在list内部,不同元素之间通过指向前一个元素的指针以及指向后一个元素的指针相关联。
list 容器与其他序列容器如vector deque array相比,由于其底层数据结构为带头双向循环链表,因此 list 在插入删除元素方面很有优势,在列表的任意位置进行插入和删除操作的时间复杂度为O(1)。但不能直接通过位置(下标)来直接访问元素。想要访问list的某个元素,必须从list的一端(或已知位置)迭代到该元素。另外,list还需要额外的存储空间来储存前一个元素和后一个元素的链接信息。
? list 的存储结构
list 容器底层为带头双向循环链表,带头双向循环链表每个节点包含指向前驱节点的pre指针,指向后继节点的next指针以及节点的数据。list存储结构如下图所示: 哨兵节点表示 链表的第一个元素节点,且不保存任何数据
如果还有老铁对 带头双向循环链表不太了解的可以看看这篇文章:详解带头双向循环链表
? list 容器的优点
高效的插入和删除:由于std::list是基于带头双向循环链表实现的,插入和删除操作在任意位置都具有常数时间复杂度O(1),不需要移动其他元素。这使得std::list在需要频繁插入和删除元素的场景下非常高效。稳定的迭代器:在std::list中进行插入和删除操作不会使得迭代器失效。这意味着在插入或删除元素后,仍然可以继续使用之前获取的迭代器进行遍历和操作。动态内存管理:std::list可以动态调整大小,根据需要分配和释放内存。这使得std::list能够有效地处理大量元素的情况,而不会浪费过多的内存空间。? list 容器的缺点
低效的随机访问:由于std::list的存储结构是带头双向循环链表,访问元素时需要从头或尾开始遍历链表,因此在列表中进行随机访问的效率较低。获取特定位置的元素需要遍历链表,时间复杂度为O(n),其中n是元素的总数量。占用额外内存:相较于其他容器,std::list在存储上需要额外的指针来维护链表结构,因此在存储大量元素时,它可能占用更多的内存空间。迭代器不支持指针算术:std::list的迭代器不支持指针算术运算,无法像指针那样直接进行加减操作,这限制了一些操作的灵活性。头文件:
list是C++ 标准模板库的一部分,因此,想要使用list,需要在程序中包含头文件 list
#include<list>
三、list 容器的定义
单独定义一个 list:
list <typename> name;
这里的typename可以是任何基本类型,例如int、double、char、结构体等,也可以是STL标准容器,例如string、set、queue、vector等。
注意:使用前必须加上头文件:#include <list>
代码展示:
#include <iostream>#include <list>using namespace std;int main(){int a[3]; // 正常定义的-----静态数组list<int> str_a; // list 定义的 int类型的链表char b[3];list<char> str_b;return 0;}
四、list 容器常用接口的使用
?list 的常见构造(初始化)
接口名称 | 接口说明 |
list() (⭐) | 构造空的list |
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list (const list& x)(⭐) | 拷贝构造函数 |
list (InputIterator first, InputIterator last | 用[first, last)区间中的元素构造list |
注意: ⭐表示重点掌握
方式一: 构造一个某类型的空容器:
list<数据类型> 函数名; 初始化为空。
list<int> lt1; //构造int类型的空容器
方式二: 构造一个含有n个val的某类型容器:
list<int> lt2(10, 2); //构造含有10个2的int类型容器
方式三: 拷贝构造某类型容器的复制品:
list<int> lt3(lt2); //拷贝构造int类型的lt2容器的复制品
方式四: 使用迭代器拷贝构造某一段内容:
string s("hello world");list<char> lt4(s.begin(),s.end()); //构造string对象某段迭代器区间的内容
方式五: 构造数组某段区间的内容:
int arr[] = { 1, 2, 3, 4, 5 };int sz = sizeof(arr) / sizeof(int);list<int> lt5(arr, arr + sz); //构造数组某段区间的复制品 ->本质是调用迭代器区间的构造函数
方式五: 使用花括号构造内容(C++11):
list<int> lt{ 1,2,3,4,5 }; // 直接使用花括号进行构造---C++11允许
代码展示1(实用):
#include <iostream>#include <list>using namespace std;int main(){list<int> frist; // 构造一个没有元素的空容器list<int> second(2, 10); // 2个值为10的整数list<int> third(second.begin(), second.end()); // 迭代器构造list<int> fourth(third); // 拷贝构造// 迭代器构造函数也可以使用数组来进行构造,传的区间是左闭右开// 因为指向数组空间的指针是天然的迭代器int arr[] = { 16,2,77,29 };list<int> fifth(arr, arr + 4);// std::list<int> fifth (arr, arr + sizeof(arr) / sizeof(int) );// first : []// second: [10,10]// third : [10,10]// fourth: [10,10]// fifth : [16,2,77,29]return 0;}
效果展示:
代码展示2(不实用):
#include <iostream>#include <list>using namespace std;int main(){// 用其它容器的迭代器初始化,只要数据d类型可以匹配上string s("hello");list<char> v(s.begin(), s.end());for (auto& e : v){cout << e << " ";}cout << endl;return 0;}
?list 的遍历及迭代器的操作
接口名称 | 接口说明 |
迭代器(⭐) | begin() + end() 或者 rbegin() + rend() |
范围for | C++11支持更简单的for的新遍历方式(底层还是借用迭代器实现) |
注意:(遍历链表-------只能用**迭代器**和**范围for**)
begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动① 迭代器
接口名称 | 接口说明 |
begin()(⭐) | 返回第一个元素的迭代器 |
end()(⭐) | 返回最后一个元素下一个位置的迭代器 |
rbegin() | 返回最后一个元素的迭代器 |
rend() | 返回第一个元素的前一个位置额迭代器 |
begin 和 end
通过begin函数可以得到容器中第一个元素的正向迭代器,通过end函数可以得到容器中最后一个元素的后一个位置的正向迭代器。正向迭代器遍历容器:
#include <iostream>#include <list>using namespace std;int main(){int array[] = { 1,2,3,4,5,6,7,8 };// 用已有的数组 初始化 list 链表list<int> lt(array, array + sizeof(array) / sizeof(array[0]));// 正向迭代器遍历容器list<int>::iterator it = lt.begin();// 开始循环遍历while (it != lt.end()){cout << *it << " "; // 1 2 3 4 5 6 7 8it++;}cout << endl;return 0;}
注意:begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动 rbegin和rend
通过rbegin函数可以得到容器中最后一个元素的反向迭代器,通过rend函数可以得到容器中第一个元素的前一个位置的反向迭代器。反向迭代器遍历容器:
#include <iostream>#include <list>using namespace std;int main(){int array[] = { 1,2,3,4,5,6,7,8 };// 用已有的数组 初始化 list 链表list<int> lt(array, array + sizeof(array) / sizeof(array[0]));// 正向迭代器遍历容器list<int>::reverse_iterator it = lt.rbegin();// 开始循环遍历while (it != lt.rend()){cout << *it << " "; // 8 7 6 5 4 3 2 1it++;}cout << endl;return 0;}
注意:rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动 ② 范围for
如果支持迭代器的话,一定支持范围for
#include <iostream>#include <list>#include <vector>using namespace std;int main(){vector<int> v{ 1,2,3,4,5 };list<int> lt(v.begin(), v.end());// C++ 范围for 的方式遍历for (auto& e : lt){cout << e << " ";}cout << endl;// 输出 :1 2 3 4 5return 0;}
?list 容器的常见容量操作
接口名称 | 接口说明 |
size | 返回容器中有效元素个数 |
resize(⭐) | 调整容器的有效元素大小(size) |
empty | 判断容器是否为空 |
clear | 用于清空容器,清空后容器的size为0, 但是头结点(哨兵位)不会被清除 |
① size
size函数用于获取当前容器当中的元素个数,
#include <iostream>#include <list>using namespace std;int main(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);cout << lt.size() << endl; //4return 0;}
② resize
在C++中,list 容器提供了resize()方法,用于调整列表的大小。使用resize()方法可以重新指定list的大小,并根据需要插入或删除list中的元素来达到指定的大小。
resize()方法有两种重载形式:
void resize(size_type count, const T& value = T()):这个重载会将列表的大小调整为count。如果count大于当前列表的大小,则在列表末尾插入足够数量的值为value的元素。如果count小于当前列表的大小,则删除超出count的元素。
void resize(size_type count):这个重载会将列表的大小调整为count。如果count大于当前列表的大小,则在列表末尾插入默认构造的元素。如果count小于当前列表的大小,则删除超出count的元素。
代码示例:
#include <iostream>#include <list>using namespace std;int main() { list<int> mylist = { 1, 2 }; // 把mylist的大小设为5 mylist.resize(5); cout << "第一次resize后list中元素为:"; for (auto a : mylist) { cout << a << " "; } cout << endl; // 把mylist的大小设为1 mylist.resize(1); cout << "第二次resize后list中元素为:"; for (auto a : mylist) { cout << a << " "; } cout << endl; // 把list大小设为4,不足部分填充5 mylist.resize(4, 5); cout << "第三次resize后list中元素为:"; for (auto a : mylist) { cout << a << " "; } cout << endl;}
效果展示:
③ empty
empty()方法用来判断list是否为空,如果为空,返回true;如果非空,返回false。示例如下:
?示例代码?
#include<iostream>#include<list>using std::cout;using std::endl;using std::list;int main () { list<int> mylist1; list<int> mylist2 = {1, 2}; cout<< "mylist1是否为空?"<< mylist1.empty() << endl; cout<< "mylist2是否为空?"<< mylist2.empty() << endl;}
?效果展示?
④ clear
clear 函数用于清空容器,清空后容器的size为0, 但是头结点(哨兵位)不会被清除
#include <iostream>#include <list>using namespace std;int main(){list<int> lt(5, 2);for (auto e : lt){cout << e << " ";}cout << endl; //2 2 2 2 2cout << lt.size() << endl; //5lt.clear(); //清空容器for (auto e : lt){cout << e << " ";}cout << endl; //(无数据)cout << lt.size() << endl; //0return 0;}
?list 容量的常见访问操作
① front()——访问list头元素
❤front()返回list第一个元素❤
示例如下:
?示例代码?
#include<iostream>#include<list>using std::cout;using std::endl;using std::list;int main() { list<int> mylist {1, 2, 3, 4, 5, 6, 7, 8}; cout<< "初始化后的mylist为:"; for (auto num : mylist) { cout<< num<< " "; } int front = mylist.front(); // 1 cout<< "\nmylist的头元素为:"<< front;}
② back()——访问list尾元素
❤back()返回list第一个元素❤
示例如下:
?示例代码?
#include<iostream>#include<list>using std::cout;using std::endl;using std::list;int main() { list<int> mylist {1, 2, 3, 4, 5, 6, 7, 8}; cout<< "初始化后的mylist为:"; for (auto num : mylist) { cout<< num<< " "; } int front = mylist.front(); // 8 cout<< "\nmylist的头元素为:"<< front;}
?list 容器的常见修改操作
接口名称 | 接口说明 |
push_front | 在list首元素前插入值为val的元素 |
pop_front | 删除list中第一个元素 |
push_back | 在list尾部插入值为val的元素 |
pop_back | 删除list中最后一个元素 |
insert(⭐) | 在list position 位置中插入值为val的元素 |
erase(⭐) | 删除list position位置的元素 |
swap | 交换两个list中的元素 |
① push_back()——添加元素(list尾部)
向list中添加元素,使用push_back()
方法,作用是向list尾部添加一个元素。示例如下:
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // 在list尾部插入一个元素8 mylist.push_back(8); cout << "\n尾部插入一个元素后的mylist为:"; for (auto num : mylist) { cout << num << " "; // 1 2 3 4 8 }}
② pop_back()——移除list元素(尾部)
删除list中的元素,使用pop_back()
方法,作用是删除list尾部的一个元素。示例如下:
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // 删除mydeue尾部一个元素 mylist.pop_back(); cout << "\n尾部删除一个元素后的mylist为:"; for (auto num : mylist) { cout << num << " "; // 1 2 3 }}
③ pop_front()——删除list元素(头部)
删除list中的元素,使用pop_front()
方法,作用是删除list头部的一个元素。示例如下:
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // 删除mydeue头部一个元素 mylist.pop_front(); cout << "\n头部删除一个元素后的mylist为:"; for (auto num : mylist) { cout << num << " "; // 2 3 4 }}
④ push_front()——添加元素(list头部)
向list中添加元素,使用push_front()
方法,作用是向list头部添加一个元素。示例如下:
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // 在list头部插入一个元素8 mylist.push_front(8); cout << "\n头部插入一个元素后的mylist为:"; for (auto num : mylist) { cout << num << " "; // 8 1 2 3 4 } }
⑤ insert()——添加元素(任意位置)
向list中添加元素。
insert共有三种形式:
- insert(iterator, value);
- insert(iterator, num, value);
- insert(iterator, iterator1, iterator2);
用法一:list.insert(iterator,value)
使用insert(iterator,value)
方法,作用是向iterator迭代器指向元素的前边添加一个元素value,并返回一个迭代器指向新插入的元素。
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // it指向mylist的第二个元素 list<int>::iterator it = mylist.begin() + 1; // 使用insert添加一个元素 list<int>::iterator itnew = mylist.insert(it, 10); cout << "\n返回的迭代器指向的元素为" << *itnew; cout << "\ninsert添加一个元素后的mylist为:"; for (auto num : mylist) { cout << num << " "; }}
?输出?
初始化后的mylist为:1 2 3 4
返回的迭代器指向的元素为10
insert添加一个元素后的mylist为:1 10 2 3 4
用法二:list.insert(iterator,num,value)
使用insert(iterator,num,value)
方法,作用是向iterator迭代器指向元素的前边添加num个元素value,并返回一个迭代器指向新插入的第一个元素.
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // it指向mylist的第二个元素 list<int>::iterator it = mylist.begin() + 1; // 使用insert添加2个元素,value为20 mylist.insert(it, 2, 20); cout << "\n使用insert插入元素后:"; for (auto num : mylist) { cout << num << " "; }}
?输出?
初始化后的mylist为:1 2 3 4
使用insert插入元素后:1 20 20 2 3 4
用法三:insert(iterator, iterator1, iterator2)
使用insert(iterator, iterator1, iterator2);
方法,作用是向iterator迭代器指向元素的前边添加[iterator1,iterator2)之间的元素。
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // it指向mylist的第二个元素 list<int>::iterator it = mylist.begin() + 1; // 定义一个辅助list list<int> list2{10, 20, 30}; // it1指向list2的第一个元素 list<int>::iterator it1 = list2.begin(); // it2指向list2的最后一个元素后一个位置 list<int>::iterator it2 = list2.end(); // 使用insert在2之前添加[it1,it2)之间的元素 mylist.insert(it, it1, it2); cout << "\n使用insert插入元素后:"; for (auto num : mylist) { cout << num << " "; }}
?输出?
初始化后的mylist为:1 2 3 4
使用insert插入元素后:1 10 20 30 2 3 4
⑥ erase()——删除元素(任意位置)
❤erase的作用就是根据传入的迭代器删除list中的元素,参数为一个迭代器,只删除迭代器指向的元素;参数为两个迭代器,删除两个迭代器之间的元素❤
erase 共有 两 种形式:
- list.erase(iterator)
- list.erase(iterator1,iterator2)
用法一:list.erase(iterator)
这种用法会删除迭代器iterator指向的元素。
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // it指向mylist的第二个元素 list<int>::iterator it = mylist.begin() + 1; // 删除it指向的元素,即2,并返回一个迭代器指向2之后的元素 list<int>::iterator itnew = mylist.erase(it); cout << "\n删除元素后返回的迭代器itnew指向的元素为:" << *itnew; cout << "\n使用erase删除元素后:"; for (auto num : mylist) { cout << num << " "; }}
?输出?
初始化后的mylist为:1 2 3 4
删除元素后返回的迭代器itnew指向的元素为:3
使用erase删除元素后:1 3 4
用法二:list.erase(iterator1,iterator2)
这种用法会删除迭代器iterator1指向的元素到iterator2指向元素之间的元素,包括iterator1指向的元素但不包括iterator2指向的元素,即擦除[iterator1,iterator2)。
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist{1, 2, 3, 4}; cout << "初始化后的mylist为:"; for (auto num : mylist) { cout << num << " "; } // it1指向mylist的第二个元素 list<int>::iterator it1 = mylist.begin() + 1; // it2指向mylist的最后一个元素后一个位置 list<int>::iterator it2 = mylist.end(); // 删除[it1,it2)之间的元素,即删除2,3,4 mylist.erase(it1, it2); cout << "\n使用erase删除元素后:"; for (auto num : mylist) { cout << num << " "; }}
?输出?
初始化后的mylist为:1 2 3 4
使用erase删除元素后:1
⑦ swap()——交换元素
❤swap的作用就是交换两个list的元素❤
交换两个list的元素,使用swap()
方法,list1.swap(list2)
,两个list存储的元素类型必须相同,元素个数可以不同。示例如下:
?示例代码?
#include <list>#include <iostream>using std::cout;using std::list;using std::endl;int main() { list<int> mylist1{1, 11, 111, 1111}; list<int> mylist2{2, 22, 222}; cout << "初始化后的mylist1为:"; for (auto num : mylist1) { cout << num << " "; } cout << "\n初始化后的mylist1为:"; for (auto num : mylist2) { cout << num << " "; } // 交换mylist1和mylist2的元素 mylist1.swap(mylist2); cout << "\n使用swap交换元素后mylist1:"; for (auto num : mylist1) { cout << num << " "; } cout << "\n使用swap交换元素后mylist2:"; for (auto num : mylist2) { cout << num << " "; }}
?输出?
初始化后的mylist1为:1 11 111 1111
初始化后的mylist1为:2 22 222
使用swap交换元素后mylist1:2 22 222
使用swap交换元素后mylist2:1 11 111 1111
?list 容器的常见操作函数
函数声明 | 接口说明 |
splice | 将元素从列表转移到其它列表 |
remove | 删除具有特定值的元素 |
remove_if | 删除满足条件的元素 |
unique | 删除重复值 |
sort | 容器中的元素排序 |
merge | 合并排序列表 |
reverse | 反转元素的顺序 |
① splice()——从另一个list中移动元素
splice 方法用于将另一个 std::list 的元素插入到当前列表的指定位置。示例如下:
splice共有四种形式,分别为:
splice(iterator_pos, otherList) : 将otherList中的所有元素移动到iterator_pos指向元素之前splice(iterator_pos, otherList, iter1): 从 otherList转移 iter1 指向的元素到当前list。元素被插入到 iterator_pos指向的元素之前。splice(iterator_pos, otherList, iter_start, iter_end) : 从 otherList转移范围 [iter_start, iter_end) 中的元素到 当前列表。元素被插入到 iterator_pos指向的元素之前。注意:
1. splice 操作不会进行元素的复制或移动,只是修改指针连接,因此效率较高。
2. 在 splice 操作后,被移动的元素将不再属于原始列表。
3. splice 操作后,原始列表和插入列表的大小会相应改变。
4. 插入操作的时间复杂度为 O(1)
?示例代码?
#include <iostream>#include <list>int main() { std::list<int> list1 = {1, 2, 3, 4, 5}; std::list<int> list2 = {10, 20, 30}; std::list<int> list3 = {1, 2, 3, 4, 5}; std::list<int> list4 = {10, 20, 30}; std::list<int> list5 = {1, 2, 3, 4, 5}; std::list<int> list6 = {10, 20, 30}; /* 用法一 : splice(iterator_pos, otherList) */ // 将list2中所有元素插入到list1的第一个位置 list1.splice(list1.begin(), list2); // 输出结果 std::cout<< "转移list2元素到list1之后的list1: "; for (int i : list1) { std::cout << i << " "; } std::cout << std::endl; std::cout<< "转移list2元素到list1之后的list1: "; for (int i : list2) { std::cout << i << " "; } std::cout << std::endl; /* 用法二:splice(iterator_pos, otherList, iter1) */ auto it = list3.begin(); // 将迭代器向后移动两个位置,指向第三个元素 std::advance(it, 2); // 将list4的第一个元素(10)插入到list3的第三个位置 list3.splice(it, list4, list4.begin()); // 输出结果 std::cout<< "转移list4元素到list3之后的list3: "; for (int i : list3) { std::cout << i << " "; } std::cout << std::endl; std::cout<< "转移list4元素到list3之后的list4: "; for (int i : list4) { std::cout << i << " "; } std::cout << std::endl; /* 用法三:splice(iterator_pos, otherList, iter_start, iter_end) */ // 将list5中第二个到第四个元素移动到list6的末尾 auto first = list5.begin(); std::advance(first, 1); auto last = list5.begin(); std::advance(last, 4); list6.splice(list6.end(), list5, first, last); // 输出结果 std::cout<< "转移list5元素到list6之后的list5: "; for (int i : list5) { std::cout << i << " "; } std::cout << std::endl; std::cout<< "转移list5元素到list6之后的list6: "; for (int i : list6) { std::cout << i << " "; } std::cout << std::endl; return 0;}
?初始化的值?
std::list<int> list1 = {1, 2, 3, 4, 5};std::list<int> list2 = {10, 20, 30};std::list<int> list3 = {1, 2, 3, 4, 5};std::list<int> list4 = {10, 20, 30};std::list<int> list5 = {1, 2, 3, 4, 5};std::list<int> list6 = {10, 20, 30};
?输出?
转移list2元素到list1之后的list1: 10 20 30 1 2 3 4 5
转移list2元素到list1之后的list1:
转移list4元素到list3之后的list3: 1 2 10 3 4 5
转移list4元素到list3之后的list4: 20 30
转移list5元素到list6之后的list5: 1 5
转移list5元素到list6之后的list6: 10 20 30 2 3 4
② remove()——移除特定值的元素
std::list 的 remove 方法用于从列表中移除与指定值相等的元素。示例如下:
?示例代码?
#include <iostream>#include <list>int main() { std::list<int> mylist {1, 2, 3, 4, 5}; // 移除列表中所有值为2的元素 mylist.remove(2); std::cout << "移除之后的mylist: "; for (const auto& element : mylist) { std::cout << element << " "; } std::cout << std::endl; return 0;}
?输出?
移除之后的mylist: 1 3 4 5
③ remove_if()——移除满足特定标准的元素
使用方式:remove_if(func)
使用 remove_if 方法可以从 mylist 中移除所有满足 函数func的元素,下边的例子中定义了一个函数isEven(),判断一个数字是否为偶数。示例如下:
?示例代码?
#include <iostream>#include <list>bool isEven(int n) { return n % 2 == 0;}int main() { std::list<int> mylist {1, 2, 3, 4, 5}; // 移除列表中所有满足 isEven 的元素(偶数) mylist.remove_if(isEven); std::cout << "移除之后的mylist: "; for (const auto& element : mylist) { std::cout << element << " "; } std::cout << std::endl; return 0;}
?输出?
移除之后的mylist: 1 3 5
④ unique()——删除连续的重复元素
unique()方法用于移除链表中相邻且重复的元素,仅保留一个副本。示例如下:
?示例代码?
#include <iostream>#include <list>int main() { std::list<int> myList = {1, 2, 2, 3, 3, 4, 5, 5, 5}; std::cout << "初始化后的list为: "; for (auto i : myList) { std::cout << i << " "; } std::cout << std::endl; myList.unique(); std::cout << "执行unique后的list为: "; for (auto i : myList) { std::cout << i << " "; } std::cout << std::endl; return 0;}
?输出?
初始化后的list为: 1 2 2 3 3 4 5 5 5
执行unique后的list为: 1 2 3 4 5
⑤ sort()——对元素进行排序
sort()方法用于对链表中的元素进行排序。默认情况下,它按升序对元素进行排序,使用元素类型的 < 运算符进行比较。示例如下:
?示例代码?
#include <iostream>#include <list>int main() { std::list<int> myList = {5, 3, 2, 4, 1}; std::cout << "初始化后的list为 "; for (auto i : myList) { std::cout << i << " "; } std::cout << std::endl; myList.sort(); std::cout << "排序后的list为: "; for (auto i : myList) { std::cout << i << " "; } std::cout << std::endl; return 0;}
?输出?
初始化后的list为 5 3 2 4 1
排序后的list为: 1 2 3 4 5
⑥ merge()——合并list
将两个已排序的列表合并成一个有序的列表。merge()方法将另一个列表的元素插入到调用方法的列表中,并保持排序顺序。示例如下:
注意:merge()方法只能用于已排序的list。如果list未排序,则合并的结果将是不正确的。
?示例代码?
#include <iostream>#include <list>using namespace std;int main() { std::list<int> list1 = {1, 3, 5}; std::list<int> list2 = {2, 4, 6}; cout << "初始化后的list1为:"; for (auto num : list1) { cout << num << " "; } std::cout << std::endl; cout << "初始化后的list2为:"; for (auto num : list2) { cout << num << " "; } std::cout << std::endl; // 将 list2 合并到 list1 中 list1.merge(list2); // 输出合并后的列表 cout << "合并后的list1为:"; for (const auto& element : list1) { std::cout << element << " "; } std::cout << std::endl; return 0;}
?输出?
初始化后的list1为:1 3 5
初始化后的list2为:2 4 6
合并后的list1为:1 2 3 4 5 6
⑦ reverse()——将该链表的所有元素的顺序反转 【C++11】
reverse()方法用于反转链表中元素的顺序,即将链表中的第一个元素变为最后一个元素,第二个元素变为倒数第二个元素,以此类推。示例如下:
?示例代码?
#include <iostream>#include <list>int main() { std::list<int> myList = {1, 2, 3, 4, 5}; std::cout << "初始化list为: "; for (const auto& element : myList) { std::cout << element << " "; } std::cout << std::endl; myList.reverse(); std::cout << "Reversed后的list为: "; for (const auto& element : myList) { std::cout << element << " "; } std::cout << std::endl; return 0;}
?输出?
初始化list为: 1 2 3 4 5
Reversed后的list为: 5 4 3 2 1
⑧ assign()——将值赋给容器
assign()方法用于将链表中的元素替换为新的元素序列。它可以接受不同形式的参数,提供了两种重载形式。
第一种形式接受迭代器范围作为参数,用于将另一个容器或数组中的元素复制到链表中。它会将链表清空,并将指定范围内的元素复制到链表中。void assign(InputIterator first, InputIterator last);第二种形式接受一个元素数量和一个值作为参数,用于将指定数量的相同值的元素分配给链表。它会将链表清空,并将指定数量的元素赋值为指定的值。void assign(size_type count, const T& value);?示例代码?
#include <iostream>#include <vector>#include <list>int main() { std::list<int> myList; // 使用迭代器范围进行分配 std::vector<int> numbers = {1, 2, 3, 4, 5}; myList.assign(numbers.begin(), numbers.end()); std::cout << "第一次执行assign之后的list为: "; for (auto i : myList) { std::cout << i << " "; } std::cout << std::endl; // 使用元素数量和值进行分配 myList.assign(3, 100); std::cout << "第二次执行assign之后的list为: "; for (auto i : myList) { std::cout << i << " "; } std::cout << std::endl; return 0;}
?输出?
第一次执行assign之后的list为: 1 2 3 4 5
第二次执行assign之后的list为: 100 100 100
五、vector 与 list 的对比
对比 | vector | list |
底层结构 | 动态顺序表,连续空间 | 带头结点的双向循环链表 |
访问 | 支持随机访问,首地址+下标 | 不能随机访问,可通过find查找,访问随即元素时间复杂度O(N) |
插入删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率较高,缓存利用率高。可以一次将一个数据附近的空间都加载到缓存,不用频繁地从内存读取数据 | 底层节点动态开辟,容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对指针进行了封装 |
迭代器失效 | 容量相关的操作都有可能导致迭代器失效,如插入引起的扩容,删除元素等 | 插入元素不会导致迭代器失效,删除节点会导致,且只影响当前迭代器,其他迭代器不受影响 |
使用场景 | 不关心插入和删除效率,支持随机访问 | 大量插入和删除操作,不关心随机访问的场景 |
六、共勉
以下就是我对 list 容器 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 C++ list容器的模拟实现 ,请持续关注我哦!!!