std::list
是 C++ 标准库中的双向链表容器,提供了快速的插入和删除操作。与 vector
不同,list
是链式存储结构,因此它不支持随机访问。
1. 概述
std::list
是一个双向链表容器,位于 <list>
头文件中。链表是一种线性表数据结构,其中每个节点都包含一个数据元素和指向前后节点的指针。它适合在任意位置进行频繁插入和删除操作,但不适合随机访问。list
提供了指针稳定性,即插入或删除元素不会影响其他元素的地址,因此适合需要频繁插入和删除操作的场景。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {1, 2, 3, 4, 5};
for (int val : myList) {
std::cout << val << " "; // 输出 1 2 3 4 5
}
return 0;
}
2. 节点
在 list
中,每个元素都被存储在一个节点中,节点通常包含以下部分:
- 数据域:存储实际的数据值。
- 前向指针(prev):指向前一个节点。
- 后向指针(next):指向后一个节点。
这种链式结构允许 list
快速插入和删除元素(时间复杂度为 ),因为只需要调整相邻节点的指针即可,不需要像 vector
那样进行数据的移动。
3. 迭代器
list
支持双向迭代器,可以通过 begin()
和 end()
方法获取头部和尾部迭代器。此外,list
还支持 rbegin()
和 rend()
迭代器进行反向遍历。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {1, 2, 3, 4, 5};
// 正向迭代
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 反向迭代
for (auto it = myList.rbegin(); it != myList.rend(); ++it) {
std::cout << *it << " ";
}
return 0;
}
运行结果
XML
1 2 3 4 5
5 4 3 2 1
与 vector
不同,list
不支持随机访问,list
只支持双向迭代器 ,而不支持随机访问迭代器 。每次访问某个特定位置的元素都需要一个完整的遍历过程,因此不能使用 []
运算符或 at()
访问指定位置的元素,必须通过迭代器进行遍历。
示例对比
以下示例展示了 vector
支持下标操作,而 list
不支持:
cpp
#include <iostream>
#include <vector>
#include <list>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> lst = {1, 2, 3, 4, 5};
// 直接使用下标访问 vector 中的元素
std::cout << "Element at index 2 in vector: " << vec[2] << std::endl;
// 使用迭代器访问 list 中的第三个元素
auto it = lst.begin();
std::advance(it, 2); // 移动迭代器到第三个位置
std::cout << "Element at index 2 in list: " << *it << std::endl;
return 0;
}
运行输出:
XML
Element at index 2 in vector: 3
Element at index 2 in list: 3
因为 list
是链表结构而不是连续内存,因此它无法提供高效的随机访问;只能通过迭代器进行线性遍历,时间复杂度为 。
需要注意的是,list有一个重要性质 :插入操作(insert)和接合操作(splice)都不会造成原有的list迭代器失效。这在vector中是不成立的,因为vector的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效。甚至list的元素删除操作(erase),也只有"指向被删除元素"的那个迭代器失效,其他迭代器不受任何影响。
下面是一个示例代码,通过删除 std::list
中的某个元素,展示删除操作只使指向被删除元素的迭代器失效,而其他迭代器保持有效:
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {1, 2, 3, 4, 5};
// 获取指向第三个元素的迭代器
auto it1 = myList.begin();
std::advance(it1, 2); // 指向元素3
// 获取指向第四个元素的迭代器
auto it2 = it1;
++it2; // 指向元素4
// 删除第三个元素(值为3)
myList.erase(it1);
// 尝试访问其他迭代器(it2)
std::cout << "Element after deletion (it2): " << *it2 << std::endl;
// 遍历并输出剩余元素,验证列表完整性
std::cout << "Remaining elements: ";
for (const auto& elem : myList) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
解释
- 创建迭代器 :
it1
指向元素3
,it2
指向元素4
。 - 删除操作 :调用
erase(it1)
删除元素3
,使it1
失效。 - 验证未失效迭代器 :
it2
仍然指向元素4
,未受到影响。 - 遍历并输出结果:剩余的元素顺序和内容保持正确,验证了删除操作只影响指向被删元素的迭代器,不会影响其他迭代器的有效性。
运行结果
XML
Element after deletion (it2): 4
Remaining elements: 1 2 4 5
4. 数据结构
std::list
的底层数据结构是一个(环状)双向链表。链表的特点是每个节点存储一个数据值和两个指针(prev
和 next
),分别指向前一个和后一个节点。双向链表适合频繁的插入和删除操作,尤其是在中间位置插入或删除元素时,可以避免大量数据移动。
然而,链表占用的内存比数组多,因为每个节点都要额外存储两个指针。同时,由于链表分散存储,不具备局部性,不适合频繁的随机访问。
5. 构造与内存管理
list
提供了多种构造方法和内存管理操作:
- 默认构造 :
std::list<int> myList;
- 指定大小构造 :
std::list<int> myList(10);
// 创建10个元素,初始值为0 - 指定大小和初始值 :
std::list<int> myList(10, 5);
// 创建10个元素,初始值为5 - 拷贝构造 :
std::list<int> myList2(myList);
- 移动构造:高效转移资源,避免拷贝
在内存管理方面,list
没有 vector
的 capacity
和 reserve
方法,因为链表的存储不需要预留空间。但 list
提供了 clear()
方法用于清空所有元素,size()
方法获取元素数量。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList(5, 2); // 大小为5,初始值为2
std::cout << "Size: " << myList.size() << std::endl;
myList.clear();
std::cout << "Size after clear: " << myList.size() << std::endl;
return 0;
}
运行结果
XML
Size: 5
Size after clear: 0
6. 元素操作方法
list
提供了一些操作方法,允许在列表任意位置插入和删除元素。
-
添加:
push_front(val)
:在头部插入元素push_back(val)
:在尾部插入元素emplace_front(args...)
和emplace_back(args...)
:原地构造新元素,避免拷贝insert(pos, val)
:在指定迭代器位置插入元素emplace(pos, args...)
:在指定位置原地构造元素splice
:将一个list
的元素移动到另一个list
splice
用于将一个 list
的一部分或全部转移到另一个 list
中。该操作不会产生拷贝,只是修改指针,因此效率非常高。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6};
// 将 list2 的所有元素移动到 list1 的末尾
list1.splice(list1.end(), list2);
std::cout << "After splice, list1: ";
for (const auto& elem : list1) {
std::cout << elem << " ";
}
std::cout << std::endl;
std::cout << "After splice, list2 (should be empty): ";
for (const auto& elem : list2) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
运行结果
XML
After splice, list1: 1 2 3 4 5 6
After splice, list2 (should be empty):
注意: splice
之后,list2
中的元素被转移到 list1
,list2
变为空。
merge
:合并两个排序的list
merge
用于将两个已经排序的 list
合并成一个排序的 list
。它要求两个列表都已经排好序。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1 = {1, 3, 5};
std::list<int> list2 = {2, 4, 6};
// 合并 list2 到 list1,并保持排序
list1.merge(list2);
std::cout << "After merge, list1: ";
for (const auto& elem : list1) {
std::cout << elem << " ";
}
std::cout << std::endl;
std::cout << "After merge, list2 (should be empty): ";
for (const auto& elem : list2) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
运行结果
XML
After merge, list1: 1 2 3 4 5 6
After merge, list2 (should be empty):
注意:merge
会将 list2
合并到 list1
,并保持排序。合并后 list2
变为空。
-
删除:
pop_front()
和pop_back()
:删除头部或尾部元素erase(pos)
:删除指定位置的元素clear()
:清空所有元素remove(val)
:删除所有等于val
的元素unique
:移除连续重复的元素。
unique
用于删除 list
中连续的重复元素。如果列表中的元素是 [1, 2, 2, 3, 3, 3, 4]
,调用 unique
后会变成 [1, 2, 3, 4]
。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {1, 2, 2, 3, 3, 3, 4};
// 移除连续重复的元素
myList.unique();
std::cout << "After unique: ";
for (const auto& elem : myList) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
# 输出:After unique: 1 2 3 4
-
访问:
front()
和back()
:访问首尾元素
-
顺序操作:
reverse()
:反转list
中的元素顺序sort()
:对list
进行排序
sort
用于对 list
中的元素进行排序。list
不支持随机访问,因此不能使用 std::sort
,必须使用 list
自带的 sort
方法。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {4, 2, 5, 1, 3};
// 排序元素
myList.sort();
std::cout << "After sort: ";
for (const auto& elem : myList) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
运行结果:
XML
After sort: 1 2 3 4 5
可以使用自定义比较函数对 list
进行降序排序或其他排序方式。
示例:使用 Lambda 表达式进行降序排序
C++11 引入的 Lambda 表达式可以让我们更简洁地定义比较函数,适合进行一次性排序操作。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {4, 2, 5, 1, 3};
// 使用 Lambda 表达式进行降序排序
myList.sort([](int a, int b) { return a > b; });
std::cout << "List after sorting in descending order using Lambda: ";
for (const auto& elem : myList) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
运行结果
XML
List after sorting in descending order using Lambda: 5 4 3 2 1
总结
std::list
是一种双向链表容器,适合频繁插入和删除操作。但list
是链式存储结构,因此它不支持随机访问