突破编程_C++_STL教程( multiset 的基础知识)

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 中的元素是根据键值排序的。如果通过迭代器修改了元素的键值,这将会破坏容器的排序特性。通常情况下,不应该这样做(实际上这么做也无法通过编译)。如果需要改变元素的键值,应该先删除原元素,然后插入新的元素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/741910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

幸运数字(蓝桥杯23省赛)

幸运数字&#xff08;蓝桥杯23省赛&#xff09; 题目分析 暴力判断的思路就不讲了&#xff0c;这道题主要想将一个思想&#xff0c;对于这种数字类的题目&#xff0c;对半枚举的思路。 100000000是不符合要求的&#xff0c;所以最多遍历到99999999。这个思路是我一半一半的凑…

【Hibernate-Validate】常用注解

常用注解: NotNull:被注释的元素(任何元素)必须不为 nul, 集合为空也是可以的。NotEmpty:用来校验字符串、集合、map、数组不能为null或也不能为空(字符串传入空格也不可以)(集合需至少包含一个元素)NotBlank:被注释的字符串的必须非空&#xff0c;空格也不行&#xff0c;空字…

Unity3D 动态生成场景管理节点详解

前言 Unity3D 提供了丰富的功能和工具&#xff0c;可以帮助开发者快速高效地创建各种类型的游戏。在游戏开发过程中&#xff0c;有时候我们需要动态生成场景管理节点来管理游戏场景中的各种元素&#xff0c;比如角色、道具、敌人等。本文将详细介绍如何在Unity3D中动态生成场景…

来吧伙计们,让AI教我们怎么说海盗语

“如果想伺机而动&#xff0c;就是这样。”——杰克船长提到海盗&#xff0c;我们往往联想到约翰尼德普在《加勒比海盗》中饰演的杰克船长。我们有什么理由不喜欢海盗呢&#xff1f;他们航行在海上&#xff0c;寻找埋藏的宝藏&#xff0c;痛饮朗姆酒&#xff0c;用自己独特的海…

FreMIM:傅里叶变换与遮罩的图像建模在医学图像分割中的应用

代码链接&#xff1a;GitHub - Rubics-Xuan/FreMIM: This repo holds the official code for the paper "FreMIM: Fourier Transform Meets Masked Image Modeling for Medical Image Segmentation". 论文链接&#xff1a;https://arxiv.org/abs/2304.10864 收录于…

差旅补助解决方案|数字化差补赋能业务提效

长期以来&#xff0c;差旅补助一直是企业为了激励员工出差并表达对员工的关怀而采取的一种方式&#xff0c;以经济和福利支持来鼓励员工积极投入工作。然而&#xff0c;由于传统差旅补助的核算、发放和管理方式存在诸多问题&#xff0c;往往适得其反&#xff0c;无法实现企业的…

RocketMQ 面试题及答案整理,最新面试题

RocketMQ的消息存储机制是如何设计的&#xff1f; RocketMQ消息存储机制的设计原理&#xff1a; 1、CommitLog文件&#xff1a; 所有的消息都存储在一个连续的CommitLog文件中&#xff0c;保证了消息的顺序写入&#xff0c;提高写入性能。 2、消费队列&#xff1a; 为每个主…

web前端练习一

1.电子汇款单 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>电子汇款单</title> </head> <body><h2>工商银行电子汇款单</h2><table border"1px">&…

C语言自学笔记10----C语言数组

C 语言数组 数组是可以存储多个值的变量。例如&#xff0c;如果要存储100个整数&#xff0c;则可以为其创建一个数组。 示例 int data[100]; 例如&#xff0c; float mark[5]; 在这里&#xff0c;我们声明了一个浮点类型的数组mark。其大小为5。意味着&#xff0c;它可以容纳5个…

MySQL row_number()函数,rank()函数和dense_rank()函数

从MySQL8.0开始引用row_number(), rank()函数和dense_rank()函数&#xff0c;也就是常见的窗口函数&#xff0c;三个函数都是一种用于计算排名的工具&#xff0c;它们根据指定的列对结果集进行排序&#xff0c;并为每一行分配一个排名值&#xff08;1,2,3,...&#xff09;。 函…

【漏洞复现】网康科技 NS-ASG 应用安全网关 SQL注入漏洞(CVE-2024-2330)

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

CoSaMP算法实现压缩感知的MATLAB实现

CoSaMP(Compressive Sampling Matching Pursuit)算法是一种基于稀疏表示的信号重构算法,它是对OMP(Orthogonal Matching Pursuit)算法的改进。CoSaMP算法在信号重构和压缩感知领域有着广泛的应用,特别是在处理稀疏信号时表现出色。 基本定义 下面是对CoSaMP算法的详细解…

(自用笔记)每天一点vue3—— echarts横坐标刻度标签显示不完全的问题

我是想做一个vue3echarts的账单数据展示项目&#xff0c;因为有vue2的基础&#xff0c;打算直接在这个项目上熟悉掌握vue3的新特性。这系列笔记就按照遇见问题解决问题的思路更新&#xff0c;不按照官方快速上手的章节&#xff0c;特此说明。 echarts 上次遗留一个横坐标刻度标…

strcat函数

函数理解记忆&#xff1a;str表示是<string.g>中的函数&#xff0c;cat表示附加。意思是将一个字符串的内容附加到另一个字符串的末尾。 注意要点&#xff1a;既然要附加&#xff0c;附加的字符串和被附加的字符串都要有\0。否则不知道附加多少&#xff0c;不知附加在哪…

Jtti:如何在CentOS中安装和配置Tomcat应用服务器

在CentOS中安装和配置Tomcat应用服务器非常简单&#xff0c;以下是一种基本的步骤&#xff1a; 步骤 1: 安装Java 首先&#xff0c;确保您的系统上已经安装了Java Development Kit (JDK)。Tomcat需要Java环境来运行。 sudo yum install java-devel 步骤 2: 下载和解压Tomcat 访…

免费视频背景素材下载

找免费视频素材、背景就上这6个网站&#xff0c;高质量&#xff0c;无版权可商用。 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYwNDUx 菜鸟图库虽然是个设计素材网站&#xff0c;但除了设计类素材之外还有很多视频、音频、办公类等素材&#xff0c;视频素材就有上…

前端实现复制粘贴功能

在前端开发的世界里&#xff0c;复制粘贴功能就像是那个总是被忽视&#xff0c;却在关键时刻能救你一命的老朋友。我们习惯了用那些古老的魔法咒语&#xff08;document.execCommand(copy)&#xff09;来实现这一功能&#xff0c;但时代在进步&#xff0c;技术在更新&#xff0…

Frostmourne - Elasticsearch源日志告警配置

简介 配置Frostmourne 接入Elasticsearch源进行日志匹配告警&#xff0c;并静默规则&#xff0c;告警消息发送到企业微信&#xff0c;告警信息使用Markdown。 部署安装教程查看&#xff1a; https://songxwn.com/frostmourne_install ELK 安装教程&#xff1a;https://songx…

[C/C++] -- 双指针

1.简介 双指针技巧是一种常见的算法解题方法&#xff0c;通过使用两个指针在数据结构上同时移动&#xff0c;可以解决多种问题。这种技巧通常适用于数组、字符串和链表等数据结构&#xff0c;下面我将详细介绍双指针技巧的特点和应用场景&#xff1a; 特点&#xff1a; 快慢…

综合实验---Web环境搭建

题目&#xff1a; 服务器IP地址规划&#xff1a;client&#xff1a;12.0.0.12/24&#xff0c;网关服务器&#xff1a;ens36:12.0.0.1/24、ens33&#xff1a;192.168.10.1/24&#xff0c;Web1&#xff1a;192.168.10.10/24&#xff0c;Web2&#xff1a;192.168.10.20/24&#xf…