multiset 与 set 在删除、查找、迭代器行为上存在本质差异:erase(key) 删除所有匹配元素而非一个;count() 返回总数,find() 返回首个迭代器;insert() 总成功且仅返回 iterator;迭代器仅在被删元素上失效。

multiset 不是“能用就行”的容器,它在删除、查找、迭代器行为上和 set 有本质差异,用错一个接口就可能删掉全部重复元素或遍历跳过部分值。
multiset::erase(key) 会删光所有相同元素,不是只删一个
这是最常踩的坑:调用 erase("apple") 不是删掉第一个 "apple",而是把容器里所有 "apple" 全干掉,返回值是删除个数(size_t),不是迭代器。
- 想只删一个?必须用
erase(iterator),先find()或lower_bound()拿到单个位置再删 - 想删指定数量?得手动循环:比如删 2 个 "apple",要
auto it = s.find("apple"); if (it != s.end()) { s.erase(it); }执行两次 - 误用
erase(key)在日志频次统计中会导致“某 IP 访问 10 次,删一次就全没了”,结果漏报
count() 和 find() 返回值语义完全不同
count(key) 返回的是匹配元素总个数(size_t),而 find(key) 只返回第一个匹配项的迭代器(iterator),找不到则返回 end()。两者不能混用判断“是否存在”。
- 用
if (s.count(x))判断存在性没问题,但效率略低(O(log n) + O(1) 遍历计数);if (s.find(x) != s.end())更直接,且能顺便拿到迭代器复用 -
count()对自定义类型开销更大——它内部要走equal_range算区间长度,不是简单计数 - 注意:
count()返回 0 或正整数,永远不返回负数或 bool,别拿来当条件直接赋值给bool变量(虽能编译,但语义模糊)
插入重复元素时 insert() 返回值不是“是否成功”
multiset::insert() 总是成功,且返回 iterator(指向新插入元素),不像 set::insert() 返回 pair<iterator bool></iterator> 告诉你“有没有插入”。这是设计使然:multiset 本就不拒绝重复。
立即学习“C++免费学习笔记(深入)”;
- 不要写
if (s.insert(x).second) { ... }——multiset::insert()返回值根本没有.second字段,编译不过 - 批量插入用
insert(first, last)或初始化列表,比循环调用单元素insert()更高效(减少树平衡次数) - 若需知道插入后该值总共有几个,得立刻跟一句
s.count(x),不能依赖返回值
迭代器失效规则比 vector 严格,但比 unordered_set 温和
multiset 的迭代器只在**被删除的那个元素**上失效,其余所有迭代器(包括 end())都保持有效。这点和 vector 完全不同,也比 unordered_set 更可靠。
- 安全做法:删元素前先保存下一个位置,比如
auto next = std::next(it); s.erase(it); it = next; - 千万别在 for 循环里边遍历边
erase(it)还继续++it——it已失效,行为未定义 - 插入操作完全不影响已有迭代器,可以放心在遍历时插入新元素
真正难的不是记住语法,而是理解红黑树如何让“允许重复”和“保持有序”共存——它靠的是把相同值的节点按插入顺序链成右斜链,而不是合并。这意味着 lower_bound 和 upper_bound 的边界差才是 count() 的真实来源,也是你做范围统计时唯一可信的依据。


















