1 std::multiset 概述
std::multiset 是 C++ STL(标准模板库)中的一个容器,它定义在头文件<set>中。std::multiset 是一个多重集合容器,允许存储重复的元素键值,并且这些元素键值按照特定的严格弱排序准则进行排序。与 set 容器不同,multiset 允许插入重复的元素,这是它们之间的主要区别。
multiset 的内部实现通常基于红黑树这种平衡二叉搜索树,因此元素的搜索、插入和删除操作都具有对数级的时间复杂度。由于这种数据结构特性,multiset 的搜索效率非常高,可以快速找到某一键值下的所有元素位置。
multiset提供了一系列成员函数来操作容器中的元素,包括插入数据(insert)、删除数据(erase)、查找数据(find)、清空数据(clear)、判空(empty)、获取有效元素的大小(size)、获取键值中查找元素的个数(count)等。此外,它还提供了反向迭代器(rbegin和rend),可以用于反向遍历容器中的元素。
需要注意的是,multiset 中元素的 value 也会识别它组成的键值对,但元素的 value 在容器中是不能被修改的。因此,multiset 更适用于需要存储重复元素且需要保持元素有序的场景。然而,如果插入和删除操作非常频繁,multiset 可能不是最佳选择,因为每次插入或删除都可能导致树的重新平衡,从而影响性能。
1.1 std::multiset 的内部实现
std::multiset 的内部实现主要基于红黑树这种数据结构。红黑树是一种自平衡的二叉搜索树,通过着色和特定的调整规则来保持树的平衡,从而在动态数据插入、删除和查找过程中保持相对高效的性能。
在std::multiset 中,红黑树的特性使得元素总是保持有序。每个节点存储一个元素,并且满足二叉搜索树的性质:对于每个节点,其左子树中的所有元素都小于该节点,其右子树中的所有元素都大于该节点。此外,红黑树还通过颜色和旋转操作来维护树的平衡,确保树的深度不会过大,从而保证了操作的效率。
由于红黑树的这些特性,std::multiset 的插入、删除和查找操作都能保持对数级的时间复杂度,即 O(log n),其中n是容器中元素的数量。这使得 std::multiset 在处理大量数据时仍能保持高效的性能。
以下是一些关于 std::multiset 内部实现中红黑树数据结构的关键点:
(1)节点结构: 红黑树的每个节点包含多个信息,包括元素的值、节点的颜色(红色或黑色)、指向左子节点和右子节点的指针。节点颜色的信息对于维护树的平衡性至关重要。
(2)性质: 红黑树满足以下五个关键性质,这些性质保证了树的平衡性和搜索效率:
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色。
- 所有叶子节点(NIL 或空节点,通常不显式表示)是黑色。
- 如果一个节点是红色的,则它的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
- 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。
(3)插入操作: 当向 multiset 中插入一个新元素时,首先会像普通的二叉搜索树一样找到新元素应该插入的位置,然后执行插入操作。插入后,可能需要通过颜色调整和旋转操作来重新平衡树,以满足红黑树的性质。
(4)删除操作: 删除操作相对复杂一些,因为需要确保在删除节点后树仍然保持平衡。删除操作可能涉及节点的合并、颜色的更改以及树的旋转。
(5)查找操作: 由于红黑树是一种二叉搜索树,因此查找操作与普通的二叉搜索树类似。从根节点开始,根据节点的值和搜索的键值进行比较,决定是向左子树还是向右子树递归查找,直到找到目标元素或确定元素不存在。
通过红黑树的这些内部实现机制,std::multiset能够高效地维护元素的排序状态,并支持快速的插入、删除和查找操作。
1.2 std::multiset 的性能特点
std::multiset 的性能特点主要体现在以下几个方面:
(1)有序性: std::multiset 是一个有序集合容器,它根据元素的键值自动进行排序。这种有序性使得元素可以按照特定的顺序进行存储和检索,从而方便了对元素的排序和查找操作。
(2)插入与删除的对数级复杂度: 由于 std::multiset 内部采用红黑树作为数据结构,因此其插入和删除操作的时间复杂度都是对数级的,即 O(log n),其中 n 是容器中元素的数量。这意味着,无论容器中有多少元素,插入和删除操作的效率都能保持相对稳定,不会随着元素数量的增加而显著下降。
(3)高效的查找性能: 同样得益于红黑树的特性,std::multiset 的查找操作也具有对数级的时间复杂度。这使得在大量元素中快速定位到特定元素成为可能,提高了程序的执行效率。
(4)允许重复元素: 与 std::set 不同,std::multiset 允许存储重复的元素。这意味着在同一个 multiset 容器中,可以有多个具有相同键值的元素存在。这一特性使得 multiset 在某些需要处理重复元素的场景中非常有用。
(5)空间消耗: 由于 std::multiset 使用红黑树作为内部数据结构,而红黑树在维护平衡的过程中可能需要额外的空间来存储节点的颜色和进行旋转操作。因此,相对于其他简单的数据结构(如数组或链表),multiset 的空间消耗可能会稍高一些。但需要注意的是,这种空间消耗是为了换取更高的操作效率而付出的代价。
需要注意的是,虽然 std::multiset 具有上述性能特点,但在某些特定场景下可能并不是最优选择。例如,如果需要进行频繁的插入和删除操作,并且不关心元素的顺序,那么使用其他数据结构(如哈希表)可能会更加高效。
2 std::multiset 的基本使用
2.1 std::multiset 的声明与初始化
声明
首先,需要包含<set>头文件以使用 std::multiset:
#include <set>
#include <string> // 声明一个整数类型的 multiset
std::multiset<int> vals;// 声明一个字符串类型的 multiset
std::multiset<std::string> strs;// 声明一个自定义类型的 multiset
struct MyStruct
{int id;std::string name;
};
std::multiset<MyStruct> myStructs;
初始化
可以使用多种方法来初始化 std::multiset。
(1)默认初始化:
创建一个空的 std::multiset 容器。
std::multiset<int> vals; // 空的 multiset
(2)使用初始化列表:
在声明时直接使用初始化列表来添加元素,元素会按照排序顺序插入。
std::multiset<int> vals = { 2, 1, 3, 3, 4, 5 };
// vals 现在包含元素:1, 2, 3, 3, 4, 5
(3)使用迭代器或范围构造:
使用另一个容器的迭代器或范围来初始化 std::multiset。
std::vector<int> vec = { 2, 1, 3, 3, 4, 5 };
std::multiset<int> vals(vec.begin(), vec.end());
// vals 现在包含元素:1, 2, 3, 3, 4, 5
(4)复制初始化:
使用另一个 std::multiset 来初始化新的 std::multiset。
std::multiset<int> firstSet = { 2, 1, 3, 3, 4, 5 };
std::multiset<int> secondSet(firstSet); // 复制初始化
// secondSet 现在包含元素:1, 2, 3, 3, 4, 5
(5)移动初始化:
使用另一个 std::multiset 的移动构造函数来初始化新的 std::multiset。
std::multiset<int> firstSet = { 2, 1, 3, 3, 4, 5 };
std::multiset<int> secondSet(std::move(firstSet)); // 移动初始化
// secondSet 现在包含元素:1, 2, 3, 3, 4, 5
// firstSet 现在是一个空的 multiset
2.2 std::multiset 的大小与容量
- std::multiset: 中与大小与容量相关的方法有如下几种:
- empty() const;: 检查 multiset 是否为空。
- size() const;: 返回 multiset 中的元素数量。
- max_size() const;: 返回 multiset 可能包含的最大元素数量。
如下为样例代码:
#include <iostream>
#include <set> int main()
{// 创建一个空的 std::multiset std::multiset<int> mySet;// 检查 multiset 是否为空,并输出信息 if (mySet.empty()) {std::cout << "The multiset is empty." << std::endl;}// 向 multiset 中插入一些元素 mySet.insert(5);mySet.insert(2);mySet.insert(9);mySet.insert(9);mySet.insert(1);// 再次检查 multiset 是否为空,并输出信息 if (!mySet.empty()) {std::cout << "The multiset is not empty." << std::endl;}// 获取 multiset 的大小,并输出信息 std::cout << "Size of the multiset: " << mySet.size() << std::endl;// 获取 multiset 可能包含的最大元素数量,并输出信息 std::cout << "Maximum possible size of the multiset: " << mySet.max_size() << std::endl;return 0;
}
上面代码的输出为:
The multiset is empty.
The multiset is not empty.
Size of the multiset: 5
Maximum possible size of the multiset: 576460752303423487
在上面代码中,首先创建了一个空的 std::multiset,并使用 empty() 方法来检查它是否为空。然后向 multiset 中插入了一些元素,并再次使用 empty() 方法来确认它现在不再为空。接着使用 size() 方法来获取 multiset 中的元素数量,并输出它。最后使用 max_size() 方法来获取 multiset 可能包含的最大元素数量。
注意:std::multiset 的 max_size() 方法通常返回的是一个非常大的数字,代表理论上的最大大小限制,这通常比实际可用的内存要大得多。在实际使用中,由于内存限制,可能无法接近这个理论上的最大值。
2.3 std::multiset 的构造函数与析构函数
构造函数
std::multiset 有多个构造函数,允许以不同的方式创建 multiset 对象。下面是一些常用的构造函数:
- std::multiset s;: 默认构造函数,创建一个空的 multiset。
- std::multiset s(const std::multiset& other);: 复制构造函数,创建一个与 other 相同的 multiset。
- std::multiset s(std::multiset&& other);: 移动构造函数,创建一个与 other 内容相同的 multiset,并将 other 置于有效但未定义的状态(C++11 新加入)。
- std::multiset s(const_iterator first, const_iterator last);: 范围构造函数,使用迭代器 first 和 last 指定的元素范围来创建 multiset。
- std::multiset s(std::initializer_list init);: 使用初始化列表来创建 multiset(C++11 新加入)。
析构函数
- ~multiset(): 析构函数,释放 multiset 中的所有元素。
如下为样例代码:
#include <iostream>
#include <set>
#include <vector> int main()
{// 使用默认构造函数创建一个空的 multiset std::multiset<int> emptySet;std::cout << "Empty multiset size: " << emptySet.size() << std::endl;// 使用初始化列表构造函数创建一个 multiset std::multiset<int> initListSet{ 1, 2, 3, 3, 4, 5 };std::cout << "Init list multiset: ";for (int num : initListSet) {std::cout << num << " ";}std::cout << std::endl;// 使用复制构造函数创建一个与 initListSet 相同的 multiset std::multiset<int> copiedSet(initListSet);std::cout << "Copied multiset: ";for (int num : copiedSet) {std::cout << num << " ";}std::cout << std::endl;// 创建一个 vector 并使用范围构造函数创建一个 multiset std::vector<int> vec{ 6, 7, 7, 8, 9, 10 };std::multiset<int> rangeSet(vec.begin(), vec.end());std::cout << "Range multiset: ";for (int num : rangeSet) {std::cout << num << " ";}std::cout << std::endl;// 使用移动构造函数从另一个 multiset 移动元素创建一个新的 multiset std::multiset<int> movingSet(std::move(initListSet));std::cout << "Moving multiset: ";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 multiset size: 0
Init list multiset: 1 2 3 3 4 5
Copied multiset: 1 2 3 3 4 5
Range multiset: 6 7 7 8 9 10
Moving multiset: 1 2 3 3 4 5
initListSet is now empty after moving construction.
上面代码展示了如何使用 std::multiset 的不同构造函数来创建 multiset 对象。每种构造函数都用于创建一个具有不同特性的 multiset。另外,还展示了如何使用迭代器范围和初始化列表来构造 multiset。
注意:std::multiset 的析构函数是自动调用的,当 multiset 对象离开其作用域时,它会自动释放其分配的内存。上面代码没有显式调用析构函数,因为 C++ 的内存管理会自动处理这些事项。
3 std::multiset 的元素操作
3.1 std::multiset 元素的访问与修改
在 std::multiset 中,元素的访问和修改有一些特定的方法。由于 std::multiset 是一个有序的集合,其元素总是按照某种排序规则(通常是小于比较)进行排序,并且允许重复的元素。因此,访问和修改元素的方式与其他容器(如 std::vector 或 std::list)略有不同。
3.1.1 访问元素
使用迭代器访问
可以使用迭代器来访问 std::multiset 中的元素。迭代器提供了对容器中元素的直接访问。
std::multiset<int> mySet = { 1, 2, 3, 3, 4, 5 };
for (std::multiset<int>::iterator it = mySet.begin(); it != mySet.end(); ++it) {std::cout << *it << " "; // 输出:1 2 3 3 4 5
}
使用 find 方法访问
在 std::multiset 中,由于允许存储重复的元素,find 方法只能找到第一个与给定键值相等的元素。如果你想要访问所有与给定键值相等的元素,你需要使用 find 方法结合迭代器来遍历这些元素。
以下是一个示例,展示了如何使用 find 方法找到 std::multiset 中的第一个重复元素,并通过迭代器遍历所有与给定键值相等的元素:
#include <iostream>
#include <set> int main()
{std::multiset<int> mySet{ 1, 2, 2, 3, 5, 5, 5 };int valueToFind = 5;// 使用 find 方法找到第一个与给定键值相等的元素 auto it = mySet.find(valueToFind);// 检查是否找到了元素 if (it != mySet.end()) {std::cout << "Found " << valueToFind << ": ";// 遍历所有与给定键值相等的元素 do {std::cout << *it << ' ';} while (++it != mySet.end() && *it == valueToFind);std::cout << std::endl;}else {std::cout << valueToFind << " not found in the multiset." << std::endl;}return 0;
}
上面代码的输出为:
Found 5: 5 5 5
在这个示例中,首先使用 find 方法找到了第一个值为 5 的元素。然后进入一个 do-while 循环,在循环内部,首先打印当前迭代器的值,然后递增迭代器。循环继续的条件是迭代器没有到达 mySet.end() 并且当前迭代器的值仍然等于要查找的值。这样就能遍历并打印出所有与给定键值相等的元素。
注意,这种方法假设 std::multiset 是按升序排序的,因此可以简单地通过递增迭代器来遍历所有相等的元素。如果 std::multiset 是按降序排序的,那么你需要递减迭代器来遍历所有相等的元素。
使用 lower_bound 和 upper_bound 访问
这两个方法可以用来查找集合中第一个不小于(或大于)给定值的元素。它们通常用于访问某个范围内的元素。
std::multiset<int> mySet = { 1, 2, 3, 3, 4, 5 };
std::multiset<int>::iterator lower = mySet.lower_bound(3); // 指向3
std::multiset<int>::iterator upper = mySet.upper_bound(4); // 指向4
while (lower != upper) {std::cout << *lower << " "; // 输出:3 3 4++lower;
}
3.1.2 修改元素
由于 std::multiset 中的元素是常量的(const),不能直接修改它们的值。如果想要修改具有特定键值的所有元素,则需要遍历 multiset 并找到所有匹配的元素,然后对每个元素执行删除和插入操作。
如下为样例代码:
#include <iostream>
#include <set> int main()
{std::multiset<int> mySet{ 1, 2, 2, 3, 5, 5, 5 };int oldValue = 5;int newValue = 6;// 遍历并修改所有与给定键值相等的元素 while (true) {auto it = mySet.find(oldValue);if (it == mySet.end()) {// 没有更多匹配的元素了 break;}// 删除旧元素 mySet.erase(it);// 插入新元素 mySet.insert(newValue);}// 打印修改后的 multiset for (const auto& elem : mySet) {std::cout << elem << ' ';}std::cout << std::endl;return 0;
}
上面代码的输出为:
1 2 2 3 6 6 6
注意:修改 std::multiset 中的元素可能会导致容器重新排序,这可能会影响性能,特别是在大型容器中。因此,在性能关键的场景中,需要仔细考虑如何有效地管理集合中的元素。
3.2 std::multiset 元素的插入操作
std::multiset 中与元素插入操作相关的方法有如下几种:
- insert(const value_type& value);: 插入一个元素。
- insert(std::initializer_list<value_type> init);: 使用初始化列表插入元素。
- emplace(const args&… args);: 在 multiset 中就地构造一个元素。
如下为样例代码:
#include <iostream>
#include <set>
#include <initializer_list> int main()
{std::multiset<int> mySet;// 使用 insert(const value_type& value) 插入一个元素 mySet.insert(1);// 使用 insert(std::initializer_list<value_type> init) 使用初始化列表插入元素 mySet.insert({ 2, 3, 3, 4 });// 使用 emplace(const args&... args) 在 multiset 中就地构造一个元素 mySet.emplace(5);mySet.emplace(6);// 打印 multiset 中的所有元素 for (const auto& elem : mySet) {std::cout << elem << ' ';}std::cout << std::endl;return 0;
}
上面代码的输出为:
1 2 3 3 4 5 6
上述代码演示了如何使用 std::multiset 的各种插入方法来添加元素。首先使用 insert(const value_type& value) 方法插入一个元素 1。接着使用 insert(std::initializer_list<value_type> init) 方法,通过初始化列表一次性插入了四个元素 2、3、3 和 4。最后使用 emplace(const args&… args) 方法就地构造并插入了两个元素 5 和 6。
emplace 方法允许直接传递构造函数的参数给 multiset,而不是先构造一个对象再插入。这可以减少不必要的拷贝或移动操作,特别是对于复杂类型或资源密集型对象来说,可以提高性能。这个例子简单地插入了 int 类型的元素,因此性能提升不明显,但对于更复杂的类型来说,使用 emplace 会更加高效。
需要注意的是,由于 std::multiset 是基于排序的集合,插入元素的顺序不会影响到最终集合中元素的顺序,元素会按照它们的键值自动排序。
3.3 std::multiset 元素的删除操作
std::multiset 中与元素删除操作相关的方法有如下几种:
- erase(const key_type& k);: 删除键为 k 的元素。
- erase(const_iterator position);: 删除指定位置的元素。
- erase(const_iterator first, const_iterator last);: 删除一个元素范围。
- clear();: 删除 multiset 中的所有元素。
如下为样例代码:
#include <iostream>
#include <set> int main()
{std::multiset<int> mySet{ 1, 2, 2, 3, 4, 4, 5 };// 打印初始的 multiset std::cout << "Initial multiset: ";for (const auto& elem : mySet) {std::cout << elem << ' ';}std::cout << std::endl;// 使用 erase(const key_type& k); 删除键为 k 的元素 mySet.erase(4); // 这将删除所有键为 4 的元素 // 打印删除后的 multiset std::cout << "After erasing 4: ";for (const auto& elem : mySet) {std::cout << elem << ' ';}std::cout << std::endl;// 使用 erase(const_iterator position); 删除指定位置的元素 auto it = mySet.find(2); // 找到键为 2 的元素 if (it != mySet.end()) {mySet.erase(it); // 删除找到的第一个键为 2 的元素 }// 打印删除后的 multiset std::cout << "After erasing one 2: ";for (const auto& elem : mySet) {std::cout << elem << ' ';}std::cout << std::endl;// 使用 erase(const_iterator first, const_iterator last); 删除一个元素范围 it = mySet.find(2); // 再次找到键为 2 的元素 if (it != mySet.end()) {++it; // 移动到下一个元素(如果有的话) mySet.erase(mySet.find(2), it); // 删除从第一个 2 到下一个元素之前的所有元素 }// 打印删除后的 multiset std::cout << "After erasing range of 2s: ";for (const auto& elem : mySet) {std::cout << elem << ' ';}std::cout << std::endl;// 使用 clear(); 删除 multiset 中的所有元素 mySet.clear();// 打印清空后的 multiset std::cout << "After clearing the multiset: ";for (const auto& elem : mySet) {std::cout << elem << ' ';}std::cout << std::endl;return 0;
}
上面代码的输出为:
Initial multiset: 1 2 2 3 4 4 5
After erasing 4: 1 2 2 3 5
After erasing one 2: 1 2 3 5
After erasing range of 2s: 1 3 5
After clearing the multiset:
上面代码展示了如何使用 std::multiset 的不同删除操作。首先,它创建了一个包含一些整数的 std::multiset,并输出了原始内容。然后,它使用 erase(const key_type& k) 删除了指定键的所有元素,并输出了删除后的内容。接着,它使用 erase(const_iterator position) 删除了一个指定位置的元素,并再次输出了内容。之后,它使用 erase(const_iterator first, const_iterator last) 删除了一个元素范围,并输出了更新后的内容。最后,它使用 clear() 清空了整个 std::multiset,并输出了最终为空的内容以及确认 multiset 大小的输出。
3.4 std::multiset 元素的查找操作
std::multiset 中与查找操作的方法有如下几种:
- find(const key_type& k);: 查找键为 k 的元素,并返回指向它的迭代器。
- count(const key_type& k);: 返回键为 k 的元素的数量(对于 multiset,此值始终为有可能大于 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::multiset<int> mySet{ 1, 2, 3, 3, 4, 5 };// 使用 find(const key_type& k); 查找键为 k 的元素 auto it = mySet.find(3);if (it != mySet.end()) {std::cout << "Found 3" << std::endl;}else {std::cout << "3 not found in the multiset." << std::endl;}// 使用 count(const key_type& k); 返回键为 k 的元素的数量 int countOfThree = mySet.count(3);std::cout << "Number of 3s in the multiset: " << countOfThree << std::endl;// 使用 lower_bound(const key_type& k); 返回指向第一个不小于 k 的元素的迭代器 auto lower = mySet.lower_bound(3);std::cout << "Lower bound of 3 is: " << *lower << std::endl;// 使用 upper_bound(const key_type& k); 返回指向第一个大于 k 的元素的迭代器 auto upper = mySet.upper_bound(3);std::cout << "Upper bound of 3 is: " << *upper << std::endl;// 使用 equal_range(const key_type& k); 返回一个包含键为 k 的所有元素的迭代器对 auto range = mySet.equal_range(3);std::cout << "Range of 3s in the multiset: ";for (auto it = range.first; it != range.second; ++it) {std::cout << *it << ' ';}std::cout << std::endl;return 0;
}
上面代码的输出为:
Found 3
Number of 3s in the multiset: 2
Lower bound of 3 is: 3
Upper bound of 3 is: 4
Range of 3s in the multiset: 3 3
在这个样例中,首先使用 find 方法查找一个特定的元素,并输出它是否找到以及找到的元素的值。接着,使用 count 方法来检查一个元素是否存在于集合中。
然后,使用 lower_bound 方法来查找集合中第一个不小于给定键的元素。如果这样的元素存在,就输出它;否则,输出一个消息说明所有元素都大于给定的键。
接着,使用 upper_bound 方法来查找集合中第一个大于给定键的元素。如果这样的元素存在,就输出它;否则,输出一个消息说明没有元素大于给定的键。
最后,使用 equal_range 方法来查找给定键的所有元素。随后遍历返回的迭代器范围,并输出所有等于给定键的元素。
3.5 std::multiset 元素的遍历删除
如果需要在遍历过程中逐个删除元素,可以使用 std::multiset::erase 方法结合普通的循环,但每次删除元素后,都需要更新迭代器。以下是一个逐个删除特定元素的例子:
如下为样例代码:
#include <iostream>
#include <set> int main()
{std::multiset<int> vals = { 1, 2, 3, 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 3 5
在上面代码中,使用一个循环来遍历 multiset,并在每次迭代中检查当前元素是否满足删除条件。如果满足条件,则使用 erase 方法删除该元素,并更新迭代器。如果不满足条件,则简单地递增迭代器以继续遍历。
注意:在删除元素后,迭代器 it 会被 erase 方法更新为指向被删除元素之后的位置,因此在下一次循环迭代中,it 仍然有效。
4 std::multiset 的迭代器
4.1 std::multiset 迭代器的基本使用
std::multiset 中与迭代器相关的方法有如下几种:
- begin(): 返回一个指向容器中第一个元素的迭代器。
- end(): 返回一个指向容器中最后一个元素之后位置的迭代器。
- rbegin(): 返回一个指向容器中最后一个元素的反向迭代器。
- rend(): 返回一个指向容器中第一个元素之前位置的反向迭代器。
- cbegin(), cend(), crbegin(), crend(): 与上述类似,但返回的是常量迭代器或常量反向迭代器。
如下为样例代码:
#include <iostream>
#include <set> int main()
{// 创建一个 std::multiset 并插入一些元素 std::multiset<int> mySet = { 5, 2, 9, 1, 5, 6 };// 使用 begin() 和 end() 遍历 multiset(正向遍历) std::cout << "Forward traversal multiset:\n";for (auto it = mySet.begin(); it != mySet.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 cbegin() 和 cend() 遍历 multiset(常量正向遍历) std::cout << "Constant forward traversal multiset:\n";for (const auto& it : mySet) {std::cout << it << " ";}std::cout << std::endl;// 使用 rbegin() 和 rend() 遍历 multiset(反向遍历) std::cout << "Reverse traversal multiset:\n";for (auto rit = mySet.rbegin(); rit != mySet.rend(); ++rit) {std::cout << *rit << " ";}std::cout << std::endl;// 使用 crbegin() 和 crend() 遍历 multiset(常量反向遍历) std::cout << "Constant reverse traversal multiset:\n";for (auto rit = mySet.crbegin(); rit != mySet.crend(); ++rit) {std::cout << *rit << " ";}std::cout << std::endl;return 0;
}
上面代码的输出为:
Forward traversal multiset:
1 2 2 5 5 6 9
Constant forward traversal multiset:
1 2 2 5 5 6 9
Reverse traversal multiset:
9 6 5 5 2 2 1
Constant reverse traversal multiset:
9 6 5 5 2 2 1
这个样例展示了如何使用不同类型的迭代器来遍历 std::multiset。begin() 和 end() 用于正向遍历,rbegin() 和 rend() 用于反向遍历。同时,cbegin(), cend(), crbegin(), 和 crend() 是常量版本的迭代器,它们用于保证在遍历过程中不会修改容器中的元素。
4.2 std::multiset 迭代器使用的注意事项
在使用 std::multiset 的迭代器时,有几个重要的注意事项需要牢记:
(1)有效性: 一旦迭代器指向的元素被删除或移动,迭代器就失效了。在删除或修改 std::multiset 中的元素后,必须确保不再使用失效的迭代器。
(2)常量迭代器: std::multiset 提供了常量迭代器(const_iterator),这些迭代器不能用来修改元素的值。
(3)遍历过程中删除元素: 在遍历 std::multiset 的过程中直接删除元素会导致迭代器失效。如果需要删除元素,通常的做法是使用一个单独的容器(如 std::vector 或另一个 std::multiset)来保存要删除的键,然后在遍历结束后使用 erase 方法删除这些键。
(4)end() 方法返回的迭代器: end() 方法返回的迭代器指向的是容器中的“尾后”位置,即最后一个元素之后的位置。这个迭代器不能被解引用。在遍历容器时,应该小心不要试图访问 end() 返回的迭代器。
(5)修改键值: std::multiset 中的元素是根据键值排序的。如果通过迭代器修改了元素的键值,这将会破坏容器的排序特性。通常情况下,不应该这样做(实际上这么做也无法通过编译)。如果需要改变元素的键值,应该先删除原元素,然后插入新的元素。