仿函数(函数对象)

转载:http://www.cnblogs.com/wuchanming/p/4411867.html

本文乃作者学习《C++标准程序库》的学习笔记,首先介绍了仿函数(函数对象)和函数适配器(配接器)的概念,然后列出STL中所有的仿函数,以及函数适配器,并摘录了几个例子演示仿函数和函数适配器的用法,最后讨论了仿函数的组合,以及实现方法。

1.仿函数是什么东西?

《C++标准程序库》里对仿函数的解释是:仿函数是泛型编程强大威力和纯粹抽象概念的又一例证。你可以说,任何东西,只要其行为像函数,它就是一个函数。因此如果你定义了一个对象,行为像函数,它就可以被当做函数来用。

那么,什么才算具备函数行为呢?所谓函数行为,是指可以"使用小括号传递参数,籍以调用某个东西"。例如:

function(argc1, argc2);

如果你指望对象也可以如此这般,就必须让它们也可以被"调用"——通过小括号的运用和参数的传递。你只需要定义operator(),并给予合适的参数型别:

class X 
{public: // define 'function call' operator return-value operator()(arguments) const; ...... };

现在,你可以把这个类别的对象当做函数来调用了:

X fo;
...... // call operator () for function object to fo(argc1, argc2);

上述调用等价于:

fo.operator() (argc1, argc2);

总结如下:

  • 函数对象是一个普通对象
  • 重载了operator ()操作符
  • 函数对象一般都比较简单,主要用到operator()操作,其他成员函数和成员变量都是为operator()服务

为了帮助理解仿函数,我们来看一个例子:

#include <algorithm>
class gen_by_two { public: gen_by_two( int seed = 0 ) : _seed( seed ){} int operator()() { return _seed += 2; } private: int _seed; }; vector<int> ivec( 10 ); // fills ivec: 102 104 106 108 110 112 114 116 118 120 generate_n( ivec.begin(), ivec.size(), gen_by_two(100) );
或者

generate_n( ivec.begin(), ivec.size(), gen_by_two() );

上面的程序使用generate_n函数,给容器赋值,在调用gen_by_two(100)的时候,我们其实是生成了一个对象,并调用对象的构造函数给成员变量赋值,然后多次(v.size())调用该变量的operator()成员函数,将返回的值依次存入容器中。

2.函数适配器又是什么东西?

函数适配器又称"函数配接器",是只能够将仿函数和另一个仿函数(或某个值,或某一个函数)结合起来的仿函数。函数适配器声明于<functional>中。

下面来看一个例子:

find_if(coll.begin(), coll.end(), //range bind2nd(greater<int>(), 42));criterion

其中表达式bind2nd(greater<int>(),42)导致一个组合型的仿函,检查某个int值是否大于42.实际上bind2nd是将一个二元仿函数转换为一个一元仿函数。bind2nd的意思就是将42作为比较(greater<int>())函数的第二个参数,也就相当于是elem.value > 42,如果容器中没有42这个值,那么下面这条语句和上面的语句是一样的。

find_if(coll.begin(), coll.end(), //range bind1st(less<int>(), 42));criterion

上面两个小例子演示了适配器的功能,同时还讲解了bind1st和bind2nd的区别。

3.预定义的仿函数

4.预定义的函数适配器

5.仿函数(以及函数适配器)的使用示例

下面摘录几个《C++标准程序库》上的例子,以及个别我自己补充的实例,用于演示仿函数的使用,希望能够通过例子快速掌握仿函数。

5.1 查找25或者35第一次出现的位置

pos = find_if(coll.begin(), coll.end(), //range compose_f_gx_hx(logical_or<bool>(), //criterion bind2nd(equal_to<int>(), 25), bind2nd(equal_to<int>(), 35)));

5.2 将集合中全部元素都设为相反值

transform(coll.begin(), coll.end(),//source coll.begin(),//destination negate<int>());//operation

5.3 对集合中的所有元素求平方

transform(coll.begin(), coll.end(),//first source coll.begin(),//second source coll.begin(),//destination multiplies<int>());//operation

5.4 所有元素乘以10

transform(coll.begin(), coll.end(),//source back_inserter(coll2),//destination bind2nd(multiplies<int>(),10));//operation

5.5 将a替换为b

replace_if(coll2.begin(), coll2.end(),//range bind2nd(equal_to<int>(), 70),//replace criterion 42);//new value

5.6 删除小于50的元素

coll.erase(remove_if(coll.begin(), coll.end(),//range bind2nd(less<int>(), 50)), //remove criterion coll.end());

5.7 返回第一个偶数

pos = find_if(coll.begin(), coll.end(), //range not1(bind2nd(modulus<int>(), 2)));

上面几个例子都还是挺使用的,返回第一偶数么,怎么看怎么奇怪,没有关系,我们来看一个不奇怪的:

5.8 调整数组顺序,使得奇数位于偶数前面

stable_partition(v.begin(), v.end(), bind2nd(modulus<int>(), 2));

关于这里用到的泛型算法,如果还有不熟悉的,可以参考这里

5.9 mem_fun_ref 与mem_fun 的区别以及用法

  • mem_fun_ref:调用某个对象的成员函数
  • mem_fun:功能和mem_fun_ref 一样,如果容器里存放的是指向对象的指针,而不是对象,则应该使用mem_fun

下面来看一个使用mem_fun_ref 的例子,假设我们定义了一个类Person,并且定义了一个print 的成员函数,容器coll 里存放了coll.size() 个Person 对象,现在要调用每个对象的print 成员函数,那么我们就可以像下面这样。

class Person 
{ public: ... void print() const { std::cout << name << std::endl; } void printWithPrefix(std::string prefix) const { std::cout << prefix << name << std::endl; } private: std::string name; }; void foo(const std::vector<Person> &coll) { for_each(coll.begin(), coll.end(), mem_fun_ref(&Person::print)); }

我们不能直接把一个成员函数传递给一个算法,所以这里必须运用函数适配器,下面这种做法会导致编译错误。

for_each(coll.begin(), coll.end(), &Person::print); //ERROR

通过使用函数适配器,我们还可以像被调用的成员函数传递一个参数。如下所示:

    //call member function printWithPrefix() for each elementfor_each(coll.begin(), coll.end(), bind2nd(mem_fun_ref(&Person::printWithPrefix), "person:"));

5.10 ptr_fun

ptr_fun 使得我们能够在其他函数适配器中使用一般函数,加入你自己定义了一个函数check(),用于检验容器中的中的元素是否符合某种条件,你就可以这样:

pos = find_if(coll.begin(), coll.end(), not1(ptr_fun(check)));

这里不能使用not1(check),因为not1()需要用到由仿函数提供的某些特殊型别.

第二种用法是,当你有一个双参数的全局函数,又想把它当做一个单参数函数来使用,可以用如下语句:

//find first string that is not empty
pos = find_if(coll.begin(), coll.end(), bind2nd(ptr_fun(strcmp),""));

6.让自定义的仿函数也可以使用函数适配器

你可以编写自己的仿函数,但如果希望它们能够和函数适配器搭配运用,就必须满足某些条件:必须提供一些型别成员来反映其参数和返回值的型别。为了方便我们,C++标准库提供了一些结构如下:

转载:http://mingxinglai.com/cn/2012/09/function-object/


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384341.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ template —— 动多态与静多态(六)

转载&#xff1a;http://www.cnblogs.com/yyxt/p/5157517.html 前面的几篇博文介绍了模板的基础知识&#xff0c;并且也深入的讲解了模板的特性。接下来的博文中&#xff0c;将会针对模板与设计进行相关的介绍。 ------------------------------------------------------------…

变量之间的区别

全局变量、局部变量、静态全局变量、静态局部变量的区别 c变量根据定义具有不同的生命周期&#xff0c;会有不同的作用域&#xff0c;主要有六个作用域&#xff1a;全局作用域&#xff0c;局部作用域&#xff0c;文件作用域&#xff0c;类作用域&#xff0c;语句作用域&#xf…

计算机的网络体系以及参考模型

计算机的网络体系以及参考模型一、OSI七层模型二、TCP/IP参考模型三、TCP/IP 五层参考模型四、OSI 模型和 TCP/IP 模型异同比较五、OSI 和 TCP/IP 协议之间的对应关系六、为什么 TCP/IP 去除了表示层和会话层&#xff1f;七、数据如何在各层之间传输&#xff08;数据的封装过程…

C++ 模板详解(二)

转载&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2736224.html 四、类模板的默认模板类型形参 1、可以为类模板的类型形参提供默认值&#xff0c;但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。 2、类模板的类…

c++类对象的创建方式

对象创建限制在堆或栈 c类对象的创建方式对象创建限制在堆或栈C 中的类的对象的建立模式如何将类限制在堆上呢&#xff1f;C 中的类的对象的建立模式 C 中的类的对象的建立模式分为两张&#xff1a;静态建立&#xff0c;动态建立 静态建立&#xff1a;由编译器为对象在栈空间…

C++ 模板详解(一)

转载&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2738929.html C模板 模板是C支持参数化多态的工具&#xff0c;使用模板可以使用户为类或者函数声明一种一般模式&#xff0c;使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。 模板是一种对类…

剑指Offer09. 用两个栈实现队列

class CQueue { public:stack<int> stack1,stack2;CQueue() {//初始化栈while(!stack1.empty()){stack1.pop();}while(!stack2.empty()){stack2.pop();}}void appendTail(int value) {stack1.push(value);}int deleteHead() {if(stack2.empty()){while(!stack1.empty()){…

rk3588 之启动

目录 uboot版本配置修改编译 linux版本配置修改编译 启动sd卡启动制作spi 烧录 参考 uboot 版本 v2024.01-rc2 https://github.com/u-boot/u-boot https://github.com/rockchip-linux/rkbin 配置修改 使用这两个配置即可&#xff1a; orangepi-5-plus-rk3588_defconfig r…

C++引用详解

转载&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/20/2732687.html 引用的概念 引用&#xff1a;就是某一变量&#xff08;目标&#xff09;的一个别名&#xff0c;对引用的操作与对变量直接操作完全一样。 引用的声明方法&#xff1a;类型标识符 &引用名目标…

剑指Offer03.数组中重复的数字

找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0&#xff5e;n-1 的范围内。数组中某些数字是重复的&#xff0c;但不知道有几个数字重复了&#xff0c;也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 示例 1&#xff1a; 输入&…

C++ 模板全特化中的函数特化

转载&#xff1a;http://blog.csdn.net/rain_qingtian/article/details/15815251 [cpp] view plaincopy print?#include <iostream> using namespace std; template<typename T> bool isLess(T x, T y) { cout << "general version\n&q…

c++面向对象总结

c面向对象总结什么是面向对象&#xff1f;面向对象的三大特性重写和重载的区别隐藏和重写&#xff0c;重载的区别什么是多态&#xff1f;多态如何实现什么是面向对象&#xff1f;面向对象的三大特性 面向对象&#xff1a;对象是指具体的某一个事物&#xff0c;这些事物的抽象就…

类模板static成员的使用

转载&#xff1a;http://blog.csdn.net/ljq32/article/details/7911390 1. 与普通类的static成员一样&#xff0c;类内部声明一次&#xff0c;类外部定义一次&#xff0c;定义时可以设置也可以不设置初始值; 2. 类模板内部声明与普通类的static成员一致&#xff1a; [html] vi…

Linux网络编程服务器模型选择之循环服务器

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3617608.html 在网络程序里面&#xff0c;通常都是一个服务器处理多个客户机&#xff0c;为了出个多个客户机的请求&#xff0c;服务器端的程序有不同的处理方式。本节开始介绍Linux下套接字编程的服务器模型选择&#xff…

剑指Offer04. 二维数组中的查找

在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该整数。 相当于二叉搜索树,左孩子比根节点小&…

Linux网络编程服务器模型选择之并发服务器(上)

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3617666.html 与循环服务器的串行处理不同&#xff0c;并发服务器对服务请求并发处理。循环服务器只能够一个一个的处理客户端的请求&#xff0c;显然效率很低。并发服务器通过建立多个子进程来实现对请求的并发处理。并发…

剑指Offer10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶&#xff0c;也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 答案需要取模 1e97&#xff08;1000000007&#xff09;&#xff0c;如计算初始结果为&#xff1a;1000000008&#xff0c;请返回 1。 示例 1&#xff1a; 输入&a…

Linux网络编程服务器模型选择之并发服务器(下)

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3618986.html 前面两篇文章&#xff08;参见&#xff09;分别介绍了循环服务器和简单的并发服务器网络模型&#xff0c;我们已经知道循环服务器模型效率较低&#xff0c;同一时刻只能为一个客户端提供服务&#xff0c;而且…

剑指Offer05. 替换空格

请实现一个函数&#xff0c;把字符串 s 中的每个空格替换成"%20"。 示例 1&#xff1a; 输入&#xff1a;s “We are happy.” 输出&#xff1a;“We%20are%20happy.” class Solution { public:string replaceSpace(string s) {int count0;int lens.size();stri…

Linux网络编程服务器模型选择之IO复用循环并发服务器

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3619091.html 在前面我们介绍了循环服务器&#xff0c;并发服务器模型。简单的循环服务器每次只能处理一个请求&#xff0c;即处理的请求是串行的&#xff0c;效率过低&#xff1b;并发服务器可以通过创建多个进程或者是线…