7.3 lambda函数

一、语法

1.基础语法

[capture](paramLists) mutable ->retunType{statement}
  • capture。捕获列表,用于捕获前文的变量供lambda函数中使用,可省略。
  • (paramLists)。参数列表,可省略。
  • mutable。lambda表达式默认具有常量性,可以通过mutable取消常量性,可省略。
  • returnType。函数返回类型,可省略。
  • statement。函数体,可省略。

结合上述可省略的内容,C++11中最简单的lambda表达式可以是(虽然没有实际意义):

[]{}

2.捕获列表

lambda函数的与普通函数最大的区别在于可以捕获前文的局部变量(仅仅对于局部而言,如果是全局lambda函数则不支持)。而捕获的方式有:

  • [var]表示值传递方式捕获变量var
  • [=]表示值传递方式捕获父作用域所有变量(包括this)
  • [&var]表示引用传递方式捕获变量var
  • [&]表示引用传递方式捕获父作用域所有变量(包括this)
  • [this]表示值传递方式捕获变量this

而由于捕获列表支持多个值(用,分隔),因此可以进行组合:

  • [=,&a,&b]表示引用传递捕获a,b,值传递捕获其他内容。
  • [&,a,this]表示值传递捕获a,this,引用传递捕获其他内容。

需要注意的是,捕获列表不能重复,如

[=,a,b]或者[&,&a,&b]等都是重复捕获(以相同的传递方式捕获同一个变量)。

3.基础使用

lambda函数通常用于局部作用域作为局部[匿名]函数。

        extern int z;
        extern float c;
        void Calc(int& , int, float &, float);
        void TestCalc() {
            int x, y = 3;
            float a, b = 4.0;
            int success = 0;
            auto validate = [&]() -> bool
            {
                if ((x == y + z) && (a == b + c))
                    return 1;
                else
                    return 0;
            };
            Calc(x, y, a, b);
            success += validate();
            y = 1024;
            b = 1e13;
            Calc(x, y, a, b);
            success += validate();
        }
        // 编译选项:g++ -c -std=c++11 7-3-7.cpp

而在有时会通过auto为lambda函数命名,使其获得自说明性。

与普通函数相比lambda有如下优势:

  • 支持直接在函数内创建,作用域外释放,而不用额外创建一个函数。
  • 能够直接捕获所有局部变量,而普通函数则需要额外传递。
  • lambda函数默认内联,在较多次调用时性能比普通函数好。
  • lambda函数的设计更简单,不需要考虑参数传递等问题

二、关于lambda的一些实验与讨论

1.捕获参数的传递方式

lambda函数中不同的捕获传递方式会造成不同的结果,对于值传递,则在传递的值在编译期就确定了,无法被修改,而对于引用传递则可以同步lambda函数外的修改。

        #include <iostream>
        using namespace std;
        int main() {
            int j = 12;
            auto by_val_lambda = [=] { return j + 1;};
            auto by_ref_lambda = [&] { return j + 1;};
            cout << "by_val_lambda: " << by_val_lambda() << endl;
            cout << "by_ref_lambda: " << by_ref_lambda() << endl;
            j++;
            cout << "by_val_lambda: " << by_val_lambda() << endl;
            cout << "by_ref_lambda: " << by_ref_lambda() << endl;
        }

运行结果:

        by_val_lambda: 13
        by_ref_lambda: 13
        by_val_lambda: 13
        by_ref_lambda: 14

2.与函数指针的关系

lambda函数与函数指针看起来很相似,但是实际上却不是函数指针,它是一种称为"闭包"(closure)的类。

这种类型支持向函数指针转换,前提是:

  • lambda函数不捕获任何变量

  • 函数指针的原型与lambda一致(参数,返回值都完全一致)

          int main() {
              int girls = 3, boys = 4;
              auto totalChild = [](int x, int y)->int{ return x + y; };
              typedef int (*allChild)(int x, int y);
              typedef int (*oneChild)(int x);
              allChild p;
                p = totalChild;
                oneChild q;
                q = totalChild;      // 编译失败,参数必须一致
                decltype(totalChild) allPeople = totalChild;   // 需通过decltype获得lambda的类型
                decltype(totalChild) totalPeople = p;       // 编译失败,指针无法转换为lambda
                return 0;
            }
            // 编译选项:g++ -std=c++11 7-3-10.cpp
    

此外,不支持函数指针向lambda转换

3.常量性与mutable

前面提到对于值传递的捕获参数具有常量性无法被修改,而想要打破这一限制,可以加上mutable关键字。(注意虽然可以修改,但仍然不影响父作用域变量)

#include <iostream>
int main() {
	int val=0;
	// 编译失败, 在const的lambda中修改常量
	//auto const_val_lambda = [=]() { val = 3; };
	// 非const的lambda,可以修改常量数据
	auto mutable_val_lambda = [=]() mutable { val = 3; };
	mutable_val_lambda();
	std::cout << val << std::endl;
	// 依然是const的lambda,不过没有改动引用本身
	auto const_ref_lambda = [&] { val = 4; };
	const_ref_lambda();
	std::cout << val << std::endl;
	// 依然是const的lambda,通过参数传递val
	auto const_param_lambda = [&](int v) { v = 5; };
	const_param_lambda(val);
	std::cout << val << std::endl;
	return 0;
}

而对于引用传递方式,则表示lambda捕获的参数引用了父作用域的变量,一边修改都会同步到另一边。

三、lambda与STL

前面说到,lambda对C++11最大的贡献,或者说是改变,应该在STL库中。这主要体现于STL算法更加容易,也更加容易学习了(可读性更高)。

下面将以for_each为例,讲述lambda带来的便捷。

#include <vector>
#include <algorithm>
using namespace std;
vector<int> nums;
vector<int> largeNums;
const int ubound = 10;
inline void LargeNumsFunc(int i){
    if (i > ubound)
        largeNums.push_back(i);
}
void Above() {
    // 传统的for循环
    for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
        if (*itr >= ubound)
            largeNums.push_back(*itr);
    }
    // 使用函数指针
    for_each(nums.begin(), nums.end(), LargeNumsFunc);
    // 使用lambda函数和算法for_each
    for_each(nums.begin(), nums.end(), [=](int i){
        if (i > ubound)
            largeNums.push_back(i);
    });
}
编译选项: g++ 7-3-13.cpp -c -std=c++11

这是通过基础for循环、for_each和lambda实现查找大于某个值的功能。相比for循环而言,for_each只需要关心数据起始点,并将每个元素作用到指定的操作上即可,在效率、正确性、可维护性上都具有一定优势。

而lambda较for_each而言,首先其函数内容会直接放在调用处,可阅读性更高(当然,有时也会被分离出来并命名,但通常不会太远);其次使用函数指针很可能导致编译器不对其进行inline优化(inline对编译器而言并非强制),在循环次数较多的时候,内联的lambda和没有能够内联的函数指针可能存在着巨大的性能差别。

此外相较于仿函数(不论是自己实现还是内置仿函数),lambda也依旧存在着不小的优势。

#include <vector>
#include <algorithm>
using namespace std;
vector<int> nums;
vector<int> largeNums;
class LNums{
public:
LNums(int u): ubound(u){}
void operator () (int i) const
{
    if (i > ubound)
        largeNums.push_back(i);
}
private:
int ubound;
};
void Above(int ubound) {
    // 传统的for循环
    for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
        if (*itr >= ubound)
            largeNums.push_back(*itr);
    }
    // 使用仿函数
    for_each(nums.begin(), nums.end(), LNums(ubound));
    // 使用lambda函数和算法for_each
    for_each(nums.begin(), nums.end(), [=](int i){
        if (i > ubound)
            largeNums.push_back(i);
    });
}

对于自己实现的仿函数,很直观的,lambda更加简洁。

而当面对更加复杂的场景时,lambda显得更加有优势:

#include <vector>
#include <algorithm>
using namespace std;
extern vector<int> nums;
void TwoCond(int low, int high) {
    // 传统的for循环
    for (auto i = nums.begin(); i != nums.end(); i++)
        if (*i >= low && *i < high) break;
    // 利用了3个内置的仿函数,以及非标准的compose2
    find_if(nums.begin(), nums.end(),
        compose2(logical_and<bool>(),
        bind2nd(less<int>(), high),
        bind2nd(greater_equal<int>(), low)));
    // 使用lambda函数
    find_if(nums.begin(), nums.end(), [=](int i) {
        return i >= low && i < high;
    });
}

这里我们需找到vector nums中第一个值介于[low, high)间的元素,可以看到内置仿函数变得异常复杂。

相关推荐
linweidong18 天前
小鹏汽车C++面试题及参考答案
c++·c++11·内存管理·大厂面试·牛客网·malloc·八股文面试
小志biubiu1 个月前
【C++11】可变参数模板/新的类功能/lambda/包装器--C++
开发语言·c++·笔记·学习·c++11·c11
Mo_YuO.o1 个月前
C++---智能指针和内存泄露
数据库·c++·c++11·智能指针
Ljw...2 个月前
C++ 的发展
c++·c++11·c++98·c++发展历程
草上爬2 个月前
C++笔试题之实现一个定时器
c++·c++11·定时器·timer
郭源潮12 个月前
【C++】左值引用和右值引用
开发语言·c++·c++11
Ljw...2 个月前
C++11
c++·c++11
炫酷的伊莉娜2 个月前
C++11 开发中的 Atomic 原子操作
c++·c++11·内存模型