1 std::set 概述
std::set 是 C++ 标准库中的一个容器,它存储唯一元素的有序集合。std::set 实现了关联容器,其中的元素在插入时自动按键(通常是元素的值)排序。每个元素在 std::set 中只出现一次,即 std::set 中的元素是唯一的。
std::set 的主要特点包括:
(1)唯一性: std::set 中的每个元素都是唯一的。如果尝试插入一个已存在的元素,该操作将被忽略。
(1)排序: std::set 中的元素按键(通常是元素的值)进行排序。默认情况下,元素按升序排列。
(1)无重复: 由于 std::set 中的元素是唯一的,因此不可能有两个相同的元素。
(1)关联容器: std::set 是一个关联容器,它允许通过键值快速访问元素。
(1)红黑树实现: 在大多数实现中,std::set 是通过红黑树数据结构实现的,这提供了高效的查找、插入和删除操作。
1.1 std::set 的内部实现
std::set 在 C++ 标准库中的内部实现通常基于一种自平衡的二叉搜索树,最常见的是红黑树(Red-Black Tree)。红黑树是一种特殊的二叉搜索树,它在保持二叉搜索树的特性的同时,通过强制实施额外的约束来维护树的平衡,从而保证在树中的搜索、插入和删除操作的时间复杂度都是 O(log n)。
红黑树作为 std::set 核心数据结构的关键特性如下:
节点结构:
- 每个节点通常包含键值(key)和可能的附加信息(如颜色标记,用于红黑树的平衡)。
- 每个节点还有指向其左子节点和右子节点的指针。
- 叶子节点(通常是空节点或哨兵节点)通常用于简化边界条件的处理。
颜色标记:
- 在红黑树中,每个节点都有一个颜色属性,通常是红色或黑色。
- 这些颜色用于维持树的平衡和满足红黑树的约束条件。
二叉搜索树特性:
- 左子节点的键值小于其父节点的键值。
- 右子节点的键值大于其父节点的键值。
红黑树的约束:
- 每个节点或者是红色,或者是黑色。
- 根节点是黑色。
- 每个叶子节点(NIL节点)是黑色。
- 如果一个节点是红色的,那么它的两个子节点都是黑色的(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
- 从任一节点到其每个叶子的所有简单路径都包含相同数量的黑色节点。
平衡调整:
- 当插入或删除节点时,红黑树可能需要重新调整以维持其平衡和满足约束条件。
- 这可能涉及到节点颜色的改变、节点的旋转(左旋或右旋)以及树结构的调整。
在 std::set 中,元素以键值(通常是元素的值)作为排序依据,每个元素都被视为树中的一个节点。通过保持树的平衡,std::set 能够保证对数时间复杂度的查找、插入和删除操作,即使在最坏的情况下也是如此。
除了红黑树之外,某些实现可能会选择其他自平衡的二叉搜索树,如 AVL 树,但红黑树由于其实现简单和相对较好的性能,在 C++ 标准库中更为常见。
需要注意的是,虽然 std::set 的内部实现通常基于红黑树,但这并不是强制规定的。标准库的实现可以自由选择任何满足 std::set 所需功能的数据结构。然而,在实际应用中,大多数实现都选择红黑树或其变种作为 std::set 的内部实现。
1.2 std::set 的性能特点
以下是关于 std::set 性能的一些关键点:
插入性能:
由于 std::set 通常基于红黑树实现,插入操作的平均时间复杂度为 O(log n),其中 n 是集合中元素的数量。这意味着在大多数情况下,插入操作是相对高效的。
删除性能:
与插入操作类似,删除操作的平均时间复杂度也是 O(log n)。std::set 提供了高效的删除操作,因为它能够快速定位到要删除的元素并相应地调整树的结构。
查找性能:
由于 std::set 本质上是一个排序的二叉搜索树,查找操作的平均时间复杂度同样是 O(log n)。这使得在集合中快速检索元素成为可能。
内存使用:
std::set 使用动态内存分配来存储元素,这意味着它可以根据需要自动调整大小。然而,由于需要维护树的结构和平衡,内部开销可能比使用简单数组或向量要高一些。
元素顺序:
std::set 中的元素始终按照排序顺序存储,这允许用户以 O(log n) 的时间复杂度访问有序序列中的任意元素。
唯一性保证:
std::set 保证集合中元素的唯一性,这意味着不会有重复的元素存在。这有助于减少存储空间的浪费,并简化了某些算法的实现。
需要注意的是,虽然 std::set 在许多情况下表现出良好的性能,但在某些特定场景下可能不是最优选择。例如,如果需要频繁进行范围查询或需要存储重复的元素,std::multiset 或其他数据结构可能更适合。此外,对于需要更高性能的场景,可能需要考虑使用自定义数据结构或并行算法来优化性能。
2 std::set 的基本使用
2.1 std::set 的声明与初始化
声明
首先,需要包含<set>头文件以使用 std::set:
#include <set>
#include <string>
// 声明一个整数类型的链表
std::set<int> vals;
// 声明一个字符串类型的链表
std::set<std::string> strs;
// 声明一个自定义类型的链表
struct MyStruct
{
bool operator<(const MyStruct& data) const
{
return id < data.id;
}
int id;
std::string name;
};
std::set<MyStruct> myStructs;
初始化
可以使用多种方法来初始化std::set。
(1)默认初始化:
创建一个空的 std::set 容器。
std::set<int> vals; // 空的 set
(2)使用初始化列表:
在声明时直接使用初始化列表来添加元素,元素会按照排序顺序插入。
std::set<int> vals = { 2, 1, 3, 4, 5};
// vals 现在包含元素:1, 2, 3, 4, 5
(3)使用迭代器或范围构造:
使用另一个容器的迭代器或范围来初始化 std::set。
std::vector<int> vec = {1, 2, 3, 4, 5};
std::set<int> vals(vec.begin(), vec.end());
(4)复制初始化:
使用另一个 std::set 来初始化新的 std::set。
std::set<int> firstSet = { 1, 2, 3 };
std::set<int> secondSet(firstSet); // 复制初始化
// secondSet 现在包含元素:1, 2, 3
(5)移动初始化:
使用另一个 std::set 的移动构造函数来初始化新的 std::set。
std::set<int> firstSet = {1, 2, 3};
std::set<int> secondSet(std::move(firstSet)); // 移动初始化
// secondSet 现在包含元素:1, 2, 3
// firstSet 现在是一个空的 set
2.2 std::set 的大小与容量
- std::set: 中与大小与容量相关的方法有如下几种:
- empty() const;: 检查 set 是否为空。
- size() const;: 返回 set 中的元素数量。
- max_size() const;: 返回 set 可能包含的最大元素数量。
如下为样例代码:
#include <iostream>
#include <set>
int main()
{
// 创建一个空的 std::set
std::set<int> mySet;
// 检查 set 是否为空,并输出信息
if (mySet.empty()) {
std::cout << "The set is empty." << std::endl;
}
// 向 set 中插入一些元素
mySet.insert(5);
mySet.insert(2);
mySet.insert(9);
mySet.insert(1);
// 再次检查 set 是否为空,并输出信息
if (!mySet.empty()) {
std::cout << "The set is not empty." << std::endl;
}
// 获取 set 的大小,并输出信息
std::cout << "Size of the set: " << mySet.size() << std::endl;
// 获取 set 可能包含的最大元素数量,并输出信息
std::cout << "Maximum possible size of the set: " << mySet.max_size() << std::endl;
return 0;
}
上面代码的输出为:
The set is empty.
The set is not empty.
Size of the set: 4
Maximum possible size of the set: 576460752303423487
在上面代码中,首先创建了一个空的 std::set,并使用 empty() 方法来检查它是否为空。然后向 set 中插入了一些元素,并再次使用 empty() 方法来确认它现在不再为空。接着使用 size() 方法来获取 set 中的元素数量,并输出它。最后使用 max_size() 方法来获取 set 可能包含的最大元素数量。
注意:std::set 的 max_size() 方法通常返回的是一个非常大的数字,代表理论上的最大大小限制,这通常比实际可用的内存要大得多。在实际使用中,由于内存限制,可能无法接近这个理论上的最大值。
2.3 std::set 的构造函数与析构函数
构造函数
std::set 有多个构造函数,允许以不同的方式创建 set 对象。下面是一些常用的构造函数:
- std::set s;: 默认构造函数,创建一个空的 set。
- std::set s(const std::set& other);: 复制构造函数,创建一个与 other 相同的 set。
- std::set s(std::set&& other);: 移动构造函数,创建一个与 other 内容相同的 set,并将 other 置于有效但未定义的状态(C++11 新加入)。
- std::set s(const_iterator first, const_iterator last);: 范围构造函数,使用迭代器 first 和 last 指定的元素范围来创建 set。
- std::set s(std::initializer_list init);: 使用初始化列表来创建 set(C++11 新加入)。
析构函数
- ~set(): 析构函数,释放 set 中的所有元素。
如下为样例代码:
#include <iostream>
#include <set>
#include <vector>
int main()
{
// 使用默认构造函数创建一个空的 set
std::set<int> emptySet;
std::cout << "Empty set size: " << emptySet.size() << std::endl;
// 使用初始化列表构造函数创建一个 set
std::set<int> initListSet{ 1, 2, 3, 4, 5 };
std::cout << "Init list set: ";
for (int num : initListSet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用复制构造函数创建一个与 initListSet 相同的 set
std::set<int> copiedSet(initListSet);
std::cout << "Copied set: ";
for (int num : copiedSet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 创建一个 vector 并使用范围构造函数创建一个 set
std::vector<int> vec{ 6, 7, 8, 9, 10 };
std::set<int> rangeSet(vec.begin(), vec.end());
std::cout << "Range set: ";
for (int num : rangeSet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用移动构造函数从另一个 set 移动元素创建一个新的 set
std::set<int> movingSet(std::move(initListSet));
std::cout << "Moving set: ";
for (int num : movingSet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 验证原始 initListSet 已被移动构造函数清空
if (initListSet.empty()) {
std::cout << "initListSet is now empty after moving construction." << std::endl;
}
else {
std::cout << "initListSet still contains elements after moving construction." << std::endl;
}
return 0;
}
上面代码的输出为:
Empty set size: 0
Init list set: 1 2 3 4 5
Copied set: 1 2 3 4 5
Range set: 6 7 8 9 10
Moving set: 1 2 3 4 5
initListSet is now empty after moving construction.
上面代码展示了如何使用 std::set 的不同构造函数来创建 set 对象。每种构造函数都用于创建一个具有不同特性的 set。另外,还展示了如何使用迭代器范围和初始化列表来构造 set。
注意:std::set 的析构函数是自动调用的,当 set 对象离开其作用域时,它会自动释放其分配的内存。上面代码没有显式调用析构函数,因为 C++ 的内存管理会自动处理这些事项。
3 std::set 的元素操作
3.1 std::set 元素的访问与修改
在 std::set 中,元素的访问和修改有一些特定的方法。由于 std::set 是一个有序的集合,它不允许重复的元素,并且元素总是按照某种排序规则(通常是小于比较)进行排序。因此,访问和修改元素的方式与其他容器(如 std::vector 或 std::list)略有不同。
3.1.1 访问元素
使用迭代器访问
可以使用迭代器来访问 std::set 中的元素。迭代器提供了对容器中元素的直接访问。
std::set<int> mySet = {1, 2, 3, 4, 5};
for (std::set<int>::iterator it = mySet.begin(); it != mySet.end(); ++it) {
std::cout << *it << " "; // 输出:1 2 3 4 5
}
使用 find 方法访问
可以使用 find 方法来查找该元素并获得一个指向它的迭代器。
std::set<int> mySet = {1, 2, 3, 4, 5};
std::set<int>::iterator it = mySet.find(3);
if (it != mySet.end()) {
std::cout << "Found: " << *it << std::endl; // 输出:Found: 3
} else {
std::cout << "Not found" << std::endl;
}
使用 lower_bound 和 upper_bound 访问
这两个方法可以用来查找集合中第一个不小于(或大于)给定值的元素。它们通常用于访问某个范围内的元素。
std::set<int> mySet = {1, 2, 3, 4, 5};
std::set<int>::iterator lower = mySet.lower_bound(3); // 指向3
std::set<int>::iterator upper = mySet.upper_bound(3); // 指向4
while (lower != upper) {
std::cout << *lower << " "; // 输出:3 4
++lower;
}
3.1.2 修改元素
由于 std::set 中的元素是常量的(const),不能直接修改它们的值。但是可以通过以下方式间接地修改元素:
删除并重新插入
可以删除要修改的元素,并使用新的值重新插入它。
std::set<int> mySet = {1, 2, 3, 4, 5};
int oldValue = 3;
int newValue = 10;
mySet.erase(mySet.find(oldValue)); // 删除旧值
mySet.insert(newValue); // 插入新值
使用 std::map 替代
如果需要能够修改键对应的值,可以使用 std::map,它与 std::set 类似,但允许键(和值)被修改。
std::map<int, int> myMap;
myMap[1] = 10;
myMap[2] = 20;
// 修改键为1的值
myMap[1] = 100;
注意:修改 std::set 中的元素可能会导致容器重新排序,这可能会影响性能,特别是在大型容器中。因此,在性能关键的场景中,需要仔细考虑如何有效地管理集合中的元素。
3.2 std::set 元素的插入操作
std::set 中与元素插入操作相关的方法有如下几种:
- insert(const value_type& value);: 插入一个元素。
- insert(const_iterator position, const value_type& value);: 在指定位置插入一个元素。
- insert(const_iterator first, const_iterator last);: 插入一个元素范围。
- insert(std::initializer_list<value_type> init);: 使用初始化列表插入元素。
- emplace(const args&… args);: 在 set 中就地构造一个元素。
- emplace_hint(const_iterator hint, const args&… args);: 使用提示在 set 中就地构造一个元素。该方法用于在指定的位置提示下就地(in-place)构造并插入一个元素。这个函数接受一个迭代器(表示提示位置)和一组用于构造元素的参数。
如下为样例代码:
#include <iostream>
#include <set>
#include <vector>
int main()
{
// 创建一个空的 std::set
std::set<int> mySet;
// 使用 insert(const value_type& value) 插入单个元素
mySet.insert(5);
mySet.insert(10);
mySet.insert(15);
// 输出当前 set 的内容
std::cout << "After inserting single elements: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 insert(const_iterator position, const value_type& value) 在指定位置插入元素
// 由于 set 是有序的,指定位置可能会使元素插入到特定位置之后
auto it = mySet.find(10);
if (it != mySet.end()) {
mySet.insert(it, 12); // 在 10 之后插入 12
}
// 输出当前 set 的内容
std::cout << "After inserting an element at a specific position: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 insert(const_iterator first, const_iterator last) 插入元素范围
std::vector<int> vec{ 20, 25, 30 };
mySet.insert(vec.begin(), vec.end());
// 输出当前 set 的内容
std::cout << "After inserting a range of elements: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 insert(std::initializer_list<value_type> init) 插入元素
mySet.insert({ 35, 40, 45 });
// 输出当前 set 的内容
std::cout << "After inserting elements from an initializer list: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 emplace(const args&... args) 就地构造并插入一个元素
mySet.emplace(50);
// 输出当前 set 的内容
std::cout << "After emplacing an element: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 emplace_hint(const_iterator hint, const args&... args) 提示并就地构造插入一个元素
it = mySet.find(40);
if (it != mySet.end()) {
mySet.emplace_hint(it, 55); // 在 40 的位置提示插入 55
}
// 输出当前 set 的内容
std::cout << "After emplacing an element with a hint: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
上面代码的输出为:
After inserting single elements: 5 10 15
After inserting an element at a specific position: 5 10 12 15
After inserting a range of elements: 5 10 12 15 20 25 30
After inserting elements from an initializer list: 5 10 12 15 20 25 30 35 40 45
After emplacing an element: 5 10 12 15 20 25 30 35 40 45 50
After emplacing an element with a hint: 5 10 12 15 20 25 30 35 40 45 50 55
上述代码演示了如何使用 std::set 的各种插入方法来添加元素。首先,它插入单个元素,然后在指定位置插入一个元素。接着,它插入一个元素范围和一个初始化列表中的元素。最后,它展示了如何使用 emplace 和 emplace_hint 方法来就地构造并插入元素。
emplace 和 emplace_hint 方法是 C++11 引入的,它们允许在容器内直接构造元素,而不需要像 insert 那样先创建元素再复制或移动它。这可以提高性能,特别是当元素类型涉及复杂或昂贵的构造操作时。
注意:由于 std::set 是有序的,插入操作可能会导致容器内部重新排序,但这对用户是透明的。
3.3 std::set 元素的删除操作
std::set 中与元素删除操作相关的方法有如下几种:
- erase(const key_type& k);: 删除键为 k 的元素。
- erase(const_iterator position);: 删除指定位置的元素。
- erase(const_iterator first, const_iterator last);: 删除一个元素范围。
- clear();: 删除 set 中的所有元素。
如下为样例代码:
#include <iostream>
#include <set>
int main()
{
// 创建一个 std::set 并插入一些元素
std::set<int> mySet = { 5, 10, 15, 20, 25, 30 };
// 输出原始 set 的内容
std::cout << "Original set:" << std::endl;
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 erase(const key_type& k) 删除键为 k 的元素
int keyToDelete = 15;
size_t elementsBeforeErase = mySet.size();
mySet.erase(keyToDelete);
size_t elementsAfterErase = mySet.size();
std::cout << "After erasing element with key " << keyToDelete
<< ": " << elementsBeforeErase - elementsAfterErase << " element deleted." << std::endl;
// 输出当前 set 的内容
std::cout << "Set after erasing key: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 erase(const_iterator position) 删除指定位置的元素
auto it = mySet.find(20);
if (it != mySet.end()) {
mySet.erase(it);
}
// 输出当前 set 的内容
std::cout << "Set after erasing element at a specific position: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 erase(const_iterator first, const_iterator last) 删除一个元素范围
it = mySet.find(25);
if (it != mySet.end()) {
mySet.erase(it, std::next(it, 2)); // 删除 it 指向的元素以及下一个元素
}
// 输出当前 set 的内容
std::cout << "Set after erasing a range of elements: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用 clear() 删除 set 中的所有元素
mySet.clear();
// 输出当前 set 的内容,应为空
std::cout << "Set after clearing all elements: ";
for (const auto& num : mySet) {
std::cout << num << " ";
}
std::cout << std::endl;
// 确认 set 确实为空
std::cout << "Size of the set after clearing: " << mySet.size() << std::endl;
return 0;
}
上面代码的输出为:
Original set:
5 10 15 20 25 30
After erasing element with key 15: 1 element deleted.
Set after erasing key: 5 10 20 25 30
Set after erasing element at a specific position: 5 10 25 30
Set after erasing a range of elements: 5 10
Set after clearing all elements:
Size of the set after clearing: 0
上面代码展示了如何使用 std::set 的不同删除操作。首先,它创建了一个包含一些整数的 std::set,并输出了原始内容。然后,它使用 erase(const key_type& k) 删除了一个指定键的元素,并输出了删除后的内容。接着,它使用 erase(const_iterator position) 删除了一个指定位置的元素,并再次输出了内容。之后,它使用 erase(const_iterator first, const_iterator last) 删除了一个元素范围,并输出了更新后的内容。最后,它使用 clear() 清空了整个 std::set,并输出了最终为空的内容以及确认 set 大小的输出。
3.4 std::set 元素的查找操作
std::set 中与查找操作的方法有如下几种:
- find(const key_type& k);: 查找键为 k 的元素,并返回指向它的迭代器。
- count(const key_type& k);: 返回键为 k 的元素的数量(对于 set,此值始终为 0 或 1)。
- lower_bound(const key_type& k);: 返回指向第一个不小于 k 的元素的迭代器。
- upper_bound(const key_type& k);: 返回指向第一个大于 k 的元素的迭代器。
- equal_range(const key_type& k);: 返回一个包含键为 k 的所有元素的迭代器对。
如下为样例代码:
#include <iostream>
#include <set>
int main()
{
// 创建一个 std::set 并插入一些元素
std::set<int> mySet = { 10, 20, 30, 40, 50 };
// 使用 find 查找元素
int keyToFind = 30;
auto it = mySet.find(keyToFind);
if (it != mySet.end()) {
std::cout << "Element " << keyToFind << " found: " << *it << std::endl;
}
else {
std::cout << "Element " << keyToFind << " not found" << std::endl;
}
// 使用 count 检查元素是否存在
int keyToCheck = 20;
size_t count = mySet.count(keyToCheck);
std::cout << "Count of element " << keyToCheck << ": " << count << std::endl;
// 使用 lower_bound 查找不小于给定键的第一个元素
int lowerBoundKey = 30;
auto lowerBoundIt = mySet.lower_bound(lowerBoundKey);
if (lowerBoundIt != mySet.end()) {
std::cout << "Lower bound of " << lowerBoundKey << ": " << *lowerBoundIt << std::endl;
}
else {
std::cout << "All elements are greater than " << lowerBoundKey << std::endl;
}
// 使用 upper_bound 查找大于给定键的第一个元素
int upperBoundKey = 30;
auto upperBoundIt = mySet.upper_bound(upperBoundKey);
if (upperBoundIt != mySet.end()) {
std::cout << "Upper bound of " << upperBoundKey << ": " << *upperBoundIt << std::endl;
}
else {
std::cout << "No elements are greater than " << upperBoundKey << std::endl;
}
// 使用 equal_range 查找给定键的所有元素(对于 set,最多一个)
int equalRangeKey = 40;
auto equalRange = mySet.equal_range(equalRangeKey);
if (equalRange.first != mySet.end()) {
std::cout << "Elements equal to " << equalRangeKey << ": ";
for (auto it = equalRange.first; it != equalRange.second; ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
else {
std::cout << "No elements equal to " << equalRangeKey << std::endl;
}
return 0;
}
上面代码的输出为:
Element 30 found: 30
Count of element 20: 1
Lower bound of 30: 30
Upper bound of 30: 40
Elements equal to 40: 40
在这个样例中,首先使用 find 方法查找一个特定的元素,并输出它是否找到以及找到的元素的值。接着,使用 count 方法来检查一个元素是否存在于集合中,因为 std::set 中的元素是唯一的,所以计数要么是 0(不存在),要么是 1(存在)。
然后,使用 lower_bound 方法来查找集合中第一个不小于给定键的元素。如果这样的元素存在,就输出它;否则,输出一个消息说明所有元素都大于给定的键。
接着,使用 upper_bound 方法来查找集合中第一个大于给定键的元素。如果这样的元素存在,就输出它;否则,输出一个消息说明没有元素大于给定的键。
最后,使用 equal_range 方法来查找给定键的所有元素。对于 std::set 来说,这通常只会返回一个元素(如果存在的话),因为集合中的元素是唯一的。遍历返回的迭代器范围,并输出所有等于给定键的元素。
3.5 std::set 元素的遍历删除
如果需要在遍历过程中逐个删除元素,可以使用 std::set::erase 方法结合普通的循环,但每次删除元素后,都需要更新迭代器。以下是一个逐个删除特定元素的例子:
如下为样例代码:
#include <iostream>
#include <set>
int main()
{
std::set<int> vals = { 1, 2, 3, 4, 5, 6 };
auto it = vals.begin();
while (it != vals.end()) {
if ((*it) % 2 == 0) {
it = vals.erase(it); // erase 返回下一个有效元素的迭代器
}
else {
++it; // 继续到下一个元素
}
}
// 输出结果
for (const auto& elem : vals) {
std::cout << elem << ' ';
}
return 0;
}
上面代码的输出为:
1 3 5
在上面代码中,使用一个循环来遍历 set,并在每次迭代中检查当前元素是否满足删除条件。如果满足条件,则使用 erase 方法删除该元素,并更新迭代器。如果不满足条件,则简单地递增迭代器以继续遍历。
注意:在删除元素后,迭代器 it 会被 erase 方法更新为指向被删除元素之后的位置,因此在下一次循环迭代中,it 仍然有效。
4 std::set 的迭代器
4.1 std::set 迭代器的基本使用
std::set 中与迭代器相关的方法有如下几种:
- begin(): 返回一个指向容器中第一个元素的迭代器。
- end(): 返回一个指向容器中最后一个元素之后位置的迭代器。
- rbegin(): 返回一个指向容器中最后一个元素的反向迭代器。
- rend(): 返回一个指向容器中第一个元素之前位置的反向迭代器。
- cbegin(), cend(), crbegin(), crend(): 与上述类似,但返回的是常量迭代器或常量反向迭代器。
如下为样例代码:
#include <iostream>
#include <set>
int main()
{
// 创建一个 std::set 并插入一些元素
std::set<int> mySet = { 5, 2, 9, 1, 5, 6 };
// 使用 begin() 和 end() 遍历 set(正向遍历)
std::cout << "Forward traversal set:\n";
for (auto it = mySet.begin(); it != mySet.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用 cbegin() 和 cend() 遍历 set(常量正向遍历)
std::cout << "Constant forward traversal set:\n";
for (const auto& it : mySet) {
std::cout << it << " ";
}
std::cout << std::endl;
// 使用 rbegin() 和 rend() 遍历 set(反向遍历)
std::cout << "Reverse traversal set:\n";
for (auto rit = mySet.rbegin(); rit != mySet.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
// 使用 crbegin() 和 crend() 遍历 set(常量反向遍历)
std::cout << "Constant reverse traversal set:\n";
for (auto rit = mySet.crbegin(); rit != mySet.crend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
return 0;
}
上面代码的输出为:
Forward traversal set:
1 2 5 6 9
Constant forward traversal set:
1 2 5 6 9
Reverse traversal set:
9 6 5 2 1
Constant reverse traversal set:
9 6 5 2 1
这个样例展示了如何使用不同类型的迭代器来遍历 std::set。begin() 和 end() 用于正向遍历,rbegin() 和 rend() 用于反向遍历。同时,cbegin(), cend(), crbegin(), 和 crend() 是常量版本的迭代器,它们用于保证在遍历过程中不会修改容器中的元素。
4.2 std::set 迭代器使用的注意事项
在使用 std::set 的迭代器时,有几个重要的注意事项需要牢记:
(1)有效性: 一旦迭代器指向的元素被删除或移动,迭代器就失效了。在删除或修改 std::set 中的元素后,必须确保不再使用失效的迭代器。
(2)常量迭代器: std::set 提供了常量迭代器(const_iterator),这些迭代器不能用来修改元素的值。
(3)遍历过程中删除元素: 在遍历 std::set 的过程中直接删除元素会导致迭代器失效。如果需要删除元素,通常的做法是使用一个单独的容器(如 std::vector 或另一个 std::set)来保存要删除的键,然后在遍历结束后使用 erase 方法删除这些键。
(4)end() 方法返回的迭代器: end() 方法返回的迭代器指向的是容器中的“尾后”位置,即最后一个元素之后的位置。这个迭代器不能被解引用。在遍历容器时,应该小心不要试图访问 end() 返回的迭代器。
(5)修改键值: std::set 中的元素是根据键值排序的。如果通过迭代器修改了元素的键值,这将会破坏容器的排序特性。通常情况下,不应该这样做。如果需要改变元素的键值,应该先删除原元素,然后插入新的元素。