目录
- [1 list 的概念](#1 list 的概念)
- [2 list 的常用接口](#2 list 的常用接口)
-
- [2.1 list 的构造函数](#2.1 list 的构造函数)
- [2.2 list 的遍历](#2.2 list 的遍历)
- [2.3 list 的空间管理](#2.3 list 的空间管理)
-
- [2.3.1 size()](#2.3.1 size())
- [2.3.2 resize()](#2.3.2 resize())
- [2.3.3 empty()](#2.3.3 empty())
- [2.4 list 的增删改查](#2.4 list 的增删改查)
-
- [2.4.1 front()](#2.4.1 front())
- [2.4.2 back()](#2.4.2 back())
- [2.4.4 push_front()](#2.4.4 push_front())
- [2.4.5 pop_front()](#2.4.5 pop_front())
- [2.4.6 push_back()](#2.4.6 push_back())
- [2.4.7 pop_back()](#2.4.7 pop_back())
- [2.4.8 insert()](#2.4.8 insert())
- [2.4.9 erase()](#2.4.9 erase())
- [2.4.10 swap()](#2.4.10 swap())
- [2.4.11 splice()](#2.4.11 splice())
- [2.4.12 remove()](#2.4.12 remove())
- [2.4.13 unique()](#2.4.13 unique())
- [2.4.14 merge()](#2.4.14 merge())
- [2.4.15 clear()](#2.4.15 clear())
- [3 list 的优缺点](#3 list 的优缺点)
- [4 与 vector 的对比](#4 与 vector 的对比)
1 list 的概念
list 是 STL 容器的一种,list 既可以用来保存内置类型数据 (char,short,int等),又可以用来保存自定义类型数据 (对象),它的底层数据结构是带头结点的双向循环链表
要想使用 list ,就需要包含头文件 list 并展开命名空间 std
cpp
#include <list>
using namespace std;
2 list 的常用接口
2.1 list 的构造函数
在构造 list 时,要遵循如下的语法:
cpp
list<类型名> 对象名;
list 的构造函数主要有以下几种:

(1)使用空间配置器进行构造,这个方式不太常用,因此在这里不做介绍
(2)使用 n 个 val 进行构造
用这种方式构造时,val 为用户给定的值,如果未给定,则它会使用缺省值(默认值),默认值为当前 list 存储的元素对应的类型的默认值,比如当前的 list 要存储 int 类型的值,那么它的默认值是 0,如果要存储 string,那么它的默认值是空字符串
cpp
list<int> list1(5, 1); //n = 5, val = 1
(3)使用其它容器的迭代器来确定区间,用区间内的元素来进行构造
用这种方式构造的时候,要保证 用于构造 list 的容器中所存储的数据的类型要和 list 中将要存储的数据的类型一致 ,也就是说,如果 list 中要存储 int 类型的数据,是不可以用 string 中存储的元素来进行构造的,因为 string 中存储的是字符
cpp
string s("hello world");
list<char> list1(s.begin(), s.end());
(4)使用一个已经存在的 list 来进行构造(拷贝构造)
用这种方式构造的时候, 要保证两个 list 存储的数据的类型是一致的
cpp
list<int> list1(5, 1);
list<int> list2(list1);
(5)构造一个空的 list
如果在定义 list 时,对象名后不加括号,就会构造出一个空的 list
cpp
list<int> v3;

2.2 list 的遍历
由于 list 的底层是 链表,所以遍历 不支持下标,只支持迭代器和范围 for:
(1)使用迭代器遍历
在使用迭代器遍历时,会使用到以下几个接口:
| 接口名称 | 作用 | 返回值类型 |
|---|---|---|
| begin() | 返回指向第一个元素的迭代器 | 普通对象返回 iterator,const 对象返回 const_iterator |
| end() | 返回指向最后一个元素的下一个位置的迭代器 | 普通对象返回 iterator,const 对象返回 const_iterator |
| rbegin() | 返回指向最后一个元素的迭代器 | 普通对象返回 reverse_iterator,const 对象返回 const_reverse_iterator |
| rend() | 返回指向第一个元素的前一个位置的迭代器 | 普通对象返回 reverse_iterator,const 对象返回 const_reverse_iterator |
list 的迭代器属于 双向迭代器,只能进行 ++,- - 操作, 支持双向遍历
正向遍历:
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5 };
list<int>::iterator it = list1.begin();
while (it != list1.end())
{
cout << *it << " ";
it++;
}
return 0;
}
反向遍历:
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5 };
list<int>::reverse_iterator rit = list1.rbegin();
while (rit != list1.rend())
{
cout << *rit << " ";
rit++;
}
return 0;
}

(2)范围 for 与 auto 遍历
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5 };
for (auto e : list1)
{
cout << e << " ";
}
return 0;
}
在 auto 后加上 & 就是 引用类型,由 auto& 修饰的变量 e 就是每一个元素的别名, 此时对 e 进行修改会影响结果,但不会影响 list 中元素的值
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5 };
for (auto& e : list1)
{
cout << e + 10 << " ";
}
return 0;
}

2.3 list 的空间管理
在对 list 进行空间管理时,经常会使用到以下几个接口:
| 接口名称 | 作用 | 返回值类型 |
|---|---|---|
| size() | 返回 list 中存储的有效元素个数 | size_t (无符号整形) |
| resize() | 增加或缩减有效元素个数 | void |
| empty() | 判断 list 是否为空 | bool |
2.3.1 size()
size 的主要作用是 返回当前 list 中有效元素的个数
cpp
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << v1.size() << endl;
return 0;
}
2.3.2 resize()

resize 的主要作用是 对有效元素的个数进行缩减或增加
它的处理分两种情况:
(1)n > 当前有效元素个数
此时会 在 list 中尾插若干个值为 val 的结点,直到有效元素个数为 n
其中 val 为用户给定的值,若没有给定则使用默认值
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.resize(10);
return 0;
}
(2)n < 当前有效元素个数
此时会将 超过 n 个有效元素的部分进行删除
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
cout << "size: " << list1.size() << endl;
list1.resize(3);
cout << "size: " << list1.size() << endl;
return 0;
}
减少元素前:
减少元素后:
2.3.3 empty()

empty 的作用是 判断 list 是否为空,是空返回 true,不是空则返回 false
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list<int> list2;
if (list1.empty())
cout << "list1 is empty" << endl;
else
cout << "list1 is not empty" << endl;
cout << endl;
if (list2.empty())
cout << "list2 is empty" << endl;
else
cout << "list2 is not empty" << endl;
return 0;
}
2.4 list 的增删改查
list 的增删改查接口主要有以下几种:
| 接口名称 | 作用 | 返回值类型 |
|---|---|---|
| front() | 返回 list 的第一个元素 | 引用 |
| back() | 返回 list 的最后一个元素 | 引用 |
| push_front() | 在 list 中头插一个元素 | void |
| pop_front() | 在 list 中进行头删 | void |
| push_back() | 在 list 中尾插一个元素 | void |
| pop_back() | 在 list 中进行尾删 | void |
| insert() | 在 list 中插入元素 | Iterator/void |
| erase() | 在 list 中删除元素 | Iterator |
| swap() | 交换 list 中的所有元素 | void |
| splice() | 将一个 list 中的元素转移到另外一个中 | void |
| remove() | 清除 list 中符合要求的元素 | void |
| unique() | 清除 list 中重复的元素 | void |
| merge() | 合并两个有序的 list | void |
| clear() | 清空 list | void |
2.4.1 front()

front 的作用是 访问 list 的第一个元素,返回该元素的引用 ,对于 const 对象,返回的就是 const 引用,就意味着不能更改该对象的值
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
cout << list1.front() << endl;
return 0;
}

2.4.2 back()

back 的作用是 访问 list 的最后一个元素,返回的也是该元素的引用, 对于 const 对象,返回的就是 const 引用,就意味着不能更改该对象的值
cpp
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << "v1 back:" << v1.back() << endl;
return 0;
}
2.4.4 push_front()
push_front 的作用是 在 list 中进行头插并更改 size()
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.push_front(8);
return 0;
}
2.4.5 pop_front()
pop_front() 的作用是 在 list 中进行头删并更新 size()
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.pop_front();
return 0;
}
2.4.6 push_back()
push_back 的作用是 在 list 中尾插一个元素 val 并更新 size()
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.push_back(7);
return 0;
}

2.4.7 pop_back()

pop_back 的作用是 删除 list 中的最后一个元素并更新 size()
cpp
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.pop_back();
return 0;
}
2.4.8 insert()

insert 的作用是 在 list 中指定一个位置进行插入,插入的方式有三种:
(1)在指定位置插入一个值
此时在插入完成后,会返回 指向新元素的迭代器
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.insert(list1.begin(), 10);
return 0;
}
(2)在指定的位置插入 n 个同样的值
在插入完成后,不进行任何返回
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.insert(list1.begin(), 5, 7);
return 0
}
(3)在指定位置插入一个区间内的值
这里的区间范围需要给出 起始迭代器 和 终止迭代器 来确定,区间是左闭右开的,插入完成后,不会返回任何值
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list<int> list2 = { 7,8,9 };
list1.insert(list1.begin(), list2.begin(), list2.end());//[list2.begin(), list2.end())
return 0;
}
2.4.9 erase()

erase 主要是 用来删除 list 中指定位置上的元素并更新size(),删除时,有两种方式:
(1)删除指定位置上的一个值
成功删除后,函数会 返回被删除元素的下一个位置的迭代器
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.erase(list1.begin());
return 0;
}
(2)删除指定区间上的所有值
这里的区间范围需要给出 起始迭代器 和 终止迭代器 来确定,区间是左闭右开 的,删除完成后,会返回最后一个被删除元素的下一个位置的迭代器
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list1.erase(list1.begin(), list1.end());
return 0;
}
需要注意的是,因为 list 的底层是双向循环带头结点的链表,删除元素就意味着释放元素所在的结点,如果删除后仍然使用被删除元素的迭代器,就会访问到已经被释放的空间,这是十分危险的行为,这也是一种迭代器失效
2.4.10 swap()
swap 的作用主要是 交换两个 list 中的值
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list<int> list2 = { 7,8,9,10,11,12 };
list1.swap(list2);
return 0;
}
2.4.11 splice()

splice 主要是 用于将一个 list 中的元素转移至另外一个 list 的指定位置上
转换的方式有三种:
(1)将整个 list 进行转换
在这个转换的过程中,被用于转换的 list 会被清空
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list<int> list2 = { 7,8,9,10,11,12 };
list1.splice(list1.begin(), list2);
return 0;
}
(2)指定 list 中的一个元素进行转换,该元素由迭代器 i 指向
在这个转换的过程中,被用于转换的元素会在原来的 list 中被删除
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list<int> list2 = { 7,8,9,10,11,12 };
list1.splice(list1.begin(), list2);
return 0;
}
(3)指定 list 中一个区间内的值进行转换
区间需要由两个迭代器给出,分别表示 起始 和 末尾 ,区间是左闭右开的
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list<int> list2 = { 7,8,9,10,11,12 };
list1.splice(list1.begin(), list2, list2.begin(), list2.end());
return 0;
}
2.4.12 remove()
remove 的作用是 移除 list 中所有值等于 val 的结点
cpp
int main()
{
list<int> list1 = { 1,2,2,4,2,6 };
list1.remove(2);
return 0;
}
2.4.13 unique()

unique 的作用是 对 list 进行去重,将相等的多余的值进行移除 ,前提是去重的 list 需要有序
cpp
int main()
{
list<int> list1 = { 1,2,2,2,2,6 };
list1.unique();
return 0;
}
如果去重的 list 无序,那么就不能完全去重
cpp
int main()
{
list<int> list1 = { 1,2,2,4,2,6 };
list1.unique();
return 0;
}
2.4.14 merge()

merge 的作用是将一个 list 合并到另外一个 list 中
合并时会将元素按序摆放,并且合并后,被合并的 list 会被清空
合并有两种方式:
(1)直接进行合并
直接进行合并时,默认是按照升序进行合并,此时要求两个 list 都为升序
cpp
int main()
{
list<int> list1 = { 1,2,3,4,5,6 };
list<int> list2 = { 2,3,4,5,6 };
list1.merge(list2);
return 0;
}

(2)合并时,按照给定的比较方式进行合并
比较方式为 x > y ,那么就是 降序合并 ,要求两个 list 都为降序
比较方式为 x < y ,那么就是 升序合并 ,要求两个 list 都为升序
cpp
bool comparison(int x, int y)
{
return x > y;
}
int main()
{
list<int> list1 = { 6,5,4,3,2,1 };
list<int> list2 = { 6,5,4,3,2 };
list1.merge(list2, comparison);
return 0;
}
2.4.15 clear()
clear 的作用是 清空 list 中的元素,将 size() 置为 0
这个操作相当于释放了 list 中的结点,只留下了虚拟头结点
cpp
int main()
{
list<int> list1 = { 6,5,4,3,2,1 };
list1.clear();
return 0;
}
3 list 的优缺点
由于 list 的底层数据结构是带头结点的双向循环链表,所以它的优缺点就是该结构的优缺点:
优点:
- 插入删除十分方便,时间复杂度低
- 需要插入新的值时,直接创建新结点再链接即可,不需要进行扩容
缺点:
- 不能随机访问,要查找值时,需要遍历一整个结构,效率低
- 结点分散,容易造成内存碎片,空间利用率低
4 与 vector 的对比
| 对比 | vector | list |
|---|---|---|
| 底层数据结构 | 动态顺序表 | 带头结点的双向循环链表 |
| 访问效率 | 支持使用下标随机访问,效率高 | 不支持使用下标随机访问,效率低 |
| 插入删除效率 | 尾插时可能原空间已满,需要开辟新空间,效率低,在头部和中间插入时,都需要将后方元素向后移动,时间复杂度是 O(N),尾删时,效率较高,不需要移动元素,在其他地方删除时,要将后方元素前移,时间复杂度是O(N) | 插入删除只需要改动结点的指针即可,效率高,并且不需要进行扩容,每次插入只需要开一个结点的空间即可 |
| 空间利用率 | 空间连续,不会产生内存碎片,空间利用率高 | 由于内部是一个个不连续的结点,所以容易产生内存碎片,空间利用率低 |
| 迭代器 | 随机迭代器,支持进行 ++, - -,+,- 操作 | 双向迭代器,只支持 ++, - - 操作 |
| 迭代器失效时机 | 插入删除均会失效 | 只有删除时会失效 |
| 使用场景 | 插入删除频率低,访问频率高时使用 | 插入删除频率高时使用 |






























