浅谈STL|STL函数对象篇

2023-09-14 16:24:22

在这里插入图片描述

一.函数对象概念

概念:

·重载函数调用操作符的类,其对象常称为函数对象
·函数对象使用重载的()时,行为类似函数调用,也叫仿函数

本质:

函数对象(仿函数)是一个类,不是一个函数

特点

  • 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值;
  • 函数对象超出普通函数的概念,函数对象可以有自己的状态;
  • 函数对象可以作为参数传递;

函数对象可以像普通函数一样被调用,并且可以接受参数和返回值。通过重载函数调用操作符 operator(),函数对象可以在使用时表现得像函数一样。

函数对象的一个优势是它可以拥有自己的状态。由于函数对象实际上是一个类的对象,所以它可以拥有成员变量,这些变量可以在每次调用时保持状态并进行更新。这使得函数对象可以在多次调用之间维护一些信息。

此外,函数对象还可以作为参数传递给其他函数。许多标准库的算法函数,例如 std::sortstd::transform 等,可以接受函数对象作为参数,以定义算法的具体操作。通过将函数对象传递给其他函数,我们可以实现更灵活、可定制的功能。

以下是一个简单的示例,展示了函数对象的用法:

#include <iostream>

class MyFunctor
{
public:
    void operator()(int x) const
    {
        // 在回调时输出消息
        std::cout << "Function object called with " << x << std::endl;
    }
};

void callWithFunctor(const MyFunctor& functor, int value)
{
    functor(value);  // 调用函数对象
}

int main()
{
    MyFunctor functor;
    callWithFunctor(functor, 42);  // 传递函数对象给函数,并进行调用
    
    return 0;
}

在上述示例中,MyFunctor 是一个函数对象,我们将它作为参数传递给 callWithFunctor 函数,并在函数内部调用函数对象。

总结来说,函数对象可以像普通函数一样被调用,可以接受参数和返回值,可以拥有自己的状态,并可以作为参数传递给其他函数,这使得函数对象在编程中非常有用和灵活。


函数对象相比于普通函数具有一个重要的优势,那就是函数对象可以拥有自己的状态。这意味着函数对象可以在多次调用之间记录和维护自己的内部数据。

普通函数是无状态的,它们不会保留任何关于之前调用的信息。每次调用函数时,它们只是根据传入的参数执行相应的操作,并返回结果。

而函数对象则可以在每次调用时保持状态,并根据状态的变化来改变行为。这种状态是通过函数对象的成员变量来实现的。函数对象的成员变量可以记录在调用过程中需要持久化的数据,而这些数据会在不同的函数调用之间持续存在。

这种函数对象的状态可以非常有用。它允许我们在不同的调用之间共享和利用数据,从而实现更复杂的行为。例如,我们可以使用函数对象来实现一个计数器,每次调用增加计数值,并在后续的调用中使用该值。

以下是一个简单的示例,展示了函数对象中保存状态的功能:

#include <iostream>

class Counter
{
public:
    Counter() : count(0) {}

    int operator()()
    {
        return ++count;
    }

private:
    int count;
};

int main()
{
    Counter counter;

    std::cout << counter() << std::endl;  // 输出:1
    std::cout << counter() << std::endl;  // 输出:2
    std::cout << counter() << std::endl;  // 输出:3

    return 0;
}

在上述示例中,Counter 是一个函数对象,它通过重载函数调用操作符 operator() 来实现计数的功能。每次调用 Counter 对象时,计数器的值会增加,并返回新的计数值。

总结而言,函数对象相比普通函数具有自己的状态,这使得它们可以在多次调用之间保持数据,并根据状态的变化改变行为。这种功能使得函数对象在许多应用场景中非常有用,例如实现计数器、缓存等。

二.谓词

概念:

  • 返回bool类型的仿函数称为谓词
  • 如果operator()接受一个参数,那么叫做一元谓词
  • 如果operator()接受两个参数,那么叫做二元谓词

返回 bool 类型的仿函数被称为谓词(Predicate)。谓词通常用于对某些条件进行判断,并返回相应的布尔值。

此外,根据 operator() 接受的参数数量,可以将谓词进一步分类为一元谓词(Unary Predicate)和二元谓词(Binary Predicate)。

  • 一元谓词是指 operator() 只接受一个参数的谓词。它用于对单个对象进行判断,返回一个布尔值。一元谓词通常用于像 std::find_ifstd::remove_if 等算法中。
  • 二元谓词是指 operator() 接受两个参数的谓词。它用于对两个对象进行比较或判断,返回一个布尔值。二元谓词通常用于像 std::sortstd::find 等算法中,用于指定排序规则或比较条件。

通过使用谓词,我们可以灵活地对对象进行条件判断和过滤。

以下是一个简单的示例,展示了一元谓词和二元谓词的概念:

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

// 一元谓词
struct IsEven
{
    bool operator()(int n) const
    {
        return n % 2 == 0;
    }
};

// 二元谓词
struct IsLess
{
    bool operator()(int a, int b) const
    {
        return a < b;
    }
};

int main()
{
    std::vector<int> numbers = { 1, 2, 3, 4, 5 };
    
    // 使用一元谓词查找第一个偶数
    auto it = std::find_if(numbers.begin(), numbers.end(), IsEven());
    if (it != numbers.end())
    {
        std::cout << "First even number: " << *it << std::endl;
    }
    
    // 使用二元谓词排序
    std::sort(numbers.begin(), numbers.end(), IsLess());
    
    // 输出排序结果
    std::cout << "Sorted numbers: ";
    for (int num : numbers)
    {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

在上述示例中,IsEven 是一个一元谓词,通过重载 operator() 来判断一个数字是否为偶数。IsLess 是一个二元谓词,通过重载 operator() 实现了两个数字的比较操作。

我们在主函数中使用了一元谓词来查找第一个偶数,并使用二元谓词对数组进行排序。

总结来说,返回 bool 类型的函数对象被称为谓词。根据 operator() 接受的参数数量,谓词可以进一步分类为一元谓词和二元谓词。谓词在标准库的算法中大量使用,用于自定义比较和条件判断。


find_if

std::find_if 是 C++ 标准库中的一个算法函数,它的作用是在给定的范围内查找满足指定条件的元素,并返回第一个满足条件的元素的迭代器。

函数签名如下所示:

template <class InputIt, class UnaryPredicate>
InputIt find_if(InputIt first, InputIt last, UnaryPredicate p);

参数说明:

  • firstlast:表示要查找的元素范围,first 表示范围的起始位置,last 表示范围的结束位置(不包含在范围内)。
  • p:一个一元谓词,用于判断元素是否满足条件。

函数返回值是一个迭代器,指向范围内第一个满足条件的元素,如果没有找到满足条件的元素,则返回 last

以下是一个简单的示例,展示了 std::find_if 的使用:

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

struct IsEven
{
    bool operator()(int n) const
    {
        return n % 2 == 0;
    }
};

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

    // 使用一元谓词查找第一个偶数
    auto it = std::find_if(numbers.begin(), numbers.end(), IsEven());
    if (it != numbers.end())
    {
        std::cout << "First even number: " << *it << std::endl;
    }

    return 0;
}

在上述示例中,我们定义了一个一元谓词 IsEven,用于判断一个数字是否为偶数。然后,我们使用 std::find_if 函数在 numbers 容器中查找第一个满足条件的偶数,并输出结果。

值得注意的是,std::find_if 在查找到满足条件的元素后,会立即停止搜索并返回该元素的迭代器。如果要查找出多个满足条件的元素,可以使用 std::find_if 结合其他算法函数或循环进行迭代查找。

三.内建函数对象

当涉及到内建函数对象时,可以根据它们的功能将它们分为三类: 算术函数对象、逻辑函数对象和关系函数对象。

  1. 算术函数对象:

    • std::plus: 加法操作。
    • std::minus: 减法操作。
    • std::multiplies: 乘法操作。
    • std::divides: 除法操作。
    • std::modulus: 取模操作。
  2. 逻辑函数对象:

    • std::logical_and: 逻辑与操作。
    • std::logical_or: 逻辑或操作。
    • std::logical_not: 逻辑非操作。
  3. 关系函数对象:

    • std::less: 小于比较。
    • std::less_equal: 小于等于比较。
    • std::greater: 大于比较。
    • std::greater_equal: 大于等于比较。
    • std::equal_to: 等于比较。
    • std::not_equal_to: 不等于比较。

这些函数对象可以通过重载 operator() 来执行特定的操作或比较,它们被设计为与标准库算法一起使用,以提供通用的功能。通过使用这些函数对象,我们可以以一种通用且灵活的方式处理算术、逻辑和关系操作。

当涉及到内建函数对象时,我们可以通过以下示例来展示它们的使用:

  1. 算术函数对象:
#include <iostream>
#include <functional>

int main() {
    std::plus<int> add;

    int a = 5, b = 10;

    int result = add(a, b);
    std::cout << "Addition result: " << result << std::endl;  // 输出:15

    return 0;
}

在上述示例中,我们使用了 std::plus 函数对象来执行加法操作,将数字 ab 相加,并将结果存储在 result 变量中。

  1. 逻辑函数对象:
#include <iostream>
#include <functional>

int main() {
    std::logical_and<bool> logicAnd;

    bool value1 = true, value2 = false;

    bool result = logicAnd(value1, value2);
    std::cout << "Logical AND result: " << std::boolalpha << result << std::endl;  // 输出:false

    return 0;
}

在上述示例中,我们使用了 std::logical_and 函数对象来执行逻辑与操作,对 value1value2 进行逻辑与运算,并将结果存储在 result 变量中。

  1. 关系函数对象:
#include <iostream>
#include <functional>

int main() {
    std::less<int> lessThan;

    int a = 5, b = 10;

    bool result = lessThan(a, b);
    std::cout << "Less than result: " << std::boolalpha << result << std::endl;  // 输出:true

    return 0;
}

在上述示例中,我们使用了 std::less 函数对象来执行小于比较,判断 a 是否小于 b,并将结果存储在 result 变量中。

这些示例演示了如何使用内建函数对象进行算术、逻辑和关系操作。您可以通过实例化适当的函数对象类模板,然后将其用作函数调用,以便执行所需的操作或比较。

请注意,这些示例可能只是演示了函数对象的一小部分用法,这些函数对象在标准库的算法和其他使用场景中非常有用。

好的,下面是一个整理成表格的例子,展示了内建函数对象及其用途的示例。

函数对象用途示例
std::plus加法操作std::plus()(2, 3) // 输出:5
std::minus减法操作std::minus()(5, 2) // 输出:3
std::multiplies乘法操作std::multiplies()(3, 4) // 输出:12
std::divides除法操作std::divides()(10, 2) // 输出:5
std::modulus取模操作std::modulus()(10, 3) // 输出:1
std::logical_and逻辑与操作std::logical_and()(true, false) // 输出:false
std::logical_or逻辑或操作std::logical_or()(true, false) // 输出:true
std::logical_not逻辑非操作std::logical_not()(true) // 输出:false
std::less小于比较std::less()(2, 5) // 输出:true
std::less_equal小于等于比较std::less_equal()(5, 5) // 输出:true
std::greater大于比较std::greater()(5, 2) // 输出:true
std::greater_equal大于等于比较std::greater_equal()(5, 5) // 输出:true
std::equal_to相等比较std::equal_to()(2, 2) // 输出:true
std::not_equal_to不等比较std::not_equal_to()(2, 5) // 输出:true
更多推荐

消息队列——rabbitmq的不同工作模式

目录Workqueues工作队列模式Pub/Sub订阅模式Routing路由模式Topics通配符模式工作模式总结Workqueues工作队列模式C1和C2属于竞争关系,一个消息只有一个消费者可以取到。代码部分只需要用两个消费者进程监听同一个队里即可。两个消费者呈现竞争关系。用一个生产者推送10条消息for(inti=

什么是线程?为什么需要线程?和进程的区别?

目录前言一.线程是什么?1.1.为什么需要线程1.2线程的概念1.3线程和进程的区别二.线程的生命周期三.认识多线程总结🎁个人主页:tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主🎥本文由tq02原创,首发于CSDN🙉🎄本章讲解内容:线程的讲解🎥学习专栏:C语言JavaSEMySQL基

2023年电赛---运动目标控制与自动追踪系统(E题)OpenART mini的代码移植到OpenMV

如果有嵌入式企业需要招聘校园大使,湖南区域的日常实习,任何区域的暑假Linux驱动实习岗位,可C站直接私聊,或者邮件:zhangyixu02@gmail.com,此消息至2025年1月1日前均有效前言(1)已经有不少同学根据我上一篇博客完成了前三问,恭喜恭喜。有很多同学卡在了第四问。(2)我说了OpenARTmini的

React、Vue框架如何实现组件更新,原理是什么?

引言React和Vue都是当今最流行的前端框架,它们都实现了组件化开发模式。为了优化性能,两者都采用了虚拟DOM技术。当组件状态发生改变时,它们会使用虚拟DOM进行局部渲染比对,只更新必要的DOM节点,从而避免重新渲染整个组件树。本文将从React和Vue的组件更新原理入手,剖析两者虚拟DOMdifer算法的异同点。R

C语言指针详解

C语言指针详解字符指针1.如何定义2.类型和指向的内容3.代码例子指针数组1.如何定义2.类型和内容数组指针1.如何定义2.类型和指向类型3.数组名vs&数组名数组指针运用数组参数&指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指针1.如何定义2.类型和指向内容3.函数名vs&函数名4.两个有趣的代码函数

2023 电赛 E 题 K210 方案

第一章:K210介绍K210芯片是一款基于RISC-V架构的嵌入式人工智能芯片,具备低功耗、高性能的特点。它拥有强大的图像处理和机器学习能力,适用于边缘计算设备和物联网应用。为了方便开发者,K210芯片提供了丰富的外设接口,包括摄像头接口、显示接口、WiFi、蓝牙等,同时支持多种编程语言和开发环境,如MicroPyth

天气API强势对接

🤵‍♂️个人主页:@香菜的个人主页,加ischongxin,备注csdn✍🏻作者简介:csdn认证博客专家,游戏开发领域优质创作者,华为云享专家,2021年度华为云年度十佳博主🐋希望大家多多支持,我们一起进步!😄如果文章对你有帮助的话,欢迎评论💬点赞👍🏻收藏📂加关注+目录前言:墨迹天气数据接口1、找到目

【E题】2023年电赛运动目标控制与自动追踪系统方案

系统的设计和制作可以按照以下步骤进行:设计红色光斑位置控制系统:选择合适的红色激光笔,并将其固定在一个二维电控云台上。使用电机和编码器来控制电控云台的水平和垂直运动。设计一个控制电路,可以通过输入控制信号来控制电机的运动,从而控制红色光斑的位置。确保控制电路可以接收来自用户输入的目标位置信息,并将其转换为相应的电机控制

代客泊车对HUT功能交互规范

目录1.版本记录...72.文档范围和控制...82.1目的/范围...82.2文档冲突...82.3文档授权...82.4文档更改控制...83.系统组成...93.1IPAS系统(环视和超声波雷达)...93.2融合泊车系统(环视和泊车)...104.AVM与HUT系统交互...114.1系统框图...114.2接

MyBatis查询数据库1(概念+创建项目+基础交互)

目录1.MyBatis是什么?2.为什么学习MyBatis?3.怎么学MyBatis4.第⼀个MyBatis查询4.1添加MyBatis框架支持4.1.1老项目添加MyBatis4.1.2新项目添加MyBatis4.2配置连接字符串和MyBatis4.2.1配置连接字符串4.2.2配置MyBatis中的XML路径5.使

进程转态及其转换过程

一.进程转态及其转换过程在Linux操作系统中,进程的状态可以相互转换,下面是不同状态之间的相互转换:就绪态(ReadyState):当一个进程创建后,它被放入就绪态。此时,进程已经被加载到内存中,并准备好被CPU分配时间片来执行。运行态(RunningState):当就绪态的进程获得CPU时间片后,进程的状态会从就绪

热文推荐