C++教程之lambda表达式一

什么是Lambda?

C++ 11加入了一个非常重要的特性——Lambda表达式。营里(戴维营)的兄弟都对Objective-C很熟悉,许多人多block情有独钟,将各种回调函数、代理通通都用它来实现。甚至有人选择用FBKVOController、BlocksKit等开源框架将KVO、控件事件处理都改为通过block解决。原因就是简单、方便、直观,函数的定义和使用出现在同一个地方。这里的Lambda表达式实际上和block非常类似,当然如果你用它和Swift语言的闭包比较,那就是一回事了。下面先看几个Lambda和block的示例代码。

1 .Objective-C的block示例代码,使用^表示block类型,总体来说与函数指针的定义类似。

#import <Foundation/Foundation.h>

int main()
{
        void (^block)() = ^void() {
                NSLog(@"In block");
        };

        block();
        return 0;
}

编译运行:

$ clang main.m -framework Foundation
$ ./a.out 
2015-01-28 14:17:52.763 a.out[9901:165707] In block

2 .Swift的闭包,参数列表、返回值类型等都写在花括号{}内部。

let closure = {
        () -> Void in
        println("In swift")
}

closure()

编译运行:

$ swiftc main.swift 
$ ./main 
In swift

测试Swift也可以直接使用Playground或REPL(Read-Eval-Print-Loop)环境。

3 .C++的Lambda表达式。

#include <iostream>

int main()
{
        auto lambda = []() -> void{
                std::cout << "In lambda" << std::endl;
        };
        lambda();
        return 0;
}

编译运行,注意使用C++ 11的特性进行编译:

$ clang++ main.cpp -std=c++11
$ ./a.out 
In lambda

Lambda、block实际上都是一个闭包(closure),它们都类似于一个匿名的函数,但是拥有捕获所在作用域中变量的能力;能够将函数做为对象一样使用。通常用它们来实现回调函数、代理等功能。

Lambda使用方法

1 .基本形式

语法 序号
[ 捕获列表 ] ( 形参数列表 ) mutable(可选) 异常属性 -> 返回值类型 { 函数体 } (1)
[ capture-list ] ( params ) -> ret { body } (2)
[ capture-list ] ( params ) { body } (3)
[ capture-list ] { body } (4)
  • (1)为完整的形式,包含变量捕获列表、形参列表、可变属性(可选)和返回值类型等。
  • (2)省略了mutable,表示Lambda不能修改捕获的变量。
  • Lambda的返回值类型如果可以由函数体中的实际返回值推导出,可以省略。
  • 如果没有形参,可以省略圆括号。

2 .捕获列表

lambda表达式中可以获取(捕获)它所在作用域中的变量值,并且有两种捕获方式:引用和值。我们可以在捕获列表中设置各变量的捕获方式。如果没有设置捕获列表,lambda默认不能捕获任何的变量,这点与block不同。

#include <iostream>

int main()
{
        int a = 123;

        auto lambda = []()->void{
                std::cout << "In lambda: " << a << std::endl;
        };
        lambda();

        return 0;
}

编译运行:

$ clang++ main.cpp -std=c++11
main.cpp:8:33: error: variable 'a' cannot be implicitly captured in a lambda
      with no capture-default specified
                std::cout << "In lambda: " << a << std::endl;
                                              ^
main.cpp:5:6: note: 'a' declared here
        int a = 123;
            ^
main.cpp:7:16: note: lambda expression begins here
        auto lambda = []()->void{
                      ^
1 error generated.

[]中设置捕获列表,就可以在lambda中使用变量a了,这里使用按值(=, by value)捕获。

#include <iostream>

int main()
{
        int a = 123;

        auto lambda = [=]()->void{
                std::cout << "In lambda: " << a << std::endl;
        };
        lambda();

        return 0;
}

编译运行:

$ clang++ main.cpp -std=c++11
$ ./a.out 
In lambda: 123

捕获列表的设置方式:

|设置方式|结果| |-|-| |[]|不捕获| |[=]|全部按值捕获| |[&]|全部按引用捕获| |[a, b]|按值捕获变量a和b| |[&a, b]|按引用捕获a,按值捕获b| |[&, a]|按值捕获a,其它变量全部按引用捕获| |[=, &a]|按引用捕获a,其它全部按值捕获|

注意: 捕获列表没有先后顺序;捕获列表中的变量不能出现重复申明,比如[&, &a]&中已经包含了&a的申明了,因此不能再出现&a

具体的捕获类型,可以通过打印变量地址进行查看。

  • 按值捕获:
auto lambda = [=]()->void{
    std::cout << "In lambda: " << &a << std::endl;
};

运行结果为:

$ clang++ main.cpp -std=c++11
$ ./a.out 
0x7fff555c69b8
In lambda: 0x7fff555c69b0
  • 按引用捕获:
auto lambda = [&]()->void{
    std::cout << "In lambda: " << &a << std::endl;
};

运行结果为:

$ clang++ main.cpp -std=c++11
$ ./a.out 
0x7fff58a9b9b8
In lambda: 0x7fff58a9b9b8

3 .可变类型(mutable)

按值传递到lambda中的变量,默认是不可变的(immutable),如果需要在lambda中进行修改的话,需要在形参列表后添加mutable关键字(按值传递无法改变lambda外变量的值)。

#include <iostream>

int main()
{
        int a = 123;
        std::cout << a << std::endl;

        auto lambda = [=]() mutable ->void{
                a = 234;
                std::cout << "In lambda: " << a << std::endl;
        };
        lambda();

        std::cout << a << std::endl;

        return 0;
}

编译运行结果为:

$ clang++ main.cpp -std=c++11
lishan:c_study apple$ ./a.out 
123
In lambda: 234  #可以修改
123             #注意这里的值,并没有改变

如果没有添加mutable,则编译出错:

$ clang++ main.cpp -std=c++11
main.cpp:9:5: error: cannot assign to a variable captured by copy in a
      non-mutable lambda
                a = 234;
                ~ ^
1 error generated.

4 .返回值类型。

lambda的返回值类型可以省略,编译器会根据实际返回值的类型自动推导。

#include <iostream>

int main()
{
        int a = 123;
        std::cout << a << std::endl;

        auto lambda = [=]() mutable{
                a = 234;
                std::cout << "In lambda: " << a << std::endl;
                return a;
        };
        int b = lambda();

        std::cout << b << std::endl;

        return 0;
}

编译运行:

$ clang++ main.cpp -std=c++11
$ ./a.out 
123
In lambda: 234
234

参考资料

  1. https://msdn.microsoft.com/en-us/library/dd293603.aspx
  2. http://www.oracle.com/technetwork/articles/servers-storage-dev/howto-use-lambda-exp-cpp11-2189895.html
  3. http://en.cppreference.com/w/cpp/language/lambda
  4. http://www.cprogramming.com/c++11/c++11-lambda-closures.html

本文档由长沙戴维营教育整理。

标签: , ,
文章分类 C++开发教程

发表评论