C++ STL set
与 multiset
高级用法详解:自定义排序与仿函数
概述
本文将深入探讨 C++ STL 中 set
和 multiset
容器的高级用法,重点讲解自定义排序规则和**函数对象(仿函数)**的应用。通过分析提供的代码示例,我们将全面理解这些高级特性。
核心知识点
1. 自定义排序规则
set
和 multiset
默认使用 less<T>
进行比较,产生升序排列。我们可以通过模板参数指定比较方式:
// 默认升序排列(等同于 set<int, less<int>>)
set<int> set1;
// 使用 greater<int> 实现降序排列
set<int, greater<int>> set1;
less<T>
:产生升序排列(默认)greater<T>
:产生降序排列- 这些是函数对象(也称为仿函数)
2. 自定义类在 set 中的使用
当 set
中存储自定义类时,需要提供比较方式。有两种方法:
方法一:重载 <
运算符
class student {
public:
// ... 其他代码 ...
bool operator<(const student& right) const {
return this->age > right.age; // 实现降序排列
}
};
- 必须声明为
const
成员函数 - 参数应为
const
引用 - 返回
bool
值表示比较结果
方法二:使用自定义比较类(仿函数)
class FunStudent {
public:
bool operator()(const student& left, const student& right) const {
cout << "调用了 Funstudent" << endl;
return left.getAge() < right.getAge();
}
};
// 使用自定义比较类
set<student, FunStudent> setStu;
- 必须重载
operator()
- 应该是无状态的(通常声明为
const
成员函数) - 可以包含调试输出等额外逻辑
3. 仿函数(函数对象)详解
仿函数是行为类似函数的对象,关键特点:
- 通过重载
operator()
实现 - 可以拥有状态(成员变量)
- 比普通函数更灵活
- 常用于 STL 算法的定制行为
// 创建仿函数对象
FunStudent funStu;
// 像函数一样调用
bool ret = funStu(liXiaoHua, wangDaChui);
代码解析
1. 基本 set 操作
set<int> setInt;
for (int i = 5; i > 0; i--) {
set1.insert(i); // 插入元素
}
// 遍历输出
for (auto it = set1.begin(); it != set1.end(); ++it) {
cout << *it << " ";
}
- 无论插入顺序如何,
set
会自动排序 - 使用迭代器遍历时按排序顺序访问
2. 自定义排序的 set
set<int, greater<int>> set1; // 降序排列
set1.insert(5); set1.insert(1); set1.insert(3);
// 输出顺序将是 5 3 1
3. 存储自定义类
set<student, FunStudent> setStu;
setStu.insert(student(18));
setStu.insert(student(19));
// 输出时将按照 FunStudent 定义的规则排序
关键区别:set vs multiset
特性 | set | multiset |
---|---|---|
元素唯一性 | 唯一 | 允许重复 |
插入效率 | 检查唯一性,稍慢 | 直接插入,较快 |
内存使用 | 较少 | 可能较多 |
查找性能 | O(log n) | O(log n) |
性能考虑
- 插入性能:
multiset
通常比set
快,因为不需要检查唯一性 - 查找性能:两者相同,都是基于红黑树的 O(log n) 查找
- 内存占用:
multiset
可能占用更多内存,因为允许重复元素
实际应用建议
- 需要唯一元素:选择
set
- 允许重复元素:选择
multiset
- 自定义排序:
- 简单情况:重载
<
运算符 - 复杂情况:使用自定义比较类
- 简单情况:重载
- 性能敏感场景:
- 考虑
unordered_set
如果不需要排序 - 对于大量数据,注意红黑树的平衡性
- 考虑
完整代码总结
#include <set>
#include <iostream>
using namespace std;
// 自定义类
class student {
public:
student(int age) : age(age) {}
// 方法一:重载 < 运算符
bool operator<(const student& right) const {
return this->age > right.age; // 降序
}
int getAge() const { return age; }
private:
int age;
};
// 方法二:自定义比较类
class FunStudent {
public:
bool operator()(const student& left, const student& right) const {
return left.getAge() < right.getAge(); // 升序
}
};
int main() {
// 1. 基本 set 使用
set<int, greater<int>> numSet; // 降序
for(int i=0; i<5; i++) numSet.insert(i);
// 2. 自定义类 set
set<student> set1; // 使用重载的 <
set<student, FunStudent> set2; // 使用仿函数
// 3. 仿函数演示
FunStudent comp;
bool result = comp(student(18), student(20));
return 0;
}
常见问题解答
Q1:为什么自定义比较函数要声明为 const?
A:因为 STL 容器可能在常量上下文中使用比较函数,保证线程安全和正确性。
Q2:set 和 map 的排序有什么区别?
A:set
直接对元素排序,map
对键排序。它们的底层实现类似,都是红黑树。
Q3:如何选择重载 < 还是自定义比较类?
A:
- 如果类有自然的排序规则,重载
<
- 需要多种排序方式时,使用不同的比较类
- 比较逻辑复杂时,使用比较类更清晰
通过这篇详细解析,你应该对 set
和 multiset
的高级用法有了全面理解。记住关键点:自动排序、元素唯一性、自定义比较和仿函数应用,这些是有效使用这两种容器的基础。