std::set通过红黑树实现自动去重和升序排序,插入重复元素静默忽略;支持自定义比较器(如降序或结构体),但排序规则必须构造时确定;emplace推荐日常使用,insert适用于已存在对象,emplace_hint需谨慎使用;纯去重用unordered_set,小数据量可选vector+sort,仅需极值考虑priority_queue。

set 自动去重和排序是怎么回事
你往 std::set 里插重复元素,它直接不收——不是报错,也不是覆盖,是静默忽略。这不是 bug,是设计:set 的底层是红黑树,每个节点的 key 必须唯一,且天然按 operator<(或自定义比较器)升序排列。
所以别写循环手动查重再插入;也别插完再调 std::sort——set 插入即有序,O(log n) 每次,总代价比先插 vector 再 sort 低得多(尤其增量插入场景)。
- 默认用
std::less<T>,也就是T::operator<,对 int、string 等内置/常见类型开箱即用 - 插入重复值(比如两次
s.insert(5))返回std::pair<iterator, bool>,.second == false表示没插进去 - 不能用下标访问(
s[0]非法),要遍历或用*s.begin()取最小值
怎么自定义排序规则(比如降序或结构体)
set 不接受「先插后排序」,排序逻辑必须在构造时定死。改规则 ≠ 改数据,得换比较器。
常见错误是以为能用 std::sort(s.begin(), s.end(), ...)——不行,set 没有随机迭代器,begin() 返回的是双向迭代器,不支持 std::sort。
立即学习“C++免费学习笔记(深入)”;
- 降序:传
std::greater<int>,如std::set<int, std::greater<int>> s; - 结构体:必须提供可调用对象(lambda、函数指针、仿函数类),且满足严格弱序。例如:
struct Person { string name; int age; }; auto cmp = [](const Person& a, const Person& b) { return a.age < b.age; }; std::set<Person, decltype(cmp)> s(cmp); - 注意:比较器对象必须能拷贝,lambda 若捕获变量(
[&x]{...})就不能用于模板参数,得用仿函数类
insert / emplace / emplace_hint 性能差别在哪
三者都保证去重,但内部动作不同:insert 是「构造再移动/拷贝」,emplace 是「就地构造」,emplace_hint 则试图用提示位置加速查找——但提示错反而更慢。
实际中,除非你刚查过某个位置(比如 find 返回的 iterator),否则别用 emplace_hint。它不是优化银弹,是特定场景的微调手段。
-
s.insert(value):适用于已存在对象,或 move-only 类型(如std::unique_ptr) -
s.emplace(args...):推荐日常使用,避免临时对象构造+移动,比如s.emplace("Alice", 28)直接构造Person -
s.emplace_hint(hint, args...):hint 应该是「大概率靠近插入位置」的 iterator,比如上一次插入返回的 it,乱传会拖慢性能
什么时候不该用 set
set 强在去重+有序,但代价是每次操作 O(log n),且内存开销比 vector 大(每个节点额外存左右子节点指针)。如果只是临时去重,之后还要随机访问或频繁修改,它可能不是最优解。
- 纯去重不要排序?用
std::unordered_set,平均 O(1) 插入,但无序 - 数据量小(< 50 个)且只插一次?vector +
std::unique+std::sort更快,cache 友好 - 需要按插入顺序遍历?set 不行,考虑
std::vector加手写去重逻辑,或用std::unordered_set记录已见元素 - 频繁查找最大/最小值?set 的
begin()/rbegin()是 O(1),但若只关心极值,std::priority_queue或 pair + 手动维护可能更轻量
最常被忽略的一点:set 迭代器失效规则和 vector 完全不同——只有删除元素会使对应迭代器失效,插入绝不会使其他迭代器失效。这点在写循环遍历时很关键,别套用 vector 的经验。







