C++ 仿函数/函数指针/闭包lambda
在上一篇文章中介绍了C++11新引入的lambda表达式(C++支持闭包的实现),现在我们看一下lambda的出现对于我们编程习惯的影响,毕竟,C++11历经10年磨砺,出140新feature,对于我们的programming idiom有深远影响。我们先看一下是不是lambda的出现对于仿函数的影响。
1) 仿函数
wikipedia 的定义:
A function object, also called a functor, functional, or functionoid, is a computer programming construct allowing an object to be invoked or called like it was an ordinary function, usually with the same syntax. |
Functor/Function Object翻译过来就是仿函数。它是通过重载()运算符模拟函数形为的类。也就是说,它不是函数(所以仿函数翻译的很贴切)。因为它重载了()运算符,因此可以像调用函数一样对它进行调用。STL中大量运用了Function Object,也提供了很多预先定义的Function Object。还是从vector遍历举例:
class PrintInt
{
public:
void operator()(int elem) const
{
std::cout<<elem<<' ';
}
}; std::vector<int> v;
for_each(v.begin(),v.end(), PrintInt()); //C++ 11 lambda stype
for_each(begin(v), end(v), [](int n){ cout<< n <<", "; });
仿函数的优点:
1.仿函数是对象,可以拥有成员函数和成员变量,即仿函数拥有状态(states)
2.每个仿函数都有自己的类型
3.仿函数通常比一般函数快(很多信息编译期确定)
首先简单看一下for_each的源码:
// TEMPLATE FUNCTION for_each
template<class _InIt,
class _Fn1> inline
_Fn1 for_each(_InIt _First, _InIt _Last, _Fn1 _Func)
{ // perform function for each element _DEBUG_RANGE(_First, _Last);
_DEBUG_POINTER(_Func);
_CHECKED_BASE_TYPE(_InIt) _ChkFirst(_CHECKED_BASE(_First));
_CHECKED_BASE_TYPE(_InIt) _ChkLast(_CHECKED_BASE(_Last));
for (; _ChkFirst != _ChkLast; ++_ChkFirst)
_Func(*_ChkFirst);
return (_Func);
} //effective STL
template< typename InputIterator, typename Function >
Function for_each( InputIterator beg, InputIterator end, Function f ) {
while ( beg != end )
f( *beg++ );
}
简单来说, for_each就是一个模板函数,将for循环语句封装起来,前面两个参数都是迭代器,第三个参数是使用一个函数指针(或仿函数),其功能是对每一个迭代器所指向的值调用仿函数。
STL包括了许多不同的预定义的函数对象,包括算子(plus,minus,multiplies,divides,modulus和negate),算术比较(equal_to,not_equal_to,greater,less,greater_equal和less_equal),和逻辑操作(logical_and,logical_or和logical_not)。这样你就可以不用手动写新的函数对象而是用这些函数对象就可以组合出相当复杂的操作。
当然,为了仅仅是遍历vector并且输出,可以使用更简单的方式,我们第一个例子仅仅是为了说明仿函数怎么用。
copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout," "));
其实通过lambda,很多STL的仿函数可以不用。但是既然有轮子了,所以如果了解或者会使用这些仿函数,无疑会更能优美、高效的编写代码。
使用std::negate<int>()进行数组元素求反:
transform( vect.begin(), vect.end(), vect.begin(), std::negate<int>() ); // TEMPLATE STRUCT negate
template<class _Ty> struct negate : public unary_function<_Ty, _Ty>
{ // functor for unary operator-
_Ty operator()(const _Ty& _Left) const
{ // apply operator- to operand
return (-_Left);
}
};
将容器中所有小于5的元素删除。
auto iter = std::remove_if( ivec.begin(), ivec.end(), std::bind2nd( std::less<int>(), 5 ) );
ivec.erase( iter, ivec.end() );
关于std::less<int>
// TEMPLATE STRUCT less
template<class _Ty>
struct less
: public binary_function<_Ty, _Ty, bool>
{ // functor for operator<
bool operator()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator< to operands
return (_Left < _Right);
}
};
关于bind2nd的实现,实现过程也有仿函数的运用:
// TEMPLATE FUNCTION bind2nd
template<class _Fn2,
class _Ty> inline
binder2nd<_Fn2> bind2nd(const _Fn2& _Func, const _Ty& _Right)
{ // return a binder2nd functor adapter typename _Fn2::second_argument_type _Val(_Right);
return (std::binder2nd<_Fn2>(_Func, _Val));
} // TEMPLATE CLASS binder2nd
template<class _Fn2>
class binder2nd
: public unary_function<typename _Fn2::first_argument_type,
typename _Fn2::result_type>
{ // functor adapter _Func(left, stored)
public:
typedef unary_function<typename _Fn2::first_argument_type,
typename _Fn2::result_type> _Base;
typedef typename _Base::argument_type argument_type;
typedef typename _Base::result_type result_type; binder2nd(const _Fn2& _Func,
const typename _Fn2::second_argument_type& _Right)
: op(_Func), value(_Right)
{ // construct from functor and right operand } result_type operator()(const argument_type& _Left) const
{ // apply functor to operands return (op(_Left, value));
} result_type operator()(argument_type& _Left) const
{ // apply functor to operands return (op(_Left, value));
} protected:
_Fn2 op; // the functor to apply
typename _Fn2::second_argument_type value; // the right operand
};
综上所述,由于STL内置的仿函数功能强大,因此如果熟悉的话可以加以利用。否则,使用lambda也是不错的选择,毕竟,都能写出优雅的代码。
如果非要细分的话,lambda什么时候可以替代仿函数?我们需要从lambda的限制说起。这一点很大程度上是由lambda的捕捉列表的限制造成的。在现行C++11的标准中,捕捉列表仅能捕捉父作用域的自动变量。比如下面这个例子:
int d = 0;
void test()
{
auto ill_lambda = [d]{};
}
g++会编译通过,但是会有warning。而一些严格遵照C++11标准的编译器则会直接报错。仿函数则没有次限制。
简单总结一下,使用lambda替代仿函数应该满足一下几个条件:
a) 是局限于一个局部作用域中使用的代码逻辑。
b) 这些代码逻辑需要作为参数进行传递。
lambda被设计的主要目的之一就是简化仿函数的使用,虽然语法看起来不像是“典型的C++”, 但是一旦熟悉之后,程序员就能准确的编写一个简单的,就地的,带状态的函数定义。当然了,如果需要全局共享的代码逻辑,我们还是需要用函数(无状态)或者仿函数(有状态)封装起来。
引用:
http://blog.chinaunix.net/uid-20384806-id-1954378.html
C++ 仿函数/函数指针/闭包lambda的更多相关文章
- c++11 符号修饰与函数签名、函数指针、匿名函数、仿函数、std::function与std::bind
一.符号修饰与函数签名 1.符号修饰 编译器将c++源代码编译成目标文件时,用函数签名的信息对函数名进行改编,形成修饰名.GCC的C++符号修饰方法如下: 1)所有符号都以_z开头 2)名字空间的名字 ...
- Swift的函数与函数指针、闭包Closure等相关内容介绍
<span style="font-size:24px;">//函数 //demo1 无參数类型 func testConcat(){ println("測试 ...
- 匿名函数、闭包、lambda表达式、Block
C#有lambda.匿名函数,js有匿名函数.闭包,OC中有block,看到这是不是心中有一万个草泥马在跑,不过它们这些都是换汤不换药,不同语言名字不一样. 从功能性上说lambda和closure( ...
- 闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别
闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别 函数最常见的形式是具名函数(named function): function foo(){ con ...
- C++使用模板、函数指针、接口和lambda表达式这四种方法做回调函数的区别比较
在C++中,两个类之间存在一种关系,某个类需要另外一个类去完成某一个功能,完成了之后需要告知该类结果,这种最普通最常见的需求,往往使用回调函数来解决. 如题,我总结下来有这么四种方式可以完成这项功能, ...
- Python:Base4(map,reduce,filter,自定义排序函数(sorted),返回函数,闭包,匿名函数(lambda) )
1.python把函数作为参数: 在2.1小节中,我们讲了高阶函数的概念,并编写了一个简单的高阶函数: def add(x, y, f): return f(x) + f(y) 如果传入abs作为参数 ...
- swift 学习(二)基础知识 (函数,闭包,ARC,柯里化,反射)
函数 func x(a:Int, b:Int) {} func x(a:Int, b:Int) -> Void {} func x(a:Int, b:Int) ->(Int,Int ...
- 函数指针&绑定: boost::functoin/std::function/bind
see link: https://isocpp.org/wiki/faq/pointers-to-members function vs template: http://stackoverflow ...
- go 学习 (三):函数 & 指针 & 结构体
一.函数 函数声明 // 声明语法 func funcName (paramName paramType, ...) (returnType, returnType...) { // do somet ...
随机推荐
- Python中的赋值(复制)、浅拷贝、深拷贝之间的区别
1.赋值: 只是复制了新对象的引用,不会开辟新的内存空间. 2.浅拷贝: 创建新对象,其内容是原对象的引用. 浅拷贝有三种形式:切片操作,工厂函数,copy模块中的copy函数. 如: ...
- 利用Python进行数据分析——重要的Python库介绍
利用Python进行数据分析--重要的Python库介绍 一.NumPy 用于数组执行元素级计算及直接对数组执行数学运算 线性代数运算.傅里叶运算.随机数的生成 用于C/C++等代码的集成 二.pan ...
- ubuntu重装指定版本的mysql
查看错误log cat /var/log/mysql/error.log 首先彻底删除mysql,比如版本5.5 apt-get autoremove --purge mysql-server-5.5 ...
- linux网络编程之二-----多播(组播)编程
多播编程实例 服务器端 下面是一个多播服务器的例子.多播服务器的程序设计很简单,建立一个数据包套接字,选定多播的IP地址和端口,直接向此多播地址发送数据就可以了.多播服务器的程序设计,不需要服务器加入 ...
- 【SSH系列】深入浅出spring IOC中三种依赖注入方式
spring的核心思想是IOC和AOP,IOC-控制反转,是一个重要的面向对象编程的法则来消减计算机程序的耦合问题,控制反转一般分为两种类型,依赖注入和依赖查找,依赖什么?为什么需要依赖?注入什么?控 ...
- PHP 文件下载 浅析
无控制类型 avi文件 rar文件 mp4MP3图片等会被直接解析 核心代码 类型 长度 实现函数 优化 原始下载文件的名称 优化后的文件下载名称 总结 文件下载的功能对一个网站而言基本上是必备的了, ...
- nginx平台初识(二) 浏览器 HTTP 协议缓存机制详解
1.缓存的分类 缓存分为服务端侧(server side,比如 Nginx.Apache)和客户端侧(client side,比如 web browser). 服务端缓存又分为 代理服务器缓存 和 反 ...
- Python模块探秘 Smtplib发送带有各种附件的邮件
这两天对Python的邮件模块比较感兴趣,于是就查了查资料.同时在实际的编码过程中也遇到了各种各样的问题.下面我就来分享一下我与smtplib的故事. 前提条件 我的上一篇博文里面讲解了,发送邮件必须 ...
- Web开发学习之路--Eclipse+Tomcat+mysql之初体验
学习了一段时间android,正好要用到android和服务器之间的交互,既然要学习android,那么就涉猎下服务器端的开发了,以前学过php,用thinkphp很快可以搭建起来,但是android ...
- Centos7下Redis3.2的安装配置与JReid测试
环境 Centos7 Redis版本 3.2.0 安装目录 /usr/local/redis/redis-3.2.0 Redis的介绍 参见官网 安装 1 安装gcc与tcl # yum instal ...