1. 基本概念
2. 语法逻辑
2.1 语义规定
2.2 基本语法
2.3 传递Lambda函数
3. 浅谈函数式编程
1. 基本概念
Lambda函数,亦称为Lambda表达式、匿名函数等,是一种函数对象,Lambda函数可以让函数像普通变量一样进行赋值、传递、函数返回等操作。C++中的Lambda函数经常用来解决如下问题:
- 使得程序更加简洁,尤其对于一次性使用的函数。
- 使得函数可以*流动,就像变量一样,这给函数式编程模式奠定了语法基础。
2. 语法逻辑
2.1 语义规定
[capture list](params list)mutable exception → return type{functionbody}
解析:
- captrue list: 外部变量列表
- params list: 形参列表
- mutable: 是否可以修改外部变量。
- 默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。
- 在使用mutable时,形参列表不可省略
- exception: 异常设定
- return type: 返回类型
- 可以不需要声明返回值,此时返回类型相当于使用decltyp根据返回值推断得到
- function body: 函数体
2.2 基本语法
下面用一些简明的例子,说明如何编写并使用Lambda表达式:
外部参数捕获
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int a = 1;
int b = 2;
int sum = [a, b]()->int{return a+b;}();
cout << "sum: " << sum << endl;
return 0;
}
在上述代码中,从左到右逐个解释:
- Lambda函数是:
[a,b]()->int{return a+b;}
- a和b是从外部可见作用域中捕获的变量,默认它们在Lambda函数内部是只读的。
-
()
圆括号内是Lambda函数的形参列表,此处为空,但不可省略。- Lambda函数的形参列表一般用于跟容器类结合的时候。
-
->int
是Lambda函数的返回值类型,可以省略。 -
{}
内部就是Lambda函数的具体代码实现。
请注意,最右边的圆括号 ()
是对Lambda函数调用的符号,不是Lamdba函数本身,我们可以将此 Lambda 函数当做一个对象,赋值给另一个具名的函数对象,再去调用它来更清楚地看到Lambda函数的本体:
// 将匿名的Lambda函数,赋值给一个名叫f的函数对象
auto f = [a, b]()->int{return a+b;};
// 两种等价的调用函数对象的形式:
int sum = [a, b]()->int{return a+b;}();
int sum = f();
以引用方式捕获外部参数
上述例子捕获的 a
和 b
都是只读的,在Lambda函数内部不可修改,但如果有需要修改这些外部参数,则需要将捕获列表改为引用模式:
[&a, b]()->int{a=100; return a+b;}();
上述代码中:
-
&a
代表以引用模式捕获变量a,此时可以在Lambda函数体内修改变量a
捕获作用域内所有变量
如果作用域内的变量较多,一个个填写到Lambda外部参数列表显然比较麻烦,此时可以用如下代码来一次性全部导入:
// 以传值模式(只读)捕获所有外部变量
[=]()->int{...};
// 以引用模式(读写)捕获所有外部变量
[&]()->int{...};
给Lambda函数传参
就像普通函数一样,可以给Lambda函数传参,比如:
```c++
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int a = 1;
int b = 2;
// 定义一个具有两个int形参的Lambda函数
// 并将Lambda函数赋值给函数对象sum(相当于起了个名字以便于后续显式调用)
auto sum =[](int x,int y){return x+y;};
// 调用函数对象
cout << sum(a, b) << endl;
return 0;
}
请注意,上述代码仅仅是为了讲解Lambda函数的形参列表的语法逻辑,而并不是Lambda函数的一般典型用法,因为上述例子已经违背了匿名函数的初衷 —— 将Lambda函数赋值给了一个具名函数对象sum,这么做毫无意义,如果非要定义一个具名函数的话,那么完全可以直接编写一个普通的函数 sum
来达到此功能。
上述代码之所以这么写的原因是,是需要举一个例子来说明Lambda函数的形参的传递过程,而在没有涉及STL容器及其配套算法库函数之前,我们没有用过能被自动调用的函数,因此我们需要一个语句来显式地调用函数,因此就需要一个具名的函数对象sum来承接Lambda函数的功能。
那么,Lambda函数形参列表的典型场景是怎样的呢?这里可以举STL算法库中的最简单的一例加以说明,如果需要对STL容器及其算法库有进一步的了解,请查阅相关课件。
#include <array>
#include <algorithm>
using namespace std;
int main(int argc, char const *argv[])
{
// 定义一个静态数组arr(这是一种STL容器,简单理解为一维数组即可)
array<int, 5> arr = {1,2,3,4,5};
// 使用算法库函数 count_if() 计算 arr 中的偶数数目
int num = count_if((), (), [](int m){return m%2==0;});
cout << "偶数数目是: " << num << endl;
return 0;
}
说明:
- 上述代码中,Lambda函数是
[](int m){return m%2==0;}
,基本含义是:- 不捕获任何外部参数
- 接受一个
int
型参数,注意此时形参将由算法库函数count_if()
自动传递给Lambda函数 - 当形参
m
为偶数时,结果为真
-
count_if()
是C++算法库函数- 其基本功能是将容器指定范围内的元素,逐个地传递给Lambda函数进行检测
- 累计所有检测结果为真的元素,并返回这个累计数目
- 指定的范围由半开半闭区间
[ (), () )
规定,其中的()
和()
是分别指向容器的迭代器(可看做广义指针),用来界定范围
这样一来就很清楚了,我们利用Lambda匿名函数逐个接收count_if
传递过来的参数(即int m
,因为容器arr中的元素类型都是int型整数),并判断其是否为偶数,以期达到计算容器中偶数数目的最终目的。
当然,算法库的函数所能接收的函数符(functor)不一定是Lambda匿名函数,Lambda匿名函数只是函数符的其中一种形式,它们还可以是普通的函数指针,还有更常见的类函数对象,这些知识点将在STL容器及算法库相关章节做进一步说明。
1. 基本概念
2. 语法逻辑
2.1 语义规定
2.2 基本语法
2.3 传递Lambda函数
3. 浅谈函数式编程
2.3 传递Lambda函数
Lambda匿名函数被设计为一种对象,这意味着可以将它们作为函数的参数、返回值,有时,那些使用了Lambda函数作为参数、返回值的函数,被称为高阶函数。
Lambda表达式参数
将Lambda表达式作为函数参数,在上述 count_if()
求偶数数目的例子中已有体现:
count_if((), (), [](int m){return m%2==0;});
此处,Lambda表达式 [](int m){return m%2==0;}
就做为第三个参数传递给了 count_if
。
Lambda表达式返回值
对于Lambda表达式,作为函数返回值与其他普通的对象语法上没有太大区别,例如:
auto f(int a)
{
return [=](int b){cout << a+b << endl;};
}
int main()
{
f(1)(3);
}
1. 基本概念
2. 语法逻辑
2.1 语义规定
2.2 基本语法
2.3 传递Lambda函数
3. 浅谈函数式编程
3. 浅谈函数式编程
注意,掌握了以上语法细节,并不意味着掌握了Lamba匿名函数,也不意味着掌握了函数式编程思维,Lambda函数最重要的设计目的之一,是给一种编程范式、一种思考角度提供语法方案,这种范式和思维方式就是函数式编程。
函数式编程是一种继过程性编程、面向对象编程之后的新的编程范式,涉及的概念很多,比如上述Lambda函数中的 f(1)(2)
实际可理解为函数的柯里化,还有诸如纯函数、只读数据、高阶函数、函子、单子等概念,是独立的一个编程领域,详情可查阅函数式编程相关课件。
↓↓↓↓↓↓↓↓↓↓ 以下不可见 ↓↓↓↓↓↓↓↓↓↓
要弄清楚 函数式编程
的基本思想,首先要弄清楚与之相对应的 命令式编程
,前者强调的是关联数据的映射,而后者关注的是解决问题的步骤。
举例子
给定一个字符串 "abcdefg"
,要求将其翻转为 "gfedcba"
,这并不是什么难题,有各种不同的解法,来看下命令式编程思维是怎么思考的?
void revert(char s[], int len)
{
// 太短,走人
if(len <= 1)
return;
// 首尾元素互换
swap(s[0], s[len-1]);
// 中间整体翻转
revert(s+1, len-2);
}
上述代码是一段典型的递归算法,其思路是分解了解决翻转问题的各个步骤,即:
- 首先,如果字符串长度太短,则无需翻转,算法结束
- 否则,就对调首尾两个字符
- 最后,将中间的若干个字符翻转
这种思路是命令式的,也就是过程性的,将注意力放在了如何构建一个通达目标的解题路径,一步两步三步,最终完成任务。
那函数式编程思维会怎么解这道题呢?函数式思维的注意力主要放在数据的映射上,即设计出一个从 旧 数据向 新 数据变化的映射关系(即函数):
char * revert(char s[], int len)
{
// 数据量太少,无法映射
if(len <= 0)
return;
char *rs = new char[len];
for(int i; len)
{
rs[i] = s[len-i];
}
return rs;
}
本文不是我写的,谢谢。主要用于本人学习。