AIGC标识 set详细使用教程

C++17 std::set 使用说明

1. 概述

std::set 是 C++ 标准库中的关联容器,用于存储唯一键的集合,元素按特定顺序自动排序。

  • 头文件:#include <set>
  • 底层实现:通常为红黑树(平衡二叉搜索树),但标准未强制。
  • 元素唯一性:同一键只能出现一次。
  • 元素有序性:按比较器 Compare 排序,默认 std::less<Key>。
  • 元素不可修改:不能通过迭代器修改元素值,因为会破坏排序。
  • 迭代器类型:双向迭代器,且为只读迭代器。

2. 模板定义

template<
    class Key,
    class Compare = std::less<Key>,
    class Allocator = std::allocator<Key>
> class set;
  • Key:存储的元素类型。
  • Compare:比较器类型,必须满足严格弱序。
  • Allocator:分配器类型,默认 std::allocator<Key>。

3. 常用成员类型

类型 说明
key_type Key
value_type Key
size_type 无符号整数类型
difference_type 有符号整数类型
key_compare Compare
value_compare 比较两个 value_type 的函数对象
allocator_type Allocator
reference const value_type&
const_reference const value_type&
pointer const value_type*
const_pointer const value_type*
iterator 只读双向迭代器
const_iterator 只读双向迭代器
reverse_iterator 反向只读迭代器
const_reverse_iterator 反向只读迭代器
node_type C++17 节点句柄类型
insert_return_type C++17 插入节点句柄的返回类型

4. 构造与初始化

std::set<int> s1;                          // 空 set
std::set<int> s2 = {3, 1, 4, 1, 5};        // 初始化列表,去重并排序:1,3,4,5
std::set<int> s3(s2);                      // 拷贝构造
std::set<int> s4(std::move(s2));           // 移动构造
std::set<int> s5(s3.begin(), s3.end());    // 范围构造
std::set<int, std::greater<int>> s6 = {1, 2, 3}; // 自定义比较器,降序:3,2,1

C++17 支持类模板参数推导:

std::set s{1, 2, 3};        // 推导为 std::set<int>
std::set s2(s.begin(), s.end()); // 推导为 std::set<int>

5. 迭代器

std::set<int> s = {1, 2, 3};

for (auto it = s.begin(); it != s.end(); ++it) {
    std::cout << *it << ' ';
}

for (int x : s) {
    std::cout << x << ' ';
}

// 反向遍历
for (auto it = s.rbegin(); it != s.rend(); ++it) {
    std::cout << *it << ' ';
}
  • begin(), end(), cbegin(), cend()
  • rbegin(), rend(), crbegin(), crend()
  • 迭代器为只读,不能通过 *it = value 修改元素。

6. 容量

s.empty();    // 是否为空
s.size();     // 元素个数
s.max_size(); // 最大可容纳元素数

7. 修改器

操作 说明
clear() 清空所有元素
insert(value) 插入元素,返回 pair<iterator, bool>
insert(hint, value) 带提示插入,返回 iterator
insert(first, last) 范围插入,返回 void
insert(ilist) 初始化列表插入,返回 void
emplace(args...) 就地构造,返回 pair<iterator, bool>
emplace_hint(hint, args...) 带提示就地构造,返回 iterator
erase(iterator) 删除指定元素,返回下一个迭代器
erase(key) 删除键为 key 的元素,返回删除个数(0 或 1)
erase(first, last) 删除范围,返回下一个迭代器
swap(other) 交换两个 set
extract(key) C++17 提取节点,返回 node_type
extract(iterator) C++17 提取节点,返回 node_type
merge(source) C++17 合并另一个 set,冲突键不合并
insert(node_type&&) C++17 插入节点句柄,返回 insert_return_type
insert(hint, node_type&&) C++17 带提示插入节点句柄,返回 iterator

插入示例

std::set<int> s;

auto [it1, ok1] = s.insert(10);   // C++17 结构化绑定
s.emplace(20);
s.insert({30, 40, 50});           // 初始化列表

if (auto [it, ok] = s.insert(10); !ok) {
    std::cout << "10 已存在,位置:" << *it << '\n';
}

删除示例

std::set<int> s = {1, 2, 3, 4, 5};

s.erase(3);                       // 按 key 删除
s.erase(s.begin());               // 按迭代器删除
s.erase(s.find(4), s.end());      // 按范围删除

8. 查找

操作 说明
count(key) 返回键为 key 的元素个数(0 或 1)
find(key) 返回指向 key 的迭代器,找不到返回 end()
lower_bound(key) 返回第一个 不小于 key 的元素
upper_bound(key) 返回第一个 大于 key 的元素
equal_range(key) 返回 pair<lower_bound, upper_bound>
std::set<int> s = {1, 3, 5, 7, 9};

if (s.find(5) != s.end()) {
    std::cout << "找到 5\n";
}

// C++17 if 初始化
if (auto it = s.find(5); it != s.end()) {
    std::cout << "找到:" << *it << '\n';
}

auto lb = s.lower_bound(5); // 指向 5
auto ub = s.upper_bound(5); // 指向 7

注意:C++17 没有 contains(),该函数是 C++20 引入的。C++17 中判断存在性请使用 find() != end() 或 count() > 0。

9. 观察器

auto cmp = s.key_comp();    // 返回 key 比较器
auto vcmp = s.value_comp(); // 返回 value 比较器

10. C++17 新特性

10.1 节点句柄 extract

extract 可以将元素从 set 中“提取”出来,返回一个 node_type。节点脱离容器后,可以修改其值,再重新插入。

std::set<int> s = {10, 20, 30};

auto node = s.extract(20);
if (!node.empty()) {
    node.value() = 25;          // 修改键值
    s.insert(std::move(node));  // 重新插入
}

// s: 10, 25, 30

10.2 合并 merge

merge 将另一个 set 中的节点移动到当前 set,不会复制元素。遇到相同键时,冲突元素保留在源 set 中。

std::set<int> a = {1, 2, 3};
std::set<int> b = {3, 4, 5};

a.merge(b);

// a: 1, 2, 3, 4, 5
// b: 3   (冲突元素保留)

10.3 insert_return_type

插入节点句柄时返回 insert_return_type,包含:

struct insert_return_type {
    bool inserted;      // 是否插入成功
    iterator position;  // 插入位置或阻止插入的位置
    node_type node;     // 未插入的节点(失败时)
};
std::set<int> s = {1, 2, 3};
std::set<int> other = {3, 4};

auto node = other.extract(4);
auto ret = s.insert(std::move(node));

if (ret.inserted) {
    std::cout << "插入成功:" << *ret.position << '\n';
} else {
    std::cout << "插入失败,冲突键:" << *ret.position << '\n';
}

10.4 推导指引

C++17 为 std::set 提供了推导指引,可省略模板参数:

std::set s{1, 2, 3};                 // std::set<int>
std::set s2(s.begin(), s.end());     // std::set<int>

11. 自定义比较器与自定义类型

11.1 自定义比较器

#include <functional>

std::set<int, std::greater<int>> desc = {1, 2, 3};
// 遍历顺序:3, 2, 1

使用 Lambda:

auto cmp = [](int a, int b) { return a > b; };
std::set<int, decltype(cmp)> s(cmp);

11.2 自定义类型

方式一:重载 operator<

#include <tuple>
#include <string>

struct Person {
    int age;
    std::string name;

    bool operator<(const Person& other) const {
        return std::tie(age, name) < std::tie(other.age, other.name);
    }
};

std::set<Person> people;
people.insert({25, "Alice"});
people.insert({20, "Bob"});

方式二:提供比较器

struct PersonCmp {
    bool operator()(const Person& a, const Person& b) const {
        if (a.age != b.age) return a.age < b.age;
        return a.name < b.name;
    }
};

std::set<Person, PersonCmp> people;

要求:比较器必须满足严格弱序,否则行为未定义。

12. 复杂度与迭代器失效

操作 复杂度
插入 O(log n)
删除 O(log n)
查找 O(log n)
遍历 O(n)
begin / end O(1)
size / empty O(1)

迭代器失效规则:

  • insert / emplace:不会使任何迭代器失效。
  • erase:只使指向被删除元素的迭代器失效。
  • extract:使指向被提取元素的迭代器失效,其他迭代器不受影响。
  • merge:指向被移动元素的迭代器仍然有效,但现在指向目标容器中的元素。
  • swap:迭代器仍然有效,但 end() 迭代器可能失效。
  • clear:使所有迭代器失效。

13. 与 std::unordered_set 对比

特性 std::set std::unordered_set
排序 有序 无序
底层结构 红黑树 哈希表
查找 O(log n) 平均 O(1),最坏 O(n)
插入 O(log n) 平均 O(1),最坏 O(n)
删除 O(log n) 平均 O(1),最坏 O(n)
迭代器 双向 前向
需要 比较器 哈希函数 + 相等比较
内存 通常较低 通常较高
迭代器稳定性 插入不失效 插入可能 rehash 失效

14. 完整示例

#include <iostream>
#include <set>
#include <string>
#include <functional>
#include <tuple>

struct Point {
    int x, y;

    bool operator<(const Point& other) const {
        return std::tie(x, y) < std::tie(other.x, other.y);
    }
};

int main() {
    // 1. 基本使用
    std::set<int> s = {3, 1, 4, 1, 5};
    s.insert(2);
    s.emplace(6);

    auto [it, ok] = s.insert(7); // C++17 结构化绑定
    if (ok) {
        std::cout << "插入成功: " << *it << '\n';
    }

    // 2. 查找
    if (auto it = s.find(3); it != s.end()) { // C++17 if 初始化
        std::cout << "找到: " << *it << '\n';
    }

    // 3. 删除
    s.erase(1);
    s.erase(s.begin());

    // 4. 遍历
    for (int x : s) {
        std::cout << x << ' ';
    }
    std::cout << '\n';

    // 5. 自定义比较器
    std::set<int, std::greater<int>> desc = {1, 2, 3};
    for (int x : desc) {
        std::cout << x << ' ';
    }
    std::cout << '\n';

    // 6. C++17 extract 修改元素
    std::set<int> s2 = {10, 20, 30};
    auto node = s2.extract(20);
    node.value() = 25;
    s2.insert(std::move(node));

    for (int x : s2) {
        std::cout << x << ' ';
    }
    std::cout << '\n';

    // 7. C++17 merge
    std::set<int> a = {1, 2, 3};
    std::set<int> b = {3, 4, 5};
    a.merge(b);

    std::cout << "a: ";
    for (int x : a) std::cout << x << ' ';
    std::cout << "\nb: ";
    for (int x : b) std::cout << x << ' ';
    std::cout << '\n';

    // 8. 自定义类型
    std::set<Point> points;
    points.insert({1, 2});
    points.insert({1, 1});

    for (const auto& p : points) {
        std::cout << "(" << p.x << "," << p.y << ") ";
    }
    std::cout << '\n';

    return 0;
}

15. 注意事项

  1. 不能通过迭代器修改元素:std::set 的元素是常量,*it = value 会编译错误。
  2. 插入不会使迭代器失效,删除只使被删除元素的迭代器失效。
  3. C++17 没有 contains(),使用 find() != end() 判断存在性。
  4. C++17 没有 erase_if(),该函数是 C++20 引入的。
  5. 自定义比较器必须满足严格弱序。
  6. 如果比较器认为两个元素等价,则 set 中只保留一个。
  7. 使用 Lambda 作为比较器时,需要指定类型:std::set<int, decltype(cmp)> s(cmp);
  8. extract 后可以修改节点值,但重新插入时若键冲突会插入失败,节点句柄保留。
  9. merge 不会复制元素,只移动节点;冲突元素保留在源容器中。
  10. std::set 不支持 operator[],因为键即值。

16. 总结

std::set 是 C++17 中非常常用的有序唯一集合容器。它基于红黑树实现,提供 O(log n) 的插入、删除和查找。C++17 引入的节点句柄(extract / merge)极大地增强了 set 的灵活性,允许在不复制元素的情况下移动节点,甚至修改原本不可变的键。使用时注意元素不可直接修改,以及自定义比较器必须满足严格弱序。

\[{\tiny 本文AI贡献大于人类} \]

posted @ 2026-09-24 17:01  wmq2012  阅读(23)  评论(0)    收藏  举报