当前位置: 首页 > article >正文

C++ 仿函数与lambda

一、使用仿函数

在 C++ 中,仿函数(Functor) 是一个类,它重载了 operator(),使得对象能够像函数一样被调用。使用仿函数的场景通常出现在需要一种 可重用且灵活的函数对象 的地方,特别是在一些复杂的算法和数据结构中。

  • 高效性:仿函数是类的对象,通常可以内联展开,而函数指针则不行,因此在一些频繁调用的场景中,仿函数效率更高。
  • 状态保存:仿函数可以保存状态。例如,如果你需要一个比较操作符,它不仅可以执行比较,还可以记录操作次数,仿函数可以在类成员变量中记录状态信息,而普通函数无法做到。
  • STL 算法(如 count_ifsorttransform 等)设计时大量依赖仿函数,通过仿函数可以使这些算法更灵活。例如,STL 的 std::lessstd::greater 等常见仿函数可以作为标准的比较操作符来传递——在 STL 中,仿函数通过统一接口的设计可以方便地组合和重用,效率高灵活性强

何时使用仿函数:

  • 复杂的逻辑或需要状态:当你需要更复杂的逻辑,或者需要在函数对象中维护某些状态时,仿函数更合适。例如,仿函数可以通过成员变量存储一些信息或进行复杂的初始化。
  • 复用与扩展:如果需要在多个地方复用相同的逻辑,或者需要定义多个成员函数以支持更多功能,那么仿函数是更好的选择。

以下是一些常见的适合使用仿函数的场景:

1. 需要一个具有状态的函数时

  • 仿函数可以保存状态信息,这意味着它们不仅执行操作,还可以在内部维护数据(例如计数器)。普通的函数和 lambda 表达式无法做到这一点,或者需要额外的外部数据来保存状态。
class CountCalls {
public:
    int operator()() {
        return ++count;
    }
private:
    int count = 0;
};

CountCalls countCalls;
std::cout << countCalls() << std::endl;  // 输出 1
std::cout << countCalls() << std::endl;  // 输出 2

这个仿函数会在每次调用时增加一个计数器,存储并管理内部的状态。

2. 将一个函数传递给算法

  • 在 C++ STL 中,很多算法(例如 std::sortstd::find_ifstd::count_if 等)需要函数或函数对象作为参数。仿函数是一种灵活的方式,它允许你在需要时携带复杂的逻辑,特别是当操作依赖于某些状态时。
class CompareGreater {
public:
    bool operator()(int a, int b) const {
        return a > b;
    }
};

std::vector<int> vec = {1, 5, 3, 9, 2};
std::sort(vec.begin(), vec.end(), CompareGreater());
for (int num : vec) {
    std::cout << num << " ";
}
// 输出: 9 5 3 2 1

3. 需要不同的操作方式

  • 使用仿函数时,可以根据需要灵活定义不同的操作。比如,多个不同的仿函数可以作为比较、操作或转换的标准传递给算法。
class MultiplyBy {
public:
    MultiplyBy(int factor) : factor(factor) {}
    int operator()(int x) const {
        return x * factor;
    }
private:
    int factor;
};

std::vector<int> vec = {1, 2, 3, 4, 5};
std::transform(vec.begin(), vec.end(), vec.begin(), MultiplyBy(10));
for (int num : vec) {
    std::cout << num << " ";  // 输出: 10 20 30 40 50
}

4. 延迟执行或动态构造函数

  • 仿函数使得你能够在运行时动态地构造函数,或者在执行时使用不同的策略进行操作。比如,根据不同的条件构造不同的仿函数。
class Power {
public:
    Power(int exponent) : exponent(exponent) {}
    int operator()(int base) const {
        return std::pow(base, exponent);
    }
private:
    int exponent;
};

std::vector<int> vec = {2, 3, 4};
std::transform(vec.begin(), vec.end(), vec.begin(), Power(3));
for (int num : vec) {
    std::cout << num << " ";  // 输出: 8 27 64
}

5. 需要一个复杂的回调机制

  • 仿函数可以用作回调函数,在一些复杂的事件驱动机制中发挥作用。例如,当你需要在某些操作完成时调用某个自定义的处理逻辑时,仿函数非常有用。
class PrintMessage {
public:
    PrintMessage(const std::string& msg) : message(msg) {}
    void operator()() const {
        std::cout << message << std::endl;
    }
private:
    std::string message;
};

PrintMessage print("Hello, world!");
print();  // 输出: Hello, world!

6. 需要与算法库中的函数接口兼容

  • 标准库的许多算法(如 std::find_if, std::sort, std::accumulate 等)需要函数对象或者可调用对象作为参数。仿函数可以在这些算法中灵活应用,且其灵活性和可组合性使得它们成为一种非常常见的选择。
std::vector<int> vec = {1, 2, 3, 4, 5};
std::cout << std::accumulate(vec.begin(), vec.end(), 0) << std::endl;  // 求和

总结

仿函数适用于以下几种场景:

  • 需要在操作中保存状态时。
  • 当需要将自定义操作传递给 STL 算法时(如 sortcount_if 等)。
  • 当你需要灵活的操作方式或者根据不同条件选择不同的行为时。
  • 当你希望能够像调用函数一样调用一个对象时,仿函数提供了很大的便利。

虽然 lambda 表达式 在 C++11 后成为了一个强大的替代选择,但在某些情况下,仿函数仍然有其独特的优势,特别是在需要状态保持、重用和性能优化的场合。

二、使用lambda 表达式

Lambda 表达式是 C++11 引入的一个特性,允许在函数内部定义匿名的内联函数。它通常用于需要小的、临时的函数对象(如回调函数、算法中的谓词等)时。

Lambda 表达式的语法:

[capture](parameter_list) -> return_type { body }
  • capture:用于捕获外部变量。
  • parameter_list:参数列表,类似于普通函数。
  • return_type:返回类型,通常可以省略,编译器会推断。
  • body:函数体。

Lambda 表达式可以访问和修改外部作用域中的变量, [] 为空,意味着不捕获外部变量。

 Lambda 表达式的简单例子:

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

int main() {
    vector<int> vec = {1, 2, 3, 4, 5};
    
    // 使用 Lambda 表达式打印所有元素
    for_each(vec.begin(), vec.end(), [](int x) {
        cout << x << " ";
    });
    
    return 0;
}

1. 排序操作

对容器中的元素进行排序并使用自定义的比较规则时,通过 Lambda 表达式直接提供比较函数,而无需单独定义一个函数。

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

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

    // 使用 Lambda 表达式按升序排序
    std::sort(nums.begin(), nums.end(), [](int a, int b) {
        return a < b;  // 返回 true 表示 a 排在 b 前面
    });

    // 输出排序后的结果
    for (int num : nums) {
        std::cout << num << " ";  // 输出:1 2 3 5 8
    }

    return 0;
}

2. 查找满足条件的元素

通过 Lambda 表达式,可以对容器进行搜索,查找符合特定条件的元素。

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

int main() {
    std::vector<int> nums = {5, 8, 2, 10, 4, 7};

    // 使用 Lambda 表达式查找第一个大于 5 的元素
    auto it = std::find_if(nums.begin(), nums.end(), [](int x) {
        return x > 5;
    });

    if (it != nums.end()) {
        std::cout << "Found: " << *it << std::endl;  // 输出:Found: 8
    }

    return 0;
}

3. 过滤容器元素

从容器中 移除或筛选 出不符合条件的元素。

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

int main() {
    std::vector<int> nums = {5, 8, 2, 10, 4, 7};

    // 使用 Lambda 表达式删除小于 5 的元素
    auto it = std::remove_if(nums.begin(), nums.end(), [](int x) {
        return x < 5;  // 删除小于 5 的元素
    });

    nums.erase(it, nums.end());  // 删除符合条件的元素

    // 输出过滤后的容器
    for (int num : nums) {
        std::cout << num << " ";  // 输出:8 10 7
    }

    return 0;
}

4. 累加器操作(如 std::accumulate

常用于累加、计算等聚合操作,可以直接传递给 std::accumulate 或类似的算法。

#include <iostream>
#include <vector>
#include <numeric>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};

    // 使用 Lambda 表达式计算容器的元素和
    int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int a, int b) {
        return a + b;  // 累加两个数
    });

    std::cout << "Sum: " << sum << std::endl;  // 输出:Sum: 15
    return 0;
}

5. 在 STL 算法中作为回调函数

用作回调函数,尤其是在 STL 算法中。可以将 Lambda 作为参数传递给算法,处理复杂的业务逻辑。

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

int main() {
    std::vector<int> nums = {10, 20, 30, 40};

    // 使用 Lambda 表达式处理每个元素(打印每个元素)
    std::for_each(nums.begin(), nums.end(), [](int x) {
        std::cout << x << " ";  // 输出:10 20 30 40
    });

    return 0;
}

6. 自定义容器操作

当需要对容器进行自定义操作时,Lambda 表达式允许灵活地传递逻辑

例如自定义的转换或修改操作:

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

int main() {
    std::vector<int> nums = {1, 2, 3, 4};

    // 使用 Lambda 表达式将每个元素乘以 2
    std::for_each(nums.begin(), nums.end(), [](int &x) {
        x *= 2;
    });

    // 输出修改后的容器
    for (int num : nums) {
        std::cout << num << " ";  // 输出:2 4 6 8
    }

    return 0;
}

7. 作为函数参数

可以将 Lambda 表达式传递给其他函数作为参数,增强代码的灵活性和可读性。

#include <iostream>
#include <vector>

void applyFunction(const std::vector<int>& nums, const std::function<void(int)>& func) {
    for (int num : nums) {
        func(num);
    }
}

int main() {
    std::vector<int> nums = {1, 2, 3, 4};

    // 将 Lambda 表达式作为函数参数传递
    applyFunction(nums, [](int x) {
        std::cout << x * x << " ";  // 输出:1 4 9 16
    });

    return 0;
}

总结

Lambda 表达式可以简化代码、提高可读性并增强函数的表达能力。常见的使用场景包括:

  • 排序、查找、过滤等 STL 算法的自定义操作。
  • 作为回调函数传递给函数。
  • 自定义容器元素操作,例如修改容器中的元素。
  • 简化临时函数对象的编写,避免过度定义类和函数。

Lambda 的优势是尤其适合  处理简单或临时的功能需求

三点 Lambda 表达式 相对于(优于) 仿函数 的使用场景

  • 短小、临时的函数对象:Lambda 表达式通常用于那些只在函数内用到的、逻辑简单的操作。比如排序、过滤、累加等操作。
  • 函数式编程:当需要将一个小的函数传递给 STL 算法时,Lambda 表达式非常合适。
  • 捕获外部变量:当需要捕获并使用外部变量时,Lambda 表达式尤其有用。它可以通过捕获列表([ ])轻松访问和修改外部变量。

 捕获外部变量例子:

Lambda 表达式(捕获外部变量):

int factor = 2;
std::vector<int> nums = {1, 2, 3, 4};
std::for_each(nums.begin(), nums.end(), [&factor](int &x) { x *= factor; });

仿函数(需要捕获外部变量):

struct MultiplyByFactor {
    int factor;
    MultiplyByFactor(int factor) : factor(factor) {}
    void operator()(int &x) const { x *= factor; }
};
std::vector<int> nums = {1, 2, 3, 4};
std::for_each(nums.begin(), nums.end(), MultiplyByFactor(factor));
//for_each 是一个 STL 算法,用于对容器中的每个元素应用一个操作
//MultiplyByFactor(factor) 是创建一个 MultiplyByFactor 仿函数对象,并传入 factor
//如果 factor 是 2,那么 MultiplyByFactor(2) 创建一个倍数为 2 的仿函数对象,operator() 就会将容器中的每个元素都乘以 2。

三、仿函数 vs Lambda 表达式

在 C++11 之前,仿函数是主要的灵活选择,而在 C++11 中引入了 lambda 表达式后,很多时候可以用 lambda 表达式来代替仿函数。不过,有些场合下仿函数仍然更灵活

  • 类型安全:在一些模板设计中,仿函数的模板类类型可以进行严格的类型检查。
  • 重用性:仿函数定义为一个类,可以在不同的算法中复用。lambda 表达式则更适合一次性、局部的操作。

语法区别:

  • 仿函数(Function Object):需要定义一个类,并在类中重载 operator()
  • Lambda 表达式:在代码中直接定义,可以作为匿名函数直接传递。

性能与灵活性:

  • 仿函数

    • 由于是一个类,可以有成员变量、构造函数和其它成员函数,因此它可以存储状态
    • 它的灵活性更高,可以在构造函数中初始化状态或接受更多的参数
  • Lambda 表达式

    • 通常用于没有复杂状态的临时函数,语法简洁
    • Lambda 表达式捕获外部变量,使得它可以在函数体内使用外部的局部变量。

捕获外部变量:

  • Lambda 表达式 可以直接捕获外部变量(通过值或引用),这使得它在需要使用外部状态时非常方便。
  • 仿函数 必须通过构造函数或其它方式传递外部参数,不能像 lambda 那样直接在函数体内捕获外部变量。

可读性与简洁性:

  • Lambda 表达式 在短小的操作中更简洁,且没有额外的类定义。
  • 仿函数 需要额外定义一个类,虽然它在更复杂的情境下(例如需要更多状态或成员函数时)表现出更多的灵活性。

总结

特性Lambda 表达式仿函数
语法简洁、内联定义,不需要定义类需要定义一个类并重载 operator()
捕获外部变量直接捕获外部变量需要通过构造函数或其它方式传递外部参数
状态适用于无状态或简单的临时状态可以有状态(成员变量)
灵活性灵活,但不如仿函数强大更强大,可以存储状态、包含多个成员函数等
性能编译器优化好,通常没有额外的性能开销可能稍微复杂一些,尤其在需要传递状态时
可读性更简洁,适合短小操作更冗长,但适用于复杂操作或需要更多灵活性时
  • Lambda 表达式 适合短小、临时的函数对象,特别是当你只需要传递一个简单的比较或操作时。
  • 仿函数 更适合需要状态、复杂逻辑或者需要复用的情况。

http://www.kler.cn/a/391046.html

相关文章:

  • Springboot 日志处理(非常详细)
  • 算法——移除链表元素(leetcode203)
  • 【JAVA】正则表达式中的中括弧
  • Vue2+3 —— Day3/4
  • 漏洞挖掘 | 某医院小程序支付漏洞+越权
  • Linux下useradd 和 adduser的区别
  • 交友系统app源码优势,怎么去开发一个交友系统,它适合的场景
  • 2445.学习周刊-2024年45周
  • 英伟达HOVER——用于人形机器人的多功能全身控制器:整合不同的控制模式且实现彼此之间的无缝切换
  • Vivado+Vscode联合打造verilog环境
  • 日常分享系列之:学习mysql-binlog-connector-java
  • Forest-HTTP客户端框架学习笔记
  • 云计算在智能交通系统中的应用
  • MySQL数据库的备份与还原
  • 动态规划习题其七【力扣】【算法学习day.29】
  • 力扣每日一题 3258. 统计满足 K 约束的子字符串数量 I
  • How to use ffmpeg to convert video format from .webm to .mp4
  • 低轨卫星互联网(二)—— 技术篇
  • 《青牛科技 GC6236:驱动芯片的卓越之选,重塑 IPcamera 和云台控制(替代 BU24036/ROHM)》
  • 第16章 SELECT 底层执行原理
  • linux详解,基本网络枚举
  • Golang | Leetcode Golang题解之第547题身份数量
  • 技术总结(二十五)
  • Spring Boot框架:计算机课程管理的工程认证之桥
  • 【数据管理】DAMA-数据建模和设计
  • Ollama服务以监听0.0.0.0地址