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

(1)使用空间配置器进行构造,这个方式不太常用,因此在这里不做介绍
(2)使用 n 个 val 进行构造
用这种方式构造时,val 为用户给定的值,如果未给定,则它会使用缺省值(默认值),默认值为当前 list 存储的元素对应的类型的默认值,比如当前的 list 要存储 int 类型的值,那么它的默认值是 0,如果要存储 string,那么它的默认值是空字符串
list<int> list1(5, 1); //n = 5, val = 1
(3)使用其它容器的迭代器来确定区间,用区间内的元素来进行构造
用这种方式构造的时候,要保证 用于构造 list 的容器中所存储的数据的类型要和 list 中将要存储的数据的类型一致 ,也就是说,如果 list 中要存储 int 类型的数据,是不可以用 string 中存储的元素来进行构造的,因为 string 中存储的是字符
string s("hello world");
list<char> list1(s.begin(), s.end());
(4)使用一个已经存在的 list 来进行构造(拷贝构造)
用这种方式构造的时候,要保证两个 list 存储的数据的类型是一致的
list<int> list1(5, 1);
list<int> list2(list1);
(5)构造一个空的 list
如果在定义 list 时,对象名后不加括号,就会构造出一个空的 list
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 的迭代器属于双向迭代器,只能进行 ++,- - 操作, 支持双向遍历
正向遍历:
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;
}
反向遍历:
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 遍历
int main()
{list<int> list1 = { 1,2,3,4,5 };for (auto e : list1){cout << e << " ";}return 0;
}
在 auto 后加上 & 就是 引用类型,由 auto& 修饰的变量 e 就是每一个元素的别名, 此时对 e 进行修改会影响结果,但不会影响 list 中元素的值
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 中有效元素的个数
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 为用户给定的值,若没有给定则使用默认值
int main()
{list<int> list1 = { 1,2,3,4,5,6 };list1.resize(10);return 0;
}
(2)n < 当前有效元素个数
此时会将超过 n 个有效元素的部分进行删除
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
int main()
{list<int> list1 = { 1,2,3,4,5,6 };list<int> list2;if (list1.empty())cout << "list1 is empty" << endl;elsecout << "list1 is not empty" << endl;cout << endl;if (list2.empty())cout << "list2 is empty" << endl;elsecout << "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 引用,就意味着不能更改该对象的值
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 引用,就意味着不能更改该对象的值
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()
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()
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()
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()
int main()
{vector<int> v1 = { 1,2,3,4,5,6 };v1.pop_back();return 0;
}
2.4.8 insert()

insert 的作用是 在 list 中指定一个位置进行插入,插入的方式有三种:
(1)在指定位置插入一个值
此时在插入完成后,会返回 指向新元素的迭代器
int main()
{list<int> list1 = { 1,2,3,4,5,6 };list1.insert(list1.begin(), 10);return 0;
}
(2)在指定的位置插入 n 个同样的值
在插入完成后,不进行任何返回
int main()
{list<int> list1 = { 1,2,3,4,5,6 };list1.insert(list1.begin(), 5, 7); return 0
}
(3)在指定位置插入一个区间内的值
这里的区间范围需要给出 起始迭代器 和 终止迭代器 来确定,区间是左闭右开的,插入完成后,不会返回任何值
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)删除指定位置上的一个值
成功删除后,函数会 返回被删除元素的下一个位置的迭代器
int main()
{list<int> list1 = { 1,2,3,4,5,6 };list1.erase(list1.begin());return 0;
}
(2)删除指定区间上的所有值
这里的区间范围需要给出 起始迭代器 和 终止迭代器 来确定,区间是左闭右开的,删除完成后,会返回最后一个被删除元素的下一个位置的迭代器
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 中的值
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 会被清空
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 中被删除
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 中一个区间内的值进行转换
区间需要由两个迭代器给出,分别表示 起始 和 末尾,区间是左闭右开的
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 的结点
int main()
{list<int> list1 = { 1,2,2,4,2,6 };list1.remove(2);return 0;
}
2.4.13 unique()

unique 的作用是 对 list 进行去重,将相等的多余的值进行移除,前提是去重的 list 需要有序
int main()
{list<int> list1 = { 1,2,2,2,2,6 };list1.unique();return 0;
}
如果去重的 list 无序,那么就不能完全去重
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 都为升序
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 都为升序
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 中的结点,只留下了虚拟头结点
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) | 插入删除只需要改动结点的指针即可,效率高,并且不需要进行扩容,每次插入只需要开一个结点的空间即可 |
| 空间利用率 | 空间连续,不会产生内存碎片,空间利用率高 | 由于内部是一个个不连续的结点,所以容易产生内存碎片,空间利用率低 |
| 迭代器 | 随机迭代器,支持进行 ++, - -,+,- 操作 | 双向迭代器,只支持 ++, - - 操作 |
| 迭代器失效时机 | 插入删除均会失效 | 只有删除时会失效 |
| 使用场景 | 插入删除频率低,访问频率高时使用 | 插入删除频率高时使用 |































