论函数对象

简介: 论函数对象

1.引出问题

函数对象说白了就是类似于C语言中的函数指针,看下面的代码

我们知道调用一个函数的话,可以用C代码来实现

int sum(int a, int b)
{
    return a + b;
}
int ret = sum(2, 1);

当然也可以这样用C++来实现

像这个operator重载了两个参数,叫做二元,重载一个叫做一元

class Sum
{
public:
  int operator(int a, int b)
    {
        return a + b;
    }
};
Sum sum;
int ret = sum(2, 1);

其中这个sum就叫做函数对象

把有operator()运算符重载函数的对象,称为函数对象或者称为仿函数

其中无论这个类或者结构体里是否还有其他的函数,但只要看有没有operator()运算符重载函数就行

函数对象一般来说只包含一个operator()运算符重载函数,但也可以包括其他的函数

在上面代码里

//int operator(int a, int b)
sum(2, 1) 等同于 sum.operator()(2, 1);

那既然他们两个都能实现,那用函数对象有啥好处吗

2.函数对象的好处

再看下面的例子

template<typename T>
bool compare(T a, T b)
{
  return a > b;
}
int main()
{
  cout << compare(10, 20) << endl;
  cout << compare('b', 'y') << endl;
  return 0;
}

现在如果想调用compare实现小于的话,会想,那把里面的a > b改成a < b不就好了,但万一情况是这个函数的实现是在库里怎么办,你总不能去改人家库里的东西吧,不现实

有人也会说,那我再实现一个这个不就好了

template<typename T>
bool compare(T a, T b)
{
  return a < b;
}

那如果多了的话,你还一个个实现吗,也没啥必要

C语言的函数指针就可以解决这个问题

template<typename T>
bool greater(T a, T b)
{
  return a > b;
}
template<typename T>
bool less(T a, T b)
{
    return a < b;
}
// compare是C++的库函数模板
template<typename T>
bool compare(T a, T b)
{
  return a > b;
}
int main()
{
  cout << compare(10, 20) << endl;
  cout << compare('b', 'y') << endl;
  return 0;
}

因为compare是C++的库函数模板,所以他不能在函数里面写死,写成要么大于,要么小于的,所以应该再传入个参数,去接收个函数指针Compare

template<typename T, typenmae Compare>
bool compare(T a, T b, Compare comp)
{
  return comp(a, b);//把ab当做这两个函数的参数传进来,根据传入的函数comp它的返回值我们再进行返回
}

这时候代码应该这样子

template<typename T>
bool mygreater(T a, T b)
{
  return a > b;
}
template<typename T>
bool myless(T a, T b)
{
    return a < b;
}
// compare是C++的库函数模板
template<typename T>
bool compare(T a, T b, Compare comp)
{
  return comp(a, b);
}
int main()
{
  cout << compare(10, 20, mygreater<int>) << endl; 
  cout << compare('b', 'y', myless<int>) << endl;
  return 0;
}

compare(10, 20, greater<int>)

这个代码进去后首先进入compare函数里,用return comp(a, b),也就是return greater(10, 20),间接调用了greater函数

compare(T a, T b, Compare comp),第三个参数传入了greater的函数地址,也就是函数指针

但return comp(a, b) 通过函数指针调用函数,是没有办法内联的,效率很低,因为有函数的调用开销

因为内联是发生在编译阶段的,比如

inline void func() {}
template<typename T>
bool compare(T a, T b, Compare comp)
{
    func()
  return comp(a, b);
}

那函数在编译时候,走到func()时,就会自动找到inline void func() {},然后把这个函数展开,这样就省去了函数的调用开销了

但就算我们把刚才的代码改成这样的

template<typename T>
inline bool mygreater(T a, T b)
{
  return a > b;
}
template<typename T>
inline bool myless(T a, T b)
{
    return a < b;
}
// compare是C++的库函数模板
template<typename T>
bool compare(T a, T b, Compare comp)
{
  return comp(a, b);
}

即使两个函数都加上inline,但编译时候走到return comp(a, b);时,编译器是不知道我调用的是 myless还是mygreater,完全不知道,除非我们明确写是调用了谁,只有在运行时候才会跑到这个地址上去找到对应的,因为函数指针只存储了函数地址,而没有任何函数体的信息

所以通过函数指针调用函数,是没有办法内联的,效率很低,因为有函数的调用开销

那我们再来看看C++函数对象的版本实现

template<typename T>
class mygreater
{
public:
  bool operator()(T a, T b)
  {
    return a > b;
  }
};
template<typename T>
class myless
{
public:
  bool operator()(T a, T b)
  {
    return a < b;
  }
};
// typename指向谁,谁就是类型名,比如这里的Compare就是一个类型
template<typename T, typename Compare>
bool compare(T a, T b, Compare comp)
{
  return comp(a, b);
}
int main()
{
  cout << compare(10, 20, mygreater<int>()) << endl;
  cout << compare('b', 'y', myless<int>()) << endl;
}
//mygreater<int>是个类名,加个括号mygreater<int>()就是函数对象,因为是operator()重载

编译时在这里我们第一次调用compare函数的时候我们已经知道了他是调用哪个对象,比如compare(10, 20, mygreater<int>())是mygreater<int>,所以 comp 的类型就是 mygreater<int>。在第二次调用 compare 函数时,Compare 被指定为 myless<int> 类型,所以 comp 的类型就是 myless<int>。因此,在编译时,编译器就能确定 comp(a, b) 调用的是哪个函数对象,它可以在编译阶段将这个函数的函数体直接插入到调用它的地方。这个过程类似于内联函数的处理方式。

总的来说,就是函数对象相对于函数指针的一个优点是,它可以在编译时确定类型,这使得编译器能够更好地优化代码。由于函数对象是一个类,它可以重载 () 运算符,使得它可以像调用函数一样被调用。当我们通过函数对象调用一个函数时,编译器能够确定这个函数对象的类型,因此也能够确定它调用的是哪个函数。这样,编译器就可以将这个函数的函数体直接插入到调用它的地方,从而减少函数调用的开销。

3.修改其他的代码印证

再举个例子

#include <iostream>
#include <queue>
#include <set>
using namespace std;
int main()
{
    priority_queue<int> que1; //vector 底层是大根堆
    for(int i = 0; i < 10; i++)
    {
        que1.push(rand() % 100);
    }
    while(!que1.empty())
    {
        cout << que1.top() << " ";
        que1.pop();
    }
    cout << endl;
}

这输出是从大到小的,那我们怎么从小到大呢,查看priority_queue源码我们会发现,第三个参数是默认less,我们是可以改的

如果我们改第三个的话,前两个参数也得手动写上

#include <iostream>
#include <queue>
#include <set>
#include <vector>
using namespace std;
int main()
{
    using MinHeap = priority_queue<int, vectpr<int>, greater<int>>; //小根堆
    MinHeap que2;
    for(int i = 0; i < 10; i++)
    {
        que2.push(rand() % 100);
    }
    while(!que2.empty())
    {
        cout << que2.top() << " ";
        que2.pop();
    }
    cout << endl;
}

这样就是按从小到大排序了

同理set也可以这样玩

相关文章
|
Kubernetes Cloud Native 应用服务中间件
对比 5 个开源网关项目,这家 SaaS 企业如何统一网关架构
对比 5 个开源网关项目,这家 SaaS 企业如何统一网关架构
45871 150
|
测试技术
有了测试标准流程后缺陷就不会遗漏到线上吗?
有了测试标准流程后缺陷就不会遗漏到线上吗?
252 0
|
算法 芯片
学习目录-STC-51单片机(15系列,51的升级版)
学习目录-STC-51单片机(15系列,51的升级版)
283 0
|
云安全 安全 网络安全
|
7天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
1999 1
|
14天前
|
JSON 程序员 数据格式
最新版Notepad++下载、安装、配置保姆级教程(附官网安装包)
Notepad++ 是一款免费开源、轻量快速的 Windows 文本编辑器,支持多标签、语法高亮、代码折叠与宏录制,体积小、启动快、资源占用低,兼容 x64/x32/ARM 架构,是程序员和文字工作者的高效利器。
3006 0
|
13天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
2216 44
|
10天前
|
Web App开发 人工智能 前端开发
2026 年 AI 浏览器怎么选?Ego Lite、Tabbit、Chrome DevTools MCP 深度对比,开发者到底该用哪个?
2026年AI浏览器选型指南:Ego Lite专注多Agent协同(Mac优先),Tabbit国产首选,原生支持Windows与Codex控制;Chrome DevTools MCP非浏览器而是开发者利器,深度集成Console/Network/Performance调试能力,让AI真正“看懂并验证”前端代码。
1401 3
|
7天前
|
安全 应用服务中间件 Go
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
PHP2Shell 双 RCE 已遭在野利用:WordPress、Drupal 全在射程内,nginx 其实另有洞。拆成因与修复顺序
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招

热门文章

最新文章