list
- list
-
- [一、`std::list` 的构造方法](#一、
std::list
的构造方法) -
- [1. 默认构造](#1. 默认构造)
- [2. 指定大小构造](#2. 指定大小构造)
- [3. 从其他容器构造](#3. 从其他容器构造)
- [4. 使用初始化列表构造](#4. 使用初始化列表构造)
- [二、`std::list` 的迭代器](#二、
std::list
的迭代器) -
- [1. 迭代器的获取](#1. 迭代器的获取)
- [2. 迭代器的使用](#2. 迭代器的使用)
- [3. 常量迭代器](#3. 常量迭代器)
- [三、`std::list` 的容量](#三、
std::list
的容量) -
- [1. 检查是否为空](#1. 检查是否为空)
- [2. 获取元素数量](#2. 获取元素数量)
- [3. 清空列表](#3. 清空列表)
- [四、`std::list` 的元素访问](#四、
std::list
的元素访问) - [五、`std::list` 的元素修改](#五、
std::list
的元素修改) -
- [1. 添加元素](#1. 添加元素)
- [2. 删除元素](#2. 删除元素)
- [3. 修改元素](#3. 修改元素)
- [六、`std::list` 的迭代器失效问题](#六、
std::list
的迭代器失效问题) -
- [1. 何时迭代器会失效](#1. 何时迭代器会失效)
- [2. 如何避免迭代器失效](#2. 如何避免迭代器失效)
- [一、`std::list` 的构造方法](#一、
- [`std::list` 与 `std::vector` 的对比](#
std::list
与std::vector
的对比) -
- 一、数据结构
-
- [1. `std::list`:双向链表](#1.
std::list
:双向链表) - [2\. `std::vector`:动态数组](#2.
std::vector
:动态数组)
- [1. `std::list`:双向链表](#1.
- 二、内存分配
-
- [1. `std::list`](#1.
std::list
) - [2. `std::vector`](#2.
std::vector
)
- [1. `std::list`](#1.
- 三、插入和删除操作
-
- [1. `std::list`](#1.
std::list
) - [2. `std::vector`](#2.
std::vector
)
- [1. `std::list`](#1.
- 四、随机访问
-
- [1. `std::list`](#1.
std::list
) - [2. `std::vector`](#2.
std::vector
)
- [1. `std::list`](#1.
- 五、迭代器
-
- [1. `std::list`](#1.
std::list
) - [2. `std::vector`](#2.
std::vector
)
- [1. `std::list`](#1.
- 六、性能对比
-
- [1. 时间复杂度](#1. 时间复杂度)
- [2. 内存使用](#2. 内存使用)
- 七、适用场景
-
- [1. `std::list` 适用场景](#1.
std::list
适用场景) - [2. `std::vector` 适用场景](#2.
std::vector
适用场景)
- [1. `std::list` 适用场景](#1.

list
在 C++ 标准模板库(STL)中,std::list
是一个非常灵活且强大的双向链表容器。它提供了高效的插入和删除操作,非常适合需要频繁动态调整元素的场景。
一、std::list
的构造方法
std::list
提供了多种构造方法,以满足不同的初始化需求:
1. 默认构造
cpp
std::list<int> myList;
这会创建一个空的 std::list
,不包含任何元素。
2. 指定大小构造
cpp
std::list<int> myList(5); // 创建一个包含 5 个默认初始化为 0 的 int 元素的 list
你可以指定初始大小,并且可以为元素提供默认值:
cpp
std::list<int> myList(5, 10); // 创建一个包含 5 个值为 10 的 int 元素的 list
3. 从其他容器构造
你可以使用另一个容器的内容来初始化 std::list
:
cpp
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> myList(vec.begin(), vec.end()); // 从 vector 的内容构造 list
4. 使用初始化列表构造
cpp
std::list<int> myList = {1, 2, 3, 4, 5};
这种方式简洁明了,非常适合快速初始化。
二、std::list
的迭代器
std::list
是一个双向链表,因此它提供的是双向迭代器。这意味着你可以通过迭代器向前或向后遍历列表。
1. 迭代器的获取
cpp
std::list<int>::iterator it = myList.begin(); // 获取指向第一个元素的迭代器
std::list<int>::iterator end = myList.end(); // 获取指向最后一个元素之后的迭代器
begin()
和 end()
分别返回指向第一个元素和最后一个元素之后的迭代器。
2. 迭代器的使用
你可以通过迭代器访问和修改元素:
cpp
for (std::list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " "; // 访问元素
*it = *it * 2; // 修改元素
}
3. 常量迭代器
如果你只需要读取元素,而不需要修改它们,可以使用常量迭代器:
cpp
for (std::list<int>::const_iterator cit = myList.cbegin(); cit != myList.cend(); ++cit) {
std::cout << *cit << " ";
}
三、std::list
的容量
std::list
是一个动态容器,其容量会根据元素的添加和删除自动调整。不过,它也提供了一些方法来管理容量:
1. 检查是否为空
cpp
if (myList.empty()) {
std::cout << "List is empty." << std::endl;
}
empty()
方法返回一个布尔值,表示列表是否为空。
2. 获取元素数量
cpp
std::cout << "Number of elements: " << myList.size() << std::endl;
size()
方法返回列表中元素的数量。
3. 清空列表
cpp
myList.clear();
clear()
方法会移除列表中的所有元素,但不会释放内存。
四、std::list
的元素访问
由于 std::list
是一个链表结构,它不支持随机访问,因此没有像数组或 std::vector
那样的下标操作符 []
。不过,你可以通过迭代器来访问元素:
cpp
std::list<int>::iterator it = myList.begin();
std::cout << *it << std::endl; // 访问第一个元素
如果你需要频繁访问特定位置的元素,建议使用其他容器(如 std::vector
)。
五、std::list
的元素修改
std::list
提供了多种方法来添加、删除和修改元素:
1. 添加元素
-
在头部添加元素
cppmyList.push_front(10);
-
在尾部添加元素
cppmyList.push_back(20);
-
在指定位置插入元素
cppstd::list<int>::iterator it = myList.begin(); myList.insert(it, 15); // 在第一个元素之前插入 15
2. 删除元素
-
删除头部元素
cppmyList.pop_front();
-
删除尾部元素
cppmyList.pop_back();
-
删除指定位置的元素
cppstd::list<int>::iterator it = myList.begin(); myList.erase(it); // 删除第一个元素
3. 修改元素
你可以通过迭代器直接修改元素:
cpp
std::list<int>::iterator it = myList.begin();
*it = 100; // 修改第一个元素的值
六、std::list
的迭代器失效问题
在使用 std::list
时,迭代器失效是一个需要注意的问题。迭代器失效是指迭代器不再指向有效的元素,这通常发生在对容器进行修改操作时。
1. 何时迭代器会失效
-
插入操作 :在
std::list
中插入元素不会使任何迭代器失效。这是因为std::list
是一个双向链表,插入操作不会影响已有的元素位置。 -
删除操作:当你删除一个元素时,指向该元素的迭代器会失效。例如:
cppstd::list<int>::iterator it = myList.begin(); myList.erase(it); // it 现在失效
如果你需要继续使用迭代器,应该在删除操作后获取新的迭代器:
cppstd::list<int>::iterator it = myList.begin(); it = myList.erase(it); // it 现在指向下一个有效元素
2. 如何避免迭代器失效
- 在删除元素时,及时更新迭代器 :如上例所示,
erase()
方法会返回指向下一个有效元素的迭代器。 - 避免在循环中直接删除元素 :如果你需要在循环中删除多个元素,建议使用
remove_if()
或remove()
方法,或者使用一个临时迭代器来管理删除操作。
std::list
与 std::vector
的对比
一、数据结构
1. std::list
:双向链表
std::list
是一个双向链表结构。每个元素通过指针(或引用)与前一个元素和后一个元素相连。链表结构使得 std::list
在插入和删除操作上非常高效,但不支持随机访问。
2. std::vector
:动态数组
std::vector
是一个动态数组结构。它在内存中以连续的方式存储元素,支持快速的随机访问,但在插入和删除操作(尤其是非尾部操作)上可能效率较低。
二、内存分配
1. std::list
- 内存分配 :
std::list
的每个元素单独分配内存,因此内存分配较为分散。 - 内存开销 :每个元素除了存储数据外,还需要额外的空间来存储指针(或引用),用于连接前后元素。因此,
std::list
的内存开销相对较大。 - CPU高速缓存命中率低
2. std::vector
- 内存分配 :
std::vector
的内存是连续分配的。它会根据需要动态调整内存大小,但每次调整可能会涉及内存复制。 - 内存开销 :
std::vector
的内存开销相对较小,因为它只需要存储元素本身,而不需要额外的指针。 - CPU高速缓存命中率高
三、插入和删除操作
1. std::list
- 插入操作 :
- 时间复杂度:O(1),插入操作只需要更新前后元素的指针。
- 适用场景:适合在任意位置频繁插入元素。
- 删除操作 :
- 时间复杂度:O(1),删除操作只需要更新前后元素的指针。
- 适用场景:适合在任意位置频繁删除元素。
2. std::vector
- 插入操作 :
- 时间复杂度:O(n),插入操作可能需要移动插入点之后的所有元素。
- 适用场景:适合在尾部插入元素,因为尾部插入的时间复杂度为 O(1)。
- 删除操作 :
- 时间复杂度:O(n),删除操作可能需要移动删除点之后的所有元素。
- 适用场景:适合在尾部删除元素,因为尾部删除的时间复杂度为 O(1)。
四、随机访问
1. std::list
- 随机访问:不支持随机访问,只能通过迭代器逐个遍历元素。
- 适用场景:适合不需要随机访问的场景。
2. std::vector
- 随机访问:支持快速随机访问,通过下标可以直接访问任意位置的元素。
- 适用场景:适合需要频繁随机访问元素的场景。
五、迭代器
1. std::list
- 迭代器类型:双向迭代器,支持向前和向后遍历。
- 迭代器失效:插入操作不会使迭代器失效,但删除操作会使指向被删除元素的迭代器失效。
2. std::vector
- 迭代器类型:随机访问迭代器,支持随机访问。
- 迭代器失效:插入或删除操作会使所有指向被修改区域的迭代器失效。
六、性能对比
1. 时间复杂度
操作类型 | std::list |
std::vector |
---|---|---|
插入(任意位置) | O(1) | O(n) |
删除(任意位置) | O(1) | O(n) |
随机访问 | O(n) | O(1) |
遍历 | O(n) | O(n) |
2. 内存使用
std::list
:内存使用较为分散,每个元素需要额外的指针空间。std::vector
:内存使用较为紧凑,但可能需要预留额外空间以支持动态扩展。
七、适用场景
1. std::list
适用场景
- 需要在任意位置频繁插入和删除元素。
- 不需要随机访问元素。
- 对内存使用分散性不敏感。
2. std::vector
适用场景
- 需要频繁随机访问元素。
- 主要在尾部插入和删除元素。
- 对内存使用紧凑性有要求。