[C++11札记]: lambda函数

作为一名C++程序员,看到如下代码你会是什么反应?

int main() {
    int girls = 3, boys = 4;
    auto totalChild = [](int x, int y)->int{ return x + y; }
    return totalChild(girls, boys);
}

这段代码是不是和我们平常所见的C++代码有点不同?对,这就是C++ 11最新增加的特性:lambda函数。

说到lambda表达式,可能对于Javascript、Python程序员并不陌生。lambda函数是一种匿名函数,比如上面的例子中,我们定义了一个lambda函数,该函数接受两个参数(int x, int y),并返回其和。直观的看,lambda函数跟普通函数相比不需要定义函数名,此外还采用了追踪返回类型的方式声明其返回值,其余看起来和普通函数定义一样。

看到这里,你可能会认为,lambda函数也没有什么特别的,真是这样么?

为什么需要lambda函数

我们还是从一个例子入手,假设你有一个地址簿类,希望添加一个搜索功能。简单而言,你可以实现一个搜索函数,以字符串作为参数,返回所有匹配该字符串的地址。大部分情况下,这个实现可以满足用户需求。问题是,如果用户只想搜索域名,或者只在用户名中进行搜索,并忽略域名中的结果呢?

用户的需求可是千奇百怪,每个用户感兴趣的东西各不相同。与其将所有这些选项构建到类中,不如提供一种通用的“查找”方法,它可以由调用程序来决定搜索什么内容。假设这个搜索方法名为findMatchingAddresses方法,它使用一个“函数”或“类似函数”的对象作为参数。

#include <string>
#include <vector>

class AddressBook
{
    public:
    // 使用模板可以忽略掉函数子、函数指针和lambda之间的差异
    template<typename Func>
    std::vector<std::string> findMatchingAddresses (Func func)
    { 
        std::vector<std::string> results;
        for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )
        {
            // 调用传递给findMatchingAddresses的函数,看是否匹配
            if ( func( *itr ) )
            {
                results.push_back( *itr );
            }
        }
        return results;
    }

    private:
    std::vector<std::string> _addresses;
};

任何人都可以将函数传递给findMatchingAddresses,该函数包含查找特定内容的逻辑。如果给定某个地址时,该函数返回true,则该地址将被返回。在早期版本的C++中,这种方法是可行的。但该方案有一个缺点:创建函数不够方便。您必须在别的地方先定义它,只是为了能够简单的使用它。

这个时候lambda函数就可以派上用场。

lambda函数如何改进我们的示例

让我们看看如何将lambda函数应用到我们的地址簿示例中,首先创建一个简单函数来查找包含“.org”的电子邮件地址。

AddressBook global_address_book;

vector<string> findAddressesFromOrgs ()
{
    return global_address_book.findMatchingAddresses( 
        // 这儿声明了一个lambda, [] 标记开始
        [] (const string& addr) { return addr.find( ".org" ) != string::npos; } 
    );
}

这里,lambda函数作为参数传递给findMatchingAddresses,每个循环都通过findMatchingAddresses调用lambda函数,该lambda函数会检查它是否包含“.org”。

通过lambda函数,我们不用事先声明函数原型,对于简单的功能可以减少代码的书写。

然而,lambda函数更强大的功能其实在于变量捕捉。

lambda变量捕捉

假设您想创建一个小小的函数来查找包含特定名称的地址。你可以这样写:

// read in the name from a user, which we want to search
string name;
cin >> name;
return global_address_book.findMatchingAddresses( 
    // notice that the lambda function uses the the variable 'name'
    [&] (const string& addr) { return addr.find( name ) != string::npos; } 
);

是不是很神奇?lambda函数可以直接使用name变量,根据以往的经验,在函数体中使用外部变量,要么通过参数传递,要么定义成全局变量。而通过lambda变量捕捉,一方面简化了代码,另一方面避免使用全局变量,这才是lambda函数的真正价值。

什么是lambda函数

通常情况下,lambda函数的语法定义如下:

[capture](parameters) mutable -> return-type { statement }

其中:

  • [capture]: 捕捉列表。它总是出现在lambda函数的开始处,编译器根据[]引出符判断接下来的代码是否lambda函数。捕捉列表能够捕捉上下文中的变量以供lambda函数使用。语法上,捕捉列表由多个捕捉项组成,并以逗号分割。捕捉列表有如下几种形式:

    • [var] 表示值传递方式捕捉变量var。
    • [=] 表示值传递方式捕捉所有父作用域的变量(包括this)。
    • [&var] 表示引用传递捕捉变量var。
    • [&] 表示引用传递捕捉所有父作用域的变量(包括this)。
    • [this] 表示值传递方式捕捉当前的this指针。
  • (parameters): 参数列表。与普通函数的参数列表一样。如果不需要参数传递,则可以连同括号()一起省略。
  • mutable: mutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,即使参数为空也不可省略参数列表。
  • ->return-type: 返回类型。不需要返回值的时候,可以连同符号->一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。
  • {statement}: 函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所捕获的变量。

在lambda函数的定义中,参数列表和返回类型都是可选的部分,而捕捉列表和函数体都可能为空。在极端情况下,C++ 11中最为简略的lambda函数为:

[]{};
lambda和STL

lambda对C++11最大的贡献,应该在STL库中,更具体的说,就是使用STL的算法更加容易,也更加容易学习。比如STL中最常见的算法for_each,比较一下以下两种写法:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )
{
    cout << *itr;
}

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for_each( v.begin(), v.end(), [] (int val)
{
    cout << val;
} );

采用lambda函数的代码更加优雅 - 它就像一个普通的循环,但是能够利用for_each提供的优点,例如,可以保证有正确的结束条件。但是,这会不会导致性能下降?事实证明,for_each具有相同的性能,有时甚至比普通的for循环更快。原因是,它可以利用循环展开。

总结

总体来说,lambda函数被设计的目的,就是要就地书写,就地使用。使用lambda的程序员,更倾向于在一个屏幕里看到所有的代码,而不是依靠代码浏览工具在文件间找到函数的实现。而在封装的思维层上,lambda只是一种局部的封装,以及局部的共享。从软件开发的角度看,以lambda概念为基础的”函数式编程” (Functional Programming) 有着面向对象编程(Object-orientated Programming) 同样的地位。关于lambda的更多实例和使用方法,可以参考《深入理解C++11:
C++11新特性解析与应用》这本书。

参考

image

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云水木石

但行好事,莫问前程

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值