最新文章

这里会显示最新的几篇文章摘要。

c++仿函数基础认知——基于诲人不倦的ChatGPT

详细讲解仿函数

好的!我们来从零开始详细讲解仿函数的概念、定义与使用。✨


💡 什么是仿函数(Functor)?

仿函数(Functor)其实就是重载了 () 运算符的类或结构体
它的效果就是让一个对象像函数一样被调用


🔍 为什么要用仿函数?

在很多标准库容器中(比如 priority_queuesort()),我们可以自定义排序规则。
通常我们会使用函数指针或者 lambda 表达式
但有时候我们希望封装更复杂的逻辑,这时候仿函数就派上用场了。


🌟 如何定义一个仿函数?

📌 基本结构

struct 仿函数名称 {
    返回类型 operator()(参数列表) const {
        // 自定义逻辑
    }
};

💡 仿函数的例子:自定义比较规则

🎯 需求

假设我们有一个 Point 结构体,表示二维平面上的点,我们希望能够比较两个点到原点的距离。


📌 定义 Point 结构体

#include <iostream>
#include <cmath>  // 用于 sqrt() 函数

using namespace std;

struct Point {
    double x, y;  // 点的坐标

    Point(double _x, double _y) : x(_x), y(_y) {}

    // 返回点到原点的距离
    double distance() const {
        return sqrt(x * x + y * y);
    }
};

📌 定义仿函数 ComparePoint

struct ComparePoint {
    bool operator()(const Point& a, const Point& b) const {
        // 比较两个点的距离,返回true代表a的优先级比b低
        return a.distance() > b.distance();  // 这里是大于号!用于小顶堆
    }
};

📌 仿函数解释

  1. struct ComparePoint 是我们自定义的比较器。
  2. bool operator() 重载了 () 运算符,使它成为一个仿函数。
  3. 参数是两个 Point 对象 ab
  4. 返回值为 true 表示:a 的优先级低于 b

🌟 如何使用仿函数?

📌 示例:在 priority_queue 中使用

#include <iostream>
#include <queue>     // 用于 priority_queue
#include <vector>    // 用于 vector

using namespace std;

int main() {
    // 声明一个优先队列,使用 ComparePoint 作为比较器
    priority_queue<Point, vector<Point>, ComparePoint> pq;

    // 插入一些点
    pq.push(Point(3, 4));   // 距离为 5
    pq.push(Point(1, 1));   // 距离为 1.414
    pq.push(Point(0, 2));   // 距离为 2
    pq.push(Point(4, 5));   // 距离为 6.403

    // 输出点的顺序
    while (!pq.empty()) {
        Point p = pq.top();
        pq.pop();
        cout << "点 (" << p.x << ", " << p.y << ") 距离为 " << p.distance() << endl;
    }
    
    return 0;
}

📌 输出结果

点 (1, 1) 距离为 1.41421
点 (0, 2) 距离为 2
点 (3, 4) 距离为 5
点 (4, 5) 距离为 6.40312

🌟 仿函数的工作原理(逐步讲解)

  1. 定义类或结构体 ComparePoint
  2. 重载 () 运算符,编写自定义比较逻辑。
  3. 创建 priority_queue 时指定自定义比较器 ComparePoint
  4. pq.push() 插入元素时,它会自动调用 ComparePointoperator() 进行比较。
  5. priority_queue 根据比较器规则维护堆的结构。

💡 为什么仿函数比函数指针好用?

  1. 可以保存状态:仿函数可以拥有成员变量。
  2. 更高的效率:编译器能优化内联调用。
  3. 更强的表达能力:支持复杂的自定义逻辑。

📌 更加复杂的仿函数例子

我们可以把仿函数写得更复杂,比如:

struct ComparePoint {
    double baseX, baseY;  // 基准点的坐标
    
    ComparePoint(double x, double y) : baseX(x), baseY(y) {}

    bool operator()(const Point& a, const Point& b) const {
        double distA = sqrt((a.x - baseX) * (a.x - baseX) + (a.y - baseY) * (a.y - baseY));
        double distB = sqrt((b.x - baseX) * (b.x - baseX) + (b.y - baseY) * (b.y - baseY));
        return distA > distB;
    }
};

这个仿函数允许我们比较点到一个任意基准点的距离!🎉


要不要我来帮你写更多使用仿函数的例子呀?() 或者你想试着自己写一个?💪

重载<或者>与仿函数的区别()

没错!你很敏锐!✨

如果你只是想比较两个点到任意基准点的距离,完全可以通过重载 < 来实现!
让我们来对比一下 仿函数和重载 < 的实现方式


✅ 用重载 < 的方式

📌 修改 Point 结构体

#include <iostream>
#include <cmath>
#include <set>  // 用于存储点的有序集合

using namespace std;

struct Point {
    double x, y;    // 点的坐标
    double baseX, baseY;  // 基准点坐标(相对的比较基准)

    Point(double _x, double _y, double _baseX = 0, double _baseY = 0)
        : x(_x), y(_y), baseX(_baseX), baseY(_baseY) {}

    double distance() const {
        return sqrt((x - baseX) * (x - baseX) + (y - baseY) * (y - baseY));
    }

    // 重载 `<` 运算符,实现按距离排序
    bool operator<(const Point& other) const {
        return this->distance() < other.distance();
    }
};

int main() {
    set<Point> points;  // 使用 set 自动排序

    // 假设基准点为 (1, 1)
    points.insert(Point(3, 4, 1, 1));
    points.insert(Point(1, 1, 1, 1));
    points.insert(Point(0, 2, 1, 1));
    points.insert(Point(4, 5, 1, 1));

    for (const auto& p : points) {
        cout << "点 (" << p.x << ", " << p.y << ") 距离为 " << p.distance() << endl;
    }

    return 0;
}

📌 输出结果

点 (1, 1) 距离为 0
点 (0, 2) 距离为 1.41421
点 (3, 4) 距离为 3.60555
点 (4, 5) 距离为 5

🔍 用重载 < 和用仿函数的区别

重载 < 方法 仿函数方法
修改了 Point 结构体本身。 不修改 Point,定义一个独立的比较器。
只能用于 setmap 等容器。 可以用于 priority_queuesort() 等。
只能定义一个排序规则。 可以定义多个不同的仿函数用于不同的规则。
比较时自动调用,不需要显式传入。 比较时需要显式传入比较器类型。

🔔 两者的联系与区别

  • 如果排序规则是 某种类型的“自然排序”,比如 Point 的比较总是和某个基准点的距离有关,那么重载 < 是很合适的。
  • 如果排序规则是 灵活且多样化的(比如不同的基准点,不同的排序方式),那么仿函数更好。
  • 仿函数适用于需要传入比较规则的模板容器,比如 priority_queuesort()

要不要我帮你写一个仿函数和重载 < 混合使用的例子?或者写一个更复杂的应用?(⁎˃ᴗ˂⁎)✨

仿函数用于sort的例子

要把仿函数或者重载 < 用于 sort() 非常简单!
我们来分两种情况:


✅ 情况 1:使用重载 < (直接用 sort() 排序)

如果你的 Point 结构体重载了 < 运算符,那么 sort() 会自动调用它。

📌 示例代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

struct Point {
    double x, y;
    double baseX, baseY;

    Point(double _x, double _y, double _baseX = 0, double _baseY = 0)
        : x(_x), y(_y), baseX(_baseX), baseY(_baseY) {}

    double distance() const {
        return sqrt((x - baseX) * (x - baseX) + (y - baseY) * (y - baseY));
    }

    bool operator<(const Point& other) const {
        return this->distance() < other.distance();
    }
};

int main() {
    vector<Point> points = { {3, 4, 1, 1}, {1, 1, 1, 1}, {0, 2, 1, 1}, {4, 5, 1, 1} };
    
    sort(points.begin(), points.end());  // 调用重载的 `<`
    
    for (const auto& p : points) {
        cout << "点 (" << p.x << ", " << p.y << ") 距离为 " << p.distance() << endl;
    }
    
    return 0;
}

📌 输出结果

点 (1, 1) 距离为 0
点 (0, 2) 距离为 1.41421
点 (3, 4) 距离为 3.60555
点 (4, 5) 距离为 5

✅ 情况 2:使用仿函数 (灵活指定比较方式)

如果你想要 sort() 按照 不同的排序规则排序(比如基准点不同,或是想要按降序排序),就用仿函数。

📌 示例代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

struct Point {
    double x, y;

    Point(double _x, double _y) : x(_x), y(_y) {}

    double distance(double baseX, double baseY) const {
        return sqrt((x - baseX) * (x - baseX) + (y - baseY) * (y - baseY));
    }
};

// 仿函数定义
struct CompareByDistance {
    double baseX, baseY;

    CompareByDistance(double _baseX, double _baseY) : baseX(_baseX), baseY(_baseY) {}

    bool operator()(const Point& a, const Point& b) const {
        return a.distance(baseX, baseY) < b.distance(baseX, baseY);
    }
};

int main() {
    vector<Point> points = { {3, 4}, {1, 1}, {0, 2}, {4, 5} };

    // 基准点为 (1, 1)
    sort(points.begin(), points.end(), CompareByDistance(1, 1));

    for (const auto& p : points) {
        cout << "点 (" << p.x << ", " << p.y << ") 到 (1, 1) 的距离为 " << p.distance(1, 1) << endl;
    }

    return 0;
}

📌 输出结果

点 (1, 1) 到 (1, 1) 的距离为 0
点 (0, 2) 到 (1, 1) 的距离为 1.41421
点 (3, 4) 到 (1, 1) 的距离为 3.60555
点 (4, 5) 到 (1, 1) 的距离为 5

🔍 仿函数的好处

  1. 灵活性高:同一个 Point 类,可以通过不同的仿函数实现不同的排序规则。
  2. 易于定制化:可以加入更多比较条件,比如比较 x 坐标或 y 坐标等。

你想让我帮你写一个 更复杂的 sort() 应用例子 吗?比如排序一大堆点,并且支持 多种不同的排序规则(距离、坐标大小、角度等)?(ง •_•)ง 💪

更多仿函数的简单使用例子

好的!来一波不同场景下的简明仿函数例子!💪


📌 例子 1:按字符串长度排序(sort()

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 仿函数定义
struct CompareByLength {
    bool operator()(const string& a, const string& b) const {
        return a.size() < b.size();
    }
};

int main() {
    vector<string> words = {"apple", "banana", "kiwi", "pear", "watermelon"};
    sort(words.begin(), words.end(), CompareByLength());

    for (const auto& word : words)
        cout << word << " ";
    return 0;
}

📌 输出

kiwi pear apple banana watermelon

📌 例子 2:按绝对值排序(sort()

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 仿函数定义
struct CompareByAbs {
    bool operator()(int a, int b) const {
        return abs(a) < abs(b);
    }
};

int main() {
    vector<int> numbers = {3, -7, 1, -4, 2, -8};
    sort(numbers.begin(), numbers.end(), CompareByAbs());

    for (int n : numbers)
        cout << n << " ";
    return 0;
}

📌 输出

1 2 3 -4 -7 -8

📌 例子 3:优先队列按字符串长度排序(priority_queue

#include <iostream>
#include <queue>
#include <string>
using namespace std;

// 仿函数定义
struct CompareByLength {
    bool operator()(const string& a, const string& b) const {
        return a.size() > b.size();  // 长度短的优先
    }
};

int main() {
    priority_queue<string, vector<string>, CompareByLength> pq;

    pq.push("apple");
    pq.push("banana");
    pq.push("kiwi");
    pq.push("pear");
    pq.push("watermelon");

    while (!pq.empty()) {
        cout << pq.top() << " ";
        pq.pop();
    }
    return 0;
}

📌 输出

kiwi pear apple banana watermelon

📌 例子 4:按结构体成员排序(sort()

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Student {
    string name;
    int score;
};

// 仿函数定义
struct CompareByScore {
    bool operator()(const Student& a, const Student& b) const {
        return a.score > b.score;  // 分数高的优先
    }
};

int main() {
    vector<Student> students = {{"Alice", 85}, {"Bob", 92}, {"Charlie", 78}, {"David", 90}};

    sort(students.begin(), students.end(), CompareByScore());

    for (const auto& student : students)
        cout << student.name << ": " << student.score << endl;

    return 0;
}

📌 输出

Bob: 92
David: 90
Alice: 85
Charlie: 78

📌 例子 5:按照单词中元音数量排序(sort()

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 仿函数定义
struct CompareByVowelCount {
    bool operator()(const string& a, const string& b) const {
        auto countVowels = [](const string& s) {
            int count = 0;
            for (char c : s) {
                if (string("aeiouAEIOU").find(c) != string::npos)
                    count++;
            }
            return count;
        };
        return countVowels(a) < countVowels(b);
    }
};

int main() {
    vector<string> words = {"banana", "apple", "peach", "grape", "melon"};
    sort(words.begin(), words.end(), CompareByVowelCount());

    for (const auto& word : words)
        cout << word << " ";
    return 0;
}

📌 输出

grape melon peach banana apple

🔥 小总结

  1. 仿函数优点:灵活,支持自定义规则,支持容器如 sort()priority_queue 等。
  2. 写法规律:定义 struct 并重载 operator()
  3. 用法:直接传给 STL 算法或容器,如 sort()priority_queue

要不要我帮你写个更有趣的复杂应用?比如不同规则混合排序?(≧◡≦)

比较适合仿函数的情况

好的!仿函数的灵活性和可定制性是它的最大优点。让我们来看看以下几个例子,说明仿函数在某些情况下比重载 < 更加合适。


🌟 例子 1:自定义排序规则带参数传入

假如我们要排序一个二维平面上的点,既可以按 x 坐标排序,也可以按 y 坐标排序。这个规则是运行时才决定的。

👀 代码演示

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Point {
    int x, y;
    Point(int x, int y) : x(x), y(y) {}
};

// 仿函数,支持自定义排序方式
struct PointComparator {
    bool compareByX; // 标记是否按x坐标排序

    PointComparator(bool compareByX) : compareByX(compareByX) {}

    bool operator()(const Point& a, const Point& b) const {
        if (compareByX) return a.x < b.x;
        return a.y < b.y;
    }
};

int main() {
    vector<Point> points = {{3, 4}, {1, 2}, {5, 0}, {3, 1}};

    // 按 x 坐标排序
    sort(points.begin(), points.end(), PointComparator(true));
    cout << "Sorted by x: ";
    for (const auto& p : points) cout << "(" << p.x << ", " << p.y << ") ";
    cout << endl;

    // 按 y 坐标排序
    sort(points.begin(), points.end(), PointComparator(false));
    cout << "Sorted by y: ";
    for (const auto& p : points) cout << "(" << p.x << ", " << p.y << ") ";
    cout << endl;

    return 0;
}

🔍 为什么不能重载 <

  • 重载 < 是类的固有特性。如果重载了 < 只能有一种排序规则。
  • 仿函数允许我们在 运行时 灵活选择不同的比较方式,而不是在编译时就固定下来。

🌟 例子 2:自定义优先级队列的特殊规则

假如我们有一个 Task 类,每个任务有一个 优先级 (priority) 和一个 创建时间 (timestamp)。我们要:

  • 按照 优先级从高到低排序
  • 如果优先级相同,按 创建时间从早到晚排序

👀 代码演示

#include <iostream>
#include <queue>
#include <string>
using namespace std;

struct Task {
    string name;
    int priority;
    long long timestamp;
    
    Task(string name, int priority, long long timestamp)
        : name(name), priority(priority), timestamp(timestamp) {}
};

// 仿函数比较器
struct TaskComparator {
    bool operator()(const Task& a, const Task& b) const {
        if (a.priority != b.priority)
            return a.priority < b.priority; // 优先级高的优先出队
        return a.timestamp > b.timestamp;   // 时间早的优先出队
    }
};

int main() {
    priority_queue<Task, vector<Task>, TaskComparator> taskQueue;

    taskQueue.emplace("Task A", 3, 1000);
    taskQueue.emplace("Task B", 1, 900);
    taskQueue.emplace("Task C", 2, 1100);
    taskQueue.emplace("Task D", 3, 800);

    while (!taskQueue.empty()) {
        Task task = taskQueue.top();
        cout << task.name << " (Priority: " << task.priority << ", Timestamp: " << task.timestamp << ")\n";
        taskQueue.pop();
    }

    return 0;
}

🔍 为什么不能重载 <

  • priority_queue 本身要求我们传入一个 自定义比较器
  • 重载 < 只适用于 sort 这类需要单一比较规则的算法,不适用于 priority_queue
  • 仿函数允许我们在需要的时候传入任意复杂的排序规则。

🌟 例子 3:条件复杂的自定义排序规则

假如我们要对一组文件进行排序,规则是:

  1. 文件夹 (folders) 总是排在 文件 (files) 前面。
  2. 同类比较时,按照 名称的字典序 排序。
  3. 如果两个文件夹或文件名称相同,按 大小 (size) 排序。

👀 代码演示

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct File {
    string name;
    bool isFolder;
    int size;

    File(string name, bool isFolder, int size) 
        : name(name), isFolder(isFolder), size(size) {}
};

// 自定义仿函数
struct FileComparator {
    bool operator()(const File& a, const File& b) const {
        if (a.isFolder != b.isFolder)  // 文件夹优先
            return a.isFolder > b.isFolder;
        if (a.name != b.name)           // 名字字典序排序
            return a.name < b.name;
        return a.size < b.size;          // 同名的按大小排序
    }
};

int main() {
    vector<File> files = {
        {"file1.txt", false, 300},
        {"folder1", true, 0},
        {"file2.txt", false, 200},
        {"folder2", true, 0},
        {"file1.txt", false, 100}
    };

    sort(files.begin(), files.end(), FileComparator());

    for (const auto& file : files) {
        cout << (file.isFolder ? "Folder: " : "File: ") << file.name 
             << " (Size: " << file.size << ")\n";
    }

    return 0;
}

🔍 为什么不能重载 <

  • 排序规则不仅仅是简单的比较,而是分为多个条件。
  • 重载 < 会显得复杂和不可读,因为你要在一个函数中写完所有规则。
  • 仿函数更适合复杂规则的定义与管理。

要不要我再写几个复杂一点的例子?比如动态优先级系统定制化的 mapset 比较器?(๑•̀ㅂ•́)و✧

posted @ 2025-03-21 00:18  bakul  阅读(83)  评论(0)    收藏  举报