在 C++ STL(标准模板库)中,set 是一种非常实用的关联式容器,它基于红黑树(平衡二叉搜索树)实现,具备自动排序去重的核心特性。本文将结合具体代码示例,从基础用法、核心特性到实战场景,带你全面掌握 set 的使用。

一、set 容器的核心特性

在学习用法之前,先明确 set 的几个关键特性,这是理解其行为的基础:

  1. 自动排序set 会根据元素的默认比较规则(less<T>,即升序)对插入的元素自动排序,无需手动调用 sort
  2. 元素唯一set 不允许存储重复元素,若插入已存在的元素,会自动忽略(插入操作返回的 pair 中,second 为 false)。
  3. 基于红黑树:底层实现保证了插入、删除、查找操作的时间复杂度均为 O(log n),效率极高。
  4. 迭代器特性set 的迭代器是双向迭代器(bidirectional iterator),支持 ++-- 操作,但不支持随机访问(如 it + 1)。

二、set 基础用法实战:从代码看本质

我们先从你提供的代码入手,逐步拆解 set 的核心操作,理解每个步骤背后的逻辑。

1. 代码重现与运行结果

先看完整代码的运行效果,再逐一分析:

cpp

#include <iostream>
#include <set>
using namespace std;

void set_test()
{
    // 1. 定义一个存储 int 类型的 set
    set<int> s;

    // 2. 插入元素(自动去重+排序)
    s.insert(1);
    s.insert(8);
    s.insert(7);
    s.insert(3);
    s.insert(2);
    s.insert(1);  // 插入重复元素,会被自动忽略

    // 3. 遍历 set(输出排序后的结果)
    cout << "插入元素后,set 内容:";
    for (auto& e : s)
    {
        cout << e << " ";  // 输出:1 2 3 7 8
    }
    cout << endl;

    // 4. 查找指定元素的下界(lower_bound)
    set<int>::iterator sit = s.lower_bound(3);  // 找到第一个 >= 3 的元素(即 3)

    // 5. 区间删除(删除 [begin(), sit) 范围内的元素)
    auto it = s.begin();  // 指向第一个元素(1)
    s.erase(it, sit);     // 删除 1、2,保留 3、7、8

    // 6. 再次遍历,查看删除后的结果
    cout << "删除元素后,set 内容:";
    for (auto& e : s)
    {
        cout << e << " ";  // 输出:3 7 8
    }
}

int main()
{
    set_test();
    return 0;
}

运行结果:

plaintext

插入元素后,set 内容:1 2 3 7 8 
删除元素后,set 内容:3 7 8 

2. 关键操作解析

(1)定义与初始化

set 的定义格式为 set<元素类型> 容器名,支持多种初始化方式:

cpp

// 方式 1:默认初始化(空 set)
set<int> s1;

// 方式 2:用初始化列表初始化(C++11 及以上)
set<int> s2 = {5, 2, 9, 2};  // 自动去重+排序,结果:2 5 9

// 方式 3:用其他容器的迭代器初始化
vector<int> vec = {3, 1, 4};
set<int> s3(vec.begin(), vec.end());  // 结果:1 3 4
(2)插入操作(insert)

insert 是 set 最常用的操作之一,核心作用是 “插入元素并维持排序和去重”。它的返回值是一个 pair<iterator, bool>

  • iterator:指向插入后的元素(若元素已存在,则指向已有的那个元素)。
  • booltrue 表示插入成功,false 表示元素已存在,插入失败。

示例:

cpp

set<int> s;
// 插入单个元素
auto ret1 = s.insert(5);
cout << "插入 5:" << (ret1.second ? "成功" : "失败") << endl;  // 成功

// 插入重复元素
auto ret2 = s.insert(5);
cout << "插入 5:" << (ret2.second ? "成功" : "失败") << endl;  // 失败

// 插入多个元素(C++11 支持的初始化列表)
s.insert({3, 7, 3});  // 自动去重,结果:3 5 7
(3)遍历操作

set 支持两种遍历方式:范围 for 循环(简洁)和 迭代器遍历(灵活)。

  • 范围 for 循环(推荐,C++11 及以上):

    cpp

    for (auto& e : s)  // 用引用避免拷贝,提高效率
    {
        cout << e << " ";
    }
    
  • 迭代器遍历(适合需要灵活控制位置的场景):

    cpp

    // 正向遍历
    set<int>::iterator it = s.begin();
    while (it != s.end())
    {
        cout << *it << " ";
        ++it;  // 双向迭代器,仅支持 ++,不支持 it += 1
    }
    
    // 反向遍历(用 reverse_iterator)
    set<int>::reverse_iterator rit = s.rbegin();
    while (rit != s.rend())
    {
        cout << *rit << " ";  // 从大到小输出
        ++rit;
    }
    
(4)查找操作(find /lower_bound/upper_bound)

set 提供了多个查找相关的成员函数,比通用算法 std::find 效率更高(O (log n) vs O (n)):

函数名 功能描述 返回值
find(val) 查找值为 val 的元素 找到则返回迭代器,否则返回 end()
lower_bound(val) 查找第一个 >= val 的元素 迭代器
upper_bound(val) 查找第一个 > val 的元素 迭代器

示例(基于你代码中的 lower_bound 场景):

cpp

set<int> s = {1, 2, 3, 7, 8};

// 1. find:查找 3
auto it1 = s.find(3);
if (it1 != s.end())
{
    cout << "找到元素:" << *it1 << endl;  // 输出:3
}

// 2. lower_bound:查找第一个 >= 3 的元素(即 3)
auto it2 = s.lower_bound(3);
cout << "lower_bound(3):" << *it2 << endl;  // 输出:3

// 3. upper_bound:查找第一个 > 3 的元素(即 7)
auto it3 = s.upper_bound(3);
cout << "upper_bound(3):" << *it3 << endl;  // 输出:7
(5)删除操作(erase)

erase 支持三种删除方式,满足不同场景的需求:

  1. 删除指定元素(按值)

    cpp

    set<int> s = {1, 2, 3, 7, 8};
    s.erase(2);  // 删除值为 2 的元素,结果:1 3 7 8
    
  2. 删除指定位置的元素(按迭代器)

    cpp

    auto it = s.find(3);
    if (it != s.end())
    {
        s.erase(it);  // 删除迭代器指向的元素(3),结果:1 7 8
    }
    
  3. 删除区间内的元素(按迭代器范围):这就是你代码中用到的方式,格式为 erase(begin_it, end_it),删除 [begin_it, end_it) 范围内的元素(左闭右开):

    cpp

    set<int> s = {1, 2, 3, 7, 8};
    auto start = s.begin();       // 指向 1
    auto end = s.lower_bound(3);  // 指向 3
    s.erase(start, end);          // 删除 1、2,结果:3 7 8
    
(6)其他常用操作

除了上述核心操作,set 还有几个常用的成员函数:

cpp

set<int> s = {1, 2, 3, 7, 8};

cout << "元素个数:" << s.size() << endl;  // 输出:5
cout << "是否为空:" << (s.empty() ? "是" : "否") << endl;  // 输出:否

s.clear();  // 清空所有元素
cout << "清空后元素个数:" << s.size() << endl;  // 输出:0

三、set 的进阶特性:自定义排序规则

默认情况下,set 按 less<T> 升序排序,但实际开发中可能需要自定义排序(如降序、按字符串长度排序等)。此时可以通过 “指定比较函数” 来实现。

1. 基础类型的自定义排序(如降序)

对于 intstring 等基础类型,可直接使用 STL 提供的 greater<T> 作为比较规则:

cpp

#include <functional>  // 需包含此头文件,以使用 greater

set<int, greater<int>> s;  // 按降序排序
s.insert({1, 8, 7, 3, 2});

cout << "降序 set:";
for (auto& e : s)
{
    cout << e << " ";  // 输出:8 7 3 2 1
}

2. 自定义类型的排序(如结构体)

如果 set 存储的是自定义结构体,需要显式定义比较规则(通过函数对象或 lambda 表达式)。

示例:存储学生结构体,按成绩降序排序,成绩相同则按姓名升序排序:

cpp

#include <string>
#include <set>

// 自定义结构体
struct Student
{
    string name;
    int score;
};

// 自定义比较函数对象(按成绩降序,成绩相同按姓名升序)
struct CompareStudent
{
    bool operator()(const Student& a, const Student& b) const
    {
        if (a.score != b.score)
        {
            return a.score > b.score;  // 成绩降序
        }
        else
        {
            return a.name < b.name;  // 姓名升序
        }
    }
};

int main()
{
    set<Student, CompareStudent> s;
    s.insert({"Alice", 90});
    s.insert({"Bob", 85});
    s.insert({"Charlie", 90});  // 成绩与 Alice 相同,按姓名排序

    // 遍历输出
    for (auto& stu : s)
    {
        cout << "姓名:" << stu.name << ",成绩:" << stu.score << endl;
    }
    return 0;
}

运行结果:

plaintext

姓名:Alice,成绩:90
姓名:Charlie,成绩:90
姓名:Bob,成绩:85

四、set 的注意事项

  1. 元素不可修改set 中的元素是 “常量”,不能通过迭代器修改元素的值(因为修改会破坏红黑树的排序结构)。若需修改元素,需先删除旧元素,再插入新元素。

    cpp

    set<int> s = {1, 2, 3};
    auto it = s.find(2);
    // *it = 5;  // 错误!set 的元素不可修改
    
  2. 迭代器失效问题set 的插入、删除操作不会导致其他迭代器失效(仅被删除的迭代器失效),这是红黑树结构的特性,使用时无需过度担心迭代器失效问题

五、总结

set 是 C++ 中非常实用的容器,核心优势在于自动排序高效的插入 / 查找 / 删除,适合需要 “有序 + 唯一” 元素的场景(如统计去重后的有序数据、区间查询等)。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐