概述
另一个 Blog 地址:http://insaneguy.me
原文链接:http://insaneguy.me/2015/03/30/lambda_expressions_in_cpp/
lambda 表达式是函数式编程语言中一个很 cool 的特性,而 C++11 标准加入了对 lambda 表达式的支持。本篇文章对 C++11 中的 lambda 表达式做一个简单的介绍。
什么是 lambda 表达式
说到 lambda expression 就不能不提 lambda calculus,前者是从后者中衍生出的概念,lambda calculus 有着严格的数学定义,与图灵机有着等价的计算能力。这里只介绍编程语言中的 lambda 表达式概念。
在函数式编程语言中,函数是一等公民。有时我们需要一个函数,但又不想要定义一个具有名字的函数,即我们需要一个匿名函数,而一个 lambda 表达式实际上就是通过表达式的方式定义了一个匿名函数。
C++ 中 lambda 表达式的语法规则
先通过一个简单的例子来看看 C++ 中 lambda 表达式的基本语法:
#include <iostream>
using namespace std;
int main()
{
auto foo = []() { cout << "Hello, Lambda!n"; };
foo();
// call the function
auto foo2 = [](int x)
{
cout << x << endl;
};
foo2(9);
// 9
int y = 2;
auto foo3 = [y](int x)
{
cout << x * y << endl;
};
foo3(5);
// 10
return 0;
}
[ capture ] ( params ) { body }
是 lambda 表达式的基本写法。
C++ 中 lambda 表达式以 []
开头,[]
称为 capture specification ,中括号中间的参数 capture 是我们希望捕获的外部引用参数。圆括号中的 params 是匿名函数的参数,而 body 即函数内容,符合 C++ 中一般函数的写法。
怎么用 lambda 表达式
上面的例子中我们用 auto
定义变量保存了 lambda 表达式定义的函数,然后就可以像普通函数一样进行调用。实际上还可以直接调用 lambda 表达式定义的匿名函数:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string my_name("insaneguy");
[](const string& name) { cout << "My name is " + name << endl; } (my_name);
// "My name is insaneguy"
int n = [](int x, int y) { return x + y; } (2, 3);
cout << n << endl;
// 5
return 0;
}
值得注意的是,lambda 表达式返回的实际上是一个匿名的函数对象(functor),或者说是一个仿函数实例,而不是直接一个普通函数。因此我们可以将 lambda 表达式作为参数传入 STL 算法,下面举一个 for_each
的例子:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> numbers;
for (int i = 0; i < 10; ++i)
{
numbers.push_back(i);
}
// print even numbers
for_each(numbers.begin(), numbers.end(), [](int n) {
if (n % 2 == 0)
{
cout << n << " ";
}
});
cout << endl;
return 0;
}
为什么要使用 lambda 表达式
前面举的例子都不能很好体现 lambda 表达式的优点。事实上,利用 lambda 表达式创建那些“只用一次”或者比较短小的函数非常方便和高效。
在编写 GUI (图形用户界面) 程序时,回调(callback)函数往往属于“用过就扔”的,即只在程序中与控件绑定一次,之后不会由用户显式地调用。传统的做法是把回调函数写成类的私有成员方法,现在我们可以使用 lambda 表达式来创建回调函数,既可以减少代码量,又能提高程序可读性。
使用 lambda 表达式构造事件回调(callback)
下面举 Cocos2d-x 3.x 中的控件事件监听绑定的例子。
第一种方式:通过回调函数绑定的形式添加事件监听器
HelloWorld
是游戏的主场景层,其中有一个 Label
标签控件,我们希望点击标签弹出一个对话框。
首先要在 HelloWorld
类中定义一个回调方法 HelloWorld::touchBeganHandler() :
// HelloWorldScene.h
#include "cocos2d.h"
class HelloWorld : public cocos2d::LayerColor
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// 一个处理触摸(onTouchBegan)事件的回调(callback)方法
virtual bool touchBeganHandler(cocos2d::Touch*, cocos2d::Event*);
// ...
};
// HelloWorldScene.cpp
// ...
bool HelloWorld::touchBeganHandler(Touch*, Event*)
{
// 弹出一个对话框
MessageBox("touch began", "Title");
return false;
}
然后在 HelloWorld::init()
中绑定事件监听器和回调方法:
// HelloWorldScene.cpp
// ...
bool HelloWorld::init()
{
//...
// 创建一个 Label 控件并添加到当前层中
auto label = Label::create();
label->setString("Hello Cocos2d-x");
label->setPosition(visibleSize / 2);
addChild(label);
// 为 label 创建一个事件监听器,将 label 的 onTouchBegan 事件与
// HelloWorld::touchBeganHandler() 回调方法进行绑定
auto listener = EventListenerTouchOneByOne::create();
// 利用 Cocos2d-x 中的宏绑定事件监听器与回调方法
listener->onTouchBegan =
CC_CALLBACK_2(HelloWorld::touchBeganHandler, this);
// 为 label 控件添加事件监听器
Director::getInstance()->getEventDispatcher()->
addEventListenerWithSceneGraphPriority(listener, label);
//...
return true;
}
第二种方式:使用 C++ lambda 表达式的形式添加事件监听器
// HelloWorldScene.cpp
// ...
bool HelloWorld::init()
{
//...
// 创建一个 Label 并添加到当前层中
auto label = Label::create();
label->setString("Hello Cocos2d-x");
label->setPosition(visibleSize / 2);
addChild(label);
// 创建一个事件监听器,通过 lambda 表达式创建一个匿名回调方法
// 并与 listener 的 onTouchBegan 进行绑定
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = [](cocos2d::Touch*, cocos2d::Event* )
{
MessageBox("touch began", "Title");
return false;
};
//...
return true;
}
直观上感受,使用 lambda 表达式的版本代码更少,逻辑清晰,代码的可读性高。
进一步来看,上面的例子中我们只为一个 Label
控件绑定了事件回调,该回调函数只“用”了一次,不需要重用,使用 lambda 表达式不需要额外为 HelloWorld
类添加一个成员方法,减少了类的名字空间被“污染”的可能性。
使用 lambda 表达式还可以帮助我们写出符合 DRY (Don’t Repeat Yourself) 的代码。
使用 lambda 表达式编写 DRY 的代码
现在我们的 HelloWorld
场景中有四个控件,我们希望为这四个控件编写相同的事件处理。考虑如下代码:
// HelloWorldScene.h
#include "cocos2d.h"
class HelloWorld : public cocos2d::LayerColor
{
public:
//...
private:
cocos2d::TextFieldTTF *aTf, *bTf;
// 文本输入框控件
cocos2d::Label *aLabel, *bLabel;
// 文本标签控件
};
// HelloWorldScene.cpp
//...
// 为控件添加事件监听器的方法
void HelloWorld::addListeners()
{
auto director = Director::getInstance();
// 获取 Director 类实例
// 使用 lambda 表达式实现 onTouchBegan 事件回调方法
// 暂时未编写事件处理代码
auto handler = [](Touch *t, Event *e) {
return false;
};
// 为输入框 aTf 添加事件监听
auto aTfClickListener = EventListenerTouchOneByOne::create();
aTfClickListener->onTouchBegan = handler;
director->getEventDispatcher()->
addEventListenerWithSceneGraphPriority(aTfClickListener, aTf);
// 为输入框 bTf 添加事件监听
auto bTfClickListener = EventListenerTouchOneByOne::create();
bTfClickListener->onTouchBegan = handler;
director->getEventDispatcher()->
addEventListenerWithSceneGraphPriority(bTfClickListener, bTf);
// 为 aLabel 添加事件监听
auto aLabelListener = EventListenerTouchOneByOne::create();
aLabelListener->onTouchBegan = handler;
director->getEventDispatcher()->
addEventListenerWithSceneGraphPriority(aLabelListener, aLabel);
// 为 bLabel 添加事件监听
auto bLabelListener = EventListenerTouchOneByOne::create();
bLabelListener->onTouchBegan = handler;
director->getEventDispatcher()->
addEventListenerWithSceneGraphPriority(bLabelListener, bLabel);
}
HelloWorld::addListeners()
方法中为不同控件添加事件监听的代码出现了明显的重复现象,我们可以使用 lambda 表达式来优化:
// HelloWorldScene.cpp
//...
void HelloWorld::addListeners()
{
auto director = Director::getInstance();
// 获取 Director 类实例
// 使用 lambda 表达式实现 onTouchBegan 事件回调函数
// 暂时未编写事件处理代码
auto handler = [=](Touch *t, Event *e) {
return false;
};
// 使用 lambda 表达式为控件添加事件监听
auto addListenerToTarget = [director, handler](Node *target) {
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = handler;
director->getEventDispatcher()->
addEventListenerWithSceneGraphPriority(listener, target);
};
addListenerToTarget(aTf);
addListenerToTarget(bTf);
addListenerToTarget(aLabel);
addListenerToTarget(bLabel);
}
现在看起来舒服多了~
注意上面的代码中 auto handler = [=](Touch *t, Event *e) {/*...*/}
的 [=]
表示通过拷贝方式捕获所有外部引用变量。关于 lambda 表达式捕获外部变量的语法有如下几种:
[]
: 不捕获任何外部变量[&]
: 通过引用方式捕获所有外部变量[=]
: 通过拷贝方式捕获所有外部变量[=, &foo]
: 通过引用方式捕获foo
变量,其他外部变量通过拷贝方式捕获[bar]
: 通过拷贝方式捕获bar
变量[this]
: 捕获当前类的this
指针
参考资料
- Lambda Functions in C++11 - the Definitive Guide
- Lambda表达式的示例-MSDN
最后
以上就是甜甜小蝴蝶为你收集整理的C++ 中的 Lambda 表达式的全部内容,希望文章能够帮你解决C++ 中的 Lambda 表达式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复