C++新特性——lambda函数

简单的看了lambda函数,感觉跟三目运算符有些类似,都是只需要进行一些简单的计算,不需要具体的函数名,是一个匿名函数,即没有函数名的函数。

lambda的函数模型为:[&count] (int x) {count += (x % 13 ==0)};

比较函数指针、函数符和Lambda函数:
1. 函数指针
先看一个示例,假如要生成一个随机整数列表,并判断其中多少个整数被3整除,多少个整数可被13整除。
生成这样的列表很简单,一种方案是使用vector存储数字,并使用STL算法generate()在其中填充随机数。

#include <vector>
#include <cmath>
#include <algorithm>

std::vector<int> numbers(1000);
std::generate(numbers.begin(), numbers.end(), std::rand);

函数generate()接收一个区间(有两个参数指定),并将每个元素设置为第三个参数返回的值,而第三个参数是一个不接受任何参数的函数对象。在上述示例中,该函数对象是一个指向标准函数rand()的指针。

通过使用count_if()算法,很容易计算出有多少个数可被3整除,函数前两个参数应指定区间,而第三个参数是一个返回true或false的函数对象。函数count_if()计算这样的元素数,即它使得指定的函数对象返回true。为判断元素是否能被3整除,可使用下面的函数定义

bool f3(int x) {return x %3 == 0;}

同样,为判断元素能否被13整除,可以使用函数定义:

bool f13(int x) {return x % 13 == 0;}

定义完上述函数之后,就可以计算符合条件的元素数了,如下:

int count3 = count_if(numbers.begin(), numbers.end(), f3);
cout<<"count of numbers divisble by 3:"<<count3<<endl;

int count13 = count_if(numbers.begin(), numbers.end(), f13);
count<<"count of numbers divisble by 13:"<<count13<<endl;

2. 函数符
下面看一下怎么使用函数符来完成这个任务。函数符是一个类对象,并非只能想函数名那样使用。这要归功于类方法operator()()。就这个示例而言,函数符的优点之一是可使用同一个函数符来完成这两项计数任务。下面是一种定义:

class f_mod
{
private:
	int dv;
public:
	f_mod(int x = 1):dv(x);
	bool operator()(int x){return x % dv == 0;}
}

可以使用构造函数创建存储特定整数值的f_mod对象

f_mod obj(3); //f_mod.dv set to 3`

而这个对象可以使用方法ooerator()来返回一个bool值:

bool is_div_by3  = obj(7); //same as obj.operator()(7)

构造函数本身可用作注入count_if()等函数的参数:
count3 = std::count_if(numbers.begin(), numbers.end(), f_mod(3));
参数f_mod(3)创建一个对象,它存储了3,而count_if()使用该对象来调用operator()(),并将参数x设置为numbers的一个元素。要计算有多少个数字可被13整除,只需将第三个参数设置为f_mod(13)

3. lambda函数
lambda名称来自于lambda calculus(λ演算)而得名——一种定义和应用函数的数学系统。这个系统能让人使用匿名函数——即无需给函数命名。在C++11中,对于接受函数指针或函数符的函数可以使用匿名函数定义(lambda)作为其参数。与上面函数f3对应的lambda如下:

[](int x){return x % 3 == 0;}

这与函数f3的定义很像:bool f3(int x) { reruen x % 3 == 0;}
其中有两处不同:第一个是使用[]代替函数名(匿名的由来),第二个是没有声明返回类型。返回类型相当于使用decltyp根据返回值的类型推断得到的,这里为bool。如果lambda不包含返回语句,推断出的返回类型将为 void。这个视力还可以使用一下的方法使用该lambda:

count3 = count_if(numbers.begin(), numbers.end(), [](int x){return x % 3 == 0;});

也就是说,使用整个lambda表达式提花函数指针或函数符构造函数。
仅当lambda表达式完全由一条返回语句组成是,自动类型推断才管用。否则需要使用新增的返回类型后置语法:

[](int x)->double{double y = x; return y % 3 == 0;};  //return type is double

为何使用lambda
使用lambda的原因由四条:距离、简洁、效率和功能
很多程序员细化配置两个显示器(甚至三个),这样有个好处就是使用其他位置的代码不需要切换页面去看(分散精力,个人很讨厌),让函数定义与使用的地方附近这会更舒服,无需翻页浏览不同页面的源代码。
从简洁的角度看,函数符代码比函数和lambda更繁琐。函数和lambda的简介程度相当,一个是显而易见的例外是,需要使用同一个lambda两次:

count1 = std::count_if(n1.begin(), n1.end(), [](int x){return x % 3 == 0;});
count2 = std::count_if(n2.begin(), n2.end(), [](int x){return x % 3 == 0;});

其实也不需要编写lambda两次,可以给lambda指定一个名称,并使用该名称两次:

auto mod3 = [](int x){return x % 3 == 0;}	//mod3 a name for the lambda
count1 = std::count_if(n1.begin(), n1.end(), mod3);
count2 = std::count_if(n2.begin(), n2.end(), mod3);

甚至还可以像使用常规函数那样使用lambda:

bool result = mod3(z);	// result is true if z % 3 == 0

然而不同于常规函数,可在函数内部定义有名称的lambda。mod3的实际类型随实现而异,它取决于编译器使用什么类型来跟踪lambda。
这三种方法相对效率取决于编译器内联的东西。函数指针方法阻止了内联,因为编译器传统上不会内联其地址被获取的函数,因为函数地址的概念意味着非内联函数。而函数符和lambda通常不会阻止内联。

lambda捕获列表:

[]不捕捉任何变量
[&]捕捉外部作用域所有变量,并作为引用在函数体内使用,函数体内引用的变量可修改(仅限被引用的变量)
[=]捕捉外部作用域所有变量 ,在函数体内作为副本被使用,拷贝的副本在匿名函数体内只是可读的
[=,&foo]按值捕获外部作用域中所有变量,并按照引用捕获外部变量foo
bar按值捕获bar变量,同时不捕获其他变量
&bar按引用捕获bar变量,同时不捕获其他变量
this捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,默认添加此选项

例如:

class Test
{
public:
	int m_number;
public:
	Test(int num = 100){m_number = num;}
	void example(int x, int y)
	{
		//auto x1 = [] {return m_number;};					//error, m_number不是可修改的左值
		auto x2 = [=] {return m_number + x + y;};			//ok
		//auto x3 = [=]{return x++;};						//error
		auto x4 = [&]()->int{m_number++; return m_number + x + y;}	//ok
		auto x5 = [this] {return m_number;};				//ok
		auto x6 = [this] {return m_number++;};				ok
		auto x7 = [this] {return m_number + x + y;};		//error
		auto x8 = [this, x, y] {return m_number + x + y;} 	//ok
	}
};

lambda配合for_each()使用
之前我们输出数组所有的元素,可能是这种编写:

先使用generate()函数在容器中存入随机元素:

vector<int> vec(10);
std::generate(vec.begin(), vec.end(), std::rand);

打印容器中的所有元素:

/*1. c风格*/
for (int idx = 0; idx < test.size(); idx++)
	cout << vec[idx] << " ";

或是这种编写风格:

/*2. 使用迭代器*/
vector<int>::iterator it;
for (it = test.begin(); it != test.end(); it++)
	cout << *it << " ";

使用lambda和for_each函数会更加简单,一行代码就可以搞定:

for_each(vec.begin(), vec.end(), []{int x}{cout<<x<<" ";});

mutable
使用 lambda 表达式捕获列表捕获外部变量,如果希望去修改按值捕获的外部变量,这就需要使用 mutable 选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。

int a = 0;
auto f1 = [=] {return a++; };              // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; };     // ok

值拷贝的方式捕获的外部变量是只读的:

lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。mutable 选项的作用就在于取消 operator () 的 const 属性。
因为 lambda 表达式在 C++ 中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:

#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
    // 包装可调用函数
    std::function<int(int)> f1 = [](int a) {return a; };
    // 绑定可调用函数
    std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);

    // 函数调用
    cout << f1(100) << endl;
    cout << f2(200) << endl;
    return 0;
}

对于没有捕获任何变量的 lambda 表达式,还可以转换成一个普通的函数指针:

using func_ptr = int(*)(int);
// 没有捕获任何外部变量的匿名函数
func_ptr f = [](int a)
{
    return a;  
};
// 函数调用
f(1314);

参考:勿在浮沙築高臺

C++ Primer Plus第六版

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

生命如歌,代码如诗

听说,打赏我的人都找到了真爱!

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

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

打赏作者

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

抵扣说明:

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

余额充值