【C++】list 容器最全详解(什么是list? list容器的常用接口有那些?)
目录
一、前言
二、 什么是 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 name;
这里的typename可以是任何基本类型,例如int、double、char、结构体等,也可以是STL标准容器,例如string、set、queue、vector等。
注意:使用前必须加上头文件:#include
代码展示:#include #include using namespace std; int main() { int a[3]; // 正常定义的-----静态数组 list str_a; // list 定义的 int类型的链表 char b[3]; list 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 lt1; //构造int类型的空容器
方式二: 构造一个含有n个val的某类型容器:
list lt2(10, 2); //构造含有10个2的int类型容器
方式三: 拷贝构造某类型容器的复制品:
list lt3(lt2); //拷贝构造int类型的lt2容器的复制品
方式四: 使用迭代器拷贝构造某一段内容:
string s("hello world"); list lt4(s.begin(),s.end()); //构造string对象某段迭代器区间的内容
方式五: 构造数组某段区间的内容:
int arr[] = { 1, 2, 3, 4, 5 }; int sz = sizeof(arr) / sizeof(int); list lt5(arr, arr + sz); //构造数组某段区间的复制品 ->本质是调用迭代器区间的构造函数
方式五: 使用花括号构造内容(C++11):
list lt{ 1,2,3,4,5 }; // 直接使用花括号进行构造---C++11允许
代码展示1(实用):
#include #include using namespace std; int main() { list frist; // 构造一个没有元素的空容器 list second(2, 10); // 2个值为10的整数 list third(second.begin(), second.end()); // 迭代器构造 list fourth(third); // 拷贝构造 // 迭代器构造函数也可以使用数组来进行构造,传的区间是左闭右开 // 因为指向数组空间的指针是天然的迭代器 int arr[] = { 16,2,77,29 }; list fifth(arr, arr + 4); // std::list 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 #include using namespace std; int main() { // 用其它容器的迭代器初始化,只要数据d类型可以匹配上 string s("hello"); list v(s.begin(), s.end()); for (auto& e : v) { cout