跳到主要内容

C++ 比较函数对象

在C++标准模板库(STL)中,比较函数对象是一类特殊的函数对象,专门用于执行元素之间的比较操作。这些函数对象在STL算法、容器和各种数据结构中扮演着重要角色,特别是在排序、搜索和元素关系判断等场景下。

什么是比较函数对象?

比较函数对象是一种特殊的函数对象,它接受两个参数,并返回一个布尔值,表示两个参数之间的比较结果。在STL中,比较函数对象主要用于确定元素之间的顺序关系。

备注

函数对象回顾:函数对象(Functor)是可以像函数一样被调用的对象,它实现了operator()操作符。

STL中的预定义比较函数对象

STL在<functional>头文件中定义了六种基本的比较函数对象:

  1. std::less<T> - 小于比较,等价于操作符 <
  2. std::less_equal<T> - 小于等于比较,等价于操作符 <=
  3. std::greater<T> - 大于比较,等价于操作符 >
  4. std::greater_equal<T> - 大于等于比较,等价于操作符 >=
  5. std::equal_to<T> - 等于比较,等价于操作符 ==
  6. std::not_equal_to<T> - 不等于比较,等价于操作符 !=

让我们通过一个简单的例子来看看这些比较函数对象的基本用法:

cpp
#include <iostream>
#include <functional>

int main() {
// 创建比较函数对象实例
std::less<int> less_than;
std::greater<int> greater_than;
std::equal_to<int> equals;

// 使用函数对象进行比较
std::cout << "10 < 20: " << less_than(10, 20) << std::endl; // 输出:1 (true)
std::cout << "10 > 20: " << greater_than(10, 20) << std::endl; // 输出:0 (false)
std::cout << "10 == 10: " << equals(10, 10) << std::endl; // 输出:1 (true)

return 0;
}

输出:

10 < 20: 1
10 > 20: 0
10 == 10: 1

在STL算法中使用比较函数对象

比较函数对象最常见的用途之一是在STL算法中定制排序准则。以下是几个例子:

示例1:对vector进行自定义排序

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9};

// 使用greater<int>进行降序排序
std::sort(numbers.begin(), numbers.end(), std::greater<int>());

std::cout << "降序排序结果: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;

// 使用less<int>进行升序排序
std::sort(numbers.begin(), numbers.end(), std::less<int>());

std::cout << "升序排序结果: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;

return 0;
}

输出:

降序排序结果: 9 8 5 2 1 
升序排序结果: 1 2 5 8 9

示例2:自定义比较函数对象

有时STL提供的比较函数对象无法满足我们的需求,这时可以自定义比较函数对象:

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

// 自定义比较函数对象,按字符串长度排序
class CompareByLength {
public:
bool operator()(const std::string& a, const std::string& b) const {
return a.length() < b.length();
}
};

int main() {
std::vector<std::string> words = {"apple", "banana", "pear", "strawberry", "fig"};

// 使用自定义比较函数对象按字符串长度排序
std::sort(words.begin(), words.end(), CompareByLength());

std::cout << "按长度排序结果: ";
for (const std::string& word : words) {
std::cout << word << " ";
}
std::cout << std::endl;

return 0;
}

输出:

按长度排序结果: fig pear apple banana strawberry 

Lambda表达式作为比较函数对象

在C++11及以后的版本中,我们可以使用lambda表达式作为比较函数对象,这使得代码更加简洁:

cpp
#include <iostream>
#include <vector>
#include <algorithm>

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

Person(std::string n, int a) : name(n), age(a) {}
};

int main() {
std::vector<Person> people = {
Person("Alice", 25),
Person("Bob", 30),
Person("Charlie", 22)
};

// 按年龄升序排序
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
});

std::cout << "按年龄排序结果:" << std::endl;
for (const Person& p : people) {
std::cout << p.name << ": " << p.age << std::endl;
}

// 按姓名字母顺序排序
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.name < b.name;
});

std::cout << "\n按姓名排序结果:" << std::endl;
for (const Person& p : people) {
std::cout << p.name << ": " << p.age << std::endl;
}

return 0;
}

输出:

按年龄排序结果:
Charlie: 22
Alice: 25
Bob: 30

按姓名排序结果:
Alice: 25
Bob: 30
Charlie: 22

在关联容器中使用比较函数对象

STL中的关联容器(如std::setstd::map等)默认使用std::less<T>作为比较函数对象。我们可以通过提供自定义的比较函数对象来改变容器中元素的排序方式:

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

// 自定义比较函数对象,不区分大小写比较字符串
struct CaseInsensitiveLess {
bool operator()(const std::string& a, const std::string& b) const {
auto to_lower = [](char c) { return std::tolower(c); };

for (size_t i = 0; i < std::min(a.length(), b.length()); ++i) {
char a_lower = to_lower(a[i]);
char b_lower = to_lower(b[i]);
if (a_lower < b_lower) return true;
if (a_lower > b_lower) return false;
}

return a.length() < b.length();
}
};

int main() {
// 创建一个使用自定义比较函数对象的set
std::set<std::string, CaseInsensitiveLess> names;

// 插入一些元素
names.insert("Apple");
names.insert("banana");
names.insert("CHERRY");
names.insert("Date");

// 打印集合中的元素
std::cout << "不区分大小写排序的结果:" << std::endl;
for (const auto& name : names) {
std::cout << name << std::endl;
}

return 0;
}

输出:

不区分大小写排序的结果:
Apple
banana
CHERRY
Date

实际应用案例

案例:自定义学生对象排序

假设我们有一个学生管理系统,需要根据不同的条件(如学号、姓名、分数等)对学生进行排序:

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

class Student {
public:
int id;
std::string name;
double score;

Student(int i, std::string n, double s) : id(i), name(n), score(s) {}
};

// 按学号排序
struct CompareById {
bool operator()(const Student& a, const Student& b) const {
return a.id < b.id;
}
};

// 按姓名排序
struct CompareByName {
bool operator()(const Student& a, const Student& b) const {
return a.name < b.name;
}
};

// 按分数排序(降序)
struct CompareByScore {
bool operator()(const Student& a, const Student& b) const {
return a.score > b.score; // 注意这里是降序
}
};

int main() {
std::vector<Student> students = {
Student(103, "Smith", 85.5),
Student(101, "Johnson", 92.0),
Student(105, "Williams", 78.5),
Student(102, "Brown", 90.0),
Student(104, "Jones", 88.0)
};

// 按学号排序
std::sort(students.begin(), students.end(), CompareById());
std::cout << "按学号排序:" << std::endl;
for (const auto& s : students) {
std::cout << "ID: " << s.id << ", 姓名: " << s.name
<< ", 分数: " << s.score << std::endl;
}

std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;

// 按姓名排序
std::sort(students.begin(), students.end(), CompareByName());
std::cout << "按姓名排序:" << std::endl;
for (const auto& s : students) {
std::cout << "ID: " << s.id << ", 姓名: " << s.name
<< ", 分数: " << s.score << std::endl;
}

std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;

// 按分数排序(降序)
std::sort(students.begin(), students.end(), CompareByScore());
std::cout << "按分数排序(降序):" << std::endl;
for (const auto& s : students) {
std::cout << "ID: " << s.id << ", 姓名: " << s.name
<< ", 分数: " << s.score << std::endl;
}

return 0;
}

输出:

按学号排序:
ID: 101, 姓名: Johnson, 分数: 92
ID: 102, 姓名: Brown, 分数: 90
ID: 103, 姓名: Smith, 分数: 85.5
ID: 104, 姓名: Jones, 分数: 88
ID: 105, 姓名: Williams, 分数: 78.5

--------------------------------------------------

按姓名排序:
ID: 102, 姓名: Brown, 分数: 90
ID: 101, 姓名: Johnson, 分数: 92
ID: 104, 姓名: Jones, 分数: 88
ID: 103, 姓名: Smith, 分数: 85.5
ID: 105, 姓名: Williams, 分数: 78.5

--------------------------------------------------

按分数排序(降序):
ID: 101, 姓名: Johnson, 分数: 92
ID: 102, 姓名: Brown, 分数: 90
ID: 104, 姓名: Jones, 分数: 88
ID: 103, 姓名: Smith, 分数: 85.5
ID: 105, 姓名: Williams, 分数: 78.5

案例:定制优先队列的比较函数

另一个常见的应用是为std::priority_queue定制比较函数对象,以实现自定义的优先级规则:

cpp
#include <iostream>
#include <queue>
#include <vector>
#include <string>

struct Task {
std::string name;
int priority;

Task(std::string n, int p) : name(n), priority(p) {}
};

// 比较函数对象,优先级高的任务先执行
struct TaskComparator {
bool operator()(const Task& a, const Task& b) const {
// 注意:priority_queue默认使用less的相反逻辑
// 返回true表示a的优先级低于b
return a.priority < b.priority;
}
};

int main() {
// 创建一个使用自定义比较函数对象的优先队列
std::priority_queue<Task, std::vector<Task>, TaskComparator> taskQueue;

// 添加任务
taskQueue.push(Task("发送邮件", 2));
taskQueue.push(Task("系统备份", 5));
taskQueue.push(Task("文件压缩", 1));
taskQueue.push(Task("紧急修复", 10));
taskQueue.push(Task("数据分析", 3));

// 按优先级顺序处理任务
std::cout << "任务处理顺序:" << std::endl;
while (!taskQueue.empty()) {
Task currentTask = taskQueue.top();
std::cout << "处理任务:" << currentTask.name
<< "(优先级:" << currentTask.priority << ")" << std::endl;
taskQueue.pop();
}

return 0;
}

输出:

任务处理顺序:
处理任务:紧急修复(优先级:10)
处理任务:系统备份(优先级:5)
处理任务:数据分析(优先级:3)
处理任务:发送邮件(优先级:2)
处理任务:文件压缩(优先级:1)

总结

通过本文的学习,我们了解了C++中比较函数对象的概念、种类以及如何在STL中使用它们。比较函数对象是STL的重要组成部分,它们为容器和算法提供了灵活的比较机制,使我们能够根据不同的需求自定义元素的排序规则。

主要要点回顾:

  1. STL提供了六种基本的比较函数对象:lessless_equalgreatergreater_equalequal_tonot_equal_to
  2. 我们可以自定义比较函数对象,只需要实现operator()操作符函数。
  3. 在C++11及以后的版本中,lambda表达式可以作为比较函数对象使用。
  4. 比较函数对象广泛应用于STL算法(如排序)和关联容器中。
  5. 实际应用中,比较函数对象可以帮助我们实现复杂的排序逻辑和数据结构。

练习

为了巩固所学知识,建议尝试以下练习:

  1. 创建一个自定义的比较函数对象,按照字符串中的数字大小(而不是字典序)对字符串进行排序。例如:"file1.txt", "file10.txt", "file2.txt" 应该排序为 "file1.txt", "file2.txt", "file10.txt"。

  2. 实现一个使用自定义比较函数对象的std::map,其中键是不区分大小写的字符串。

  3. 创建一个比较函数对象,按照自定义的多条件规则对学生进行排序(例如,先按照成绩降序排列,成绩相同时按照名字字母顺序排列)。

  4. 使用lambda表达式作为比较函数,对一个包含一对整数(表示二维平面上的点)的vector进行排序,排序规则是先按照到原点的距离,然后按照X坐标,最后按照Y坐标。

通过这些练习,你将能够更好地掌握比较函数对象在C++中的应用,为你的编程工具箱增添有力的工具。