C++11/C++14 中的 Lambda 与 Objective-C 中的 Block
当年上大学我们专业分 C++ (包括 .NET ) 和 Java 两个方向,而我原本是 C++ 方向的,后来阴差阳错走上了 Java -> J2EE -> Android 这条“不归路”,以至于不少当年的同学都以为我一开始是 Java 方向的。
不过当年因为课程设置的原因,《面向对象程序设计( C++ )》和《面向对象程序设计( Java )》是相对应的,都过分强调面向对象那一块,除此之外印象比较深的就只有运算符重载这一块了。
12 年学 Cocos2d-X 游戏开发,总算又有机会重拾 C++,但依然没有去系统的温习 C++ 语言本身。
现在主要写 Android / iOS / Golang,虽然很少直接接触 C++,但涉及到 [音视频和图形图像处理] 、[后端网络服务和数据存储基础设施] 这两大块领域,还是离不开 C++。
最近一个月“工作量不饱和”,忙里偷闲通过《 C++ Primer 第 5 版》重新温习了一遍 C++。写的 Demo 在这里。
着重看了以前没怎么接触过的 template
这块内容,以及众多 C++11 新特性:auto
自动类型推导、shared_ptr
/ unique_ptr
/ weak_ptr
智能指针、Lambda 、Enum Class
强类型枚举等。
而印象最深的就是 Lambda 这块内容,可能是因为前不久刚刚学习过 Haskell 这门纯函数式编程语言。
C++11 开始引入 Lambda ,前不久刚发布的 C++14 又加入了一些新特性。
而 Objective-C 中通过 Block 也提供了类似的支持。
C++11 中的 Lambda
使用 Lambda 需要 #include <functional>
,用 GCC 或 Clang 编译时需要加入参数 -std=c++11
。
定义 Lambda
Lambda 的类型为 std::function
, 其一般表达形式为:
[capture](parameters) -> return_type { function_body }
如果没有参数, parameters
这部分可以省略。
return_type
这部分也可以省略: 如果 function_body
中包含了 return
语句,则可以推导出返回类型;如果没有,则默认返回类型为 void
。个人认为最好还是不要省略,这样能增强代码可读性。
例如定义一个传入两个 double
类型参数、返回值也为 double
类型的 Lambda , 其类型应为 std::function<double(double, double)>
:
std::function<double(double, double)> multiply;
multiply = [] (double x, double y) -> double {
return x * y;
};
Objective-C 中 Block 的定义基本是与此类似的:
double (^multiply)(double x, double y);
multiply = ^(double x, double y)
{
return x * y;
};
而考虑到 C++11 中引入了 auto
自动类型推导,上面的 C++ 版本的 Lambda 定义还可以更简洁:
auto multiply = [] (double x, double y) -> double {
return x * y;
};
捕获外部变量
C++ 中 Lambda 访问外部变量的几种情况:
[]
: 不使用任何外部变量;[=]
: 通过拷贝的方式使用外部所有变量;[var]
: 通过拷贝的方式使用外部变量var
;[&]
: 通过引用的方式使用外部所有变量;[&var]
: 通过引用的方式使用外部变量var
;[=, &var]
: 除了var
通过引用的方式使用,其他所有外部变量均通过拷贝的方式使用;[&, var]
: 除了var
通过拷贝的方式使用,其他所有外部变量均通过引用的方式使用;
如果通过引用的方式使用的外部变量 var
不是 const
的,则可以在 Lambda 中修改它的值。
下面定义一个求圆面积的 Lambda ,它需要访问并修改外部变量 pi
:
double pi = 3.14;
auto circleSpace = [=, &pi] (double radius) -> double {
pi = 3.1415926535;
return pi * radius * radius;
};
std::cout << "Result: " << circleSpace(1.23456789) << std::endl;
此外,如果要在 Lambda 中改变外部变量的值,还可以通过在参数列表后添加 mutable
关键字来实现:
auto circleSpace = [=] (double radius) mutable -> double {
pi = 3.1415926535;
return pi * radius * radius;
};
而在 Objective-C 的 Block 中,如果需要修改外部变量,需要在该变量前面加上关键字 __block
:
__block double pi = 3.14;
double (^circleSpace)(double radius);
circleSpace = ^(double radius)
{
pi = 3.1415926535;
return pi * radius * radius;
}
NSLog(@"Result: %f", circleSpace(1.23456789));
Lambda 的其他用法
C++ 中的 for_each
第三个参数可以为一个 callable 对象,因此使用 Lambda 可以更方便地遍历 vector
等容器对象:
std::vector<char> v;
for (int i = 0; i < 26; i++) {
v.push_back((char)('A' + i));
}
for_each(v.begin(), v.end(), [] (char c) -> void {
std::cout << c << std::endl;
});
C++14 中的 Lambda 新特性
如果使用了 C++14 中的 Lambda 新特性,GCC / Clang 编译参数需要加上 -std=c++1y
。
Generic Lambda
Lambda 的参数类型也可以声明为 auto
了!相当于泛型。
依然以上面的 multiply
为例:
auto multiply = [] (auto x, auto y) {
return x * y;
};
std::cout << multiply(-1234, 5.6789) << std::endl;
std::cout << multiply('A', 'Z') << std::endl;
Initialized Lambda Captures
Capture 语句支持初始化表达式了!
假设有一个 unique_ptr
类型的指针,如果要在 Lambda 内部访问它,就必须先用 move
转移所有权,或者用 release()
释放所有权。现在,你可以直接将这一步放在 Capture 语句中完成:
std::unique_ptr<std::string> nameP(new std::string("Rinc"));
auto readName = [nameP = move(nameP)] {
return *nameP;
};
/*
auto readName = [nameP = nameP.release()] {
return *nameP;
};
*/
std::cout << readName() << std::endl;
当然,用 ,
分隔的多个初始化表达式作为 Capture 也是可以的:
std::shared_ptr<std::string> str1, str2;
auto print = [
str1 = std::make_shared<std::string>("Rinc"),
str2 = std::make_shared<std::string>("Liu")
] () {
std::cout << *str1 << " " << *str2 << std::endl;
};
print();