c/c++补完计划(六): 语法糖lambda

时间:2022-07-23
本文章向大家介绍c/c++补完计划(六): 语法糖lambda,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

很多语言都有lambda, c++自然不能缺, 在c++11里面加入了, 是程序猿喜欢的语法糖, 便于阅读, 也便于理解. 当然, 它有很多相关概念, 这里尽可能展开说.

lambda表达式

[capture list] (params list) mutable exception-> return type { function body }
  • 捕获子句(在c++规范中也称为lambda引导)
  • 参数列表可有可无. (也称为lambda 声明符)
  • 可变规范可有可无.
  • 异常规范可有可无.
  • 尾随-返回类型可有可无.
  • lambda体, 也就是函数体.

你会觉得和函数声明很像, 但是注意, 不可以有默认值, 也不支持可变参数, 类似printf, 参数必须要有参数名. 甚至你会觉得和python3很像, 尤其是这个->return type, 所以你看, c++也越来越友好了.

先来几个小栗子:

auto f = [](int a, int b) { return a + b; };
cout << f(1, 5) << endl;

可以看到a, b是形参, 1, 5是实参, f似乎是函数, 这个放到后面说.

int x = 3, y = 5;
auto f2 = [x, y] { return x + y; };
cout << f2() << endl;

然后你发现, lambda更神奇的地方在于, 可以捕获外界参数, 是不是很酷, 其实这里本质还是值传递, 当然后面有引用, 但是默认是值传递.

参数捕获

捕获形式

说明

[]

不捕获任何外部变量

[var1,var2, …]

默认以值得形式捕获指定的多个外部变量(用逗号分隔)

[this]

以值的形式捕获this指针

[=]

以值的形式捕获所有外部变量

[&]

以引用形式捕获所有外部变量

[=, &var1]

变量var1以引用形式捕获,其余变量以传值形式捕获

[&, var1]

变量var1以值的形式捕获,其余变量以引用形式捕获

很多都是一眼懂, 举个栗子吧:

int x = 1, y = 2, z = 3;
cout << "before: " << x << ' ' << y << ' ' << z << endl;
auto f = [&, x]()mutable {
    x = 5;
    y = 6;
    z = 7;
    cout << "in: " << x << ' ' << y << ' ' << z << endl;
};
f();
cout << "after: " << x << ' ' << y << ' ' << z << endl;
before: 1 2 3
in: 5 6 7
after: 1 6 7

x是值传递, y, z是引用传递, 值传递实际上是不能修改的, 但是这里加了mutable, 所以可以在函数体内进行改动. 然后引用传入, 会对外部产生影响, 值传入则不会, 很好理解.

this

甚至可以传入this指针.

class A {
public:
    explicit A(int d) : data(d) {}

    void foo(vector<int> &v) {
        for_each(v.begin(), v.end(), [this](int i) {
            cout << "before: " << i << endl;
            i += data;
            cout << "after: " << i << endl;
        });
    }

private:
    int data;
};

vector<int> v{1, 2, 3};
A a(3);
a.foo(v);
before: 1
after: 4
before: 2
after: 5
before: 3
after: 6

传入this指针之后, 就可以使用成员变量.

function

然后你会发现一个问题, 我这里写的都是auto, 那具体是什么呢?

function<int(int, int)> f = [](int a, int b) { return a + b; };

返回一开始的栗子, 看到function里面写了函数的返回值, 然后括号里面是参数类型. std::function是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。 目的肯定是取代函数指针的, 来看一个栗子:

function<int(int, int)> add = [](int a, int b) { return a + b; };
cout << add(1, 5) << endl;
function<int(int, int)> div = [](int a, int b) { return a / b; };
cout << div(5, 1) << endl;

想想之前的写法, 是不是用宏定义一个函数指针, 类似typedef int(*fp)(int, int);, 是不是很像, 用function的话, 整体也更直观好操作.

bind

曾经我被问到, 如何实现lambda获取外部参数的功能, 那答案就是利用bind, 先来看个栗子:

function<int(int, int)> div = [](int a, int b) { return a / b; };
cout << div(5, 1) << endl;
auto divEx = bind(div, placeholders::_2, placeholders::_1);
cout << divEx(5, 1) << endl;
5
0

是不是挺酷的, 什么都没改, 就把参数位置换了.

A a;
auto ret = bind(&A::f, &a, 5, placeholders::_1);
cout << ret(3) << endl;

之前也说了, lambda是不支持默认参数的, 但是通过bind+占位符, 就很好实现了这个点, 甚至可以随意调整参数位置. 而且可以绑定成员函数, 不过你需要传入具体实例在第二个参数.

void print(ostream &os, string &str, char space) {
    os << str << space;
}

ostringstream os;
vector<string> data{"cpp", "c", "java"};
char space = ' ';
for_each(data.begin(), data.end(), bind(print, ref(os), placeholders::_1, space));
cout << os.str() << endl;

os.str("");
for_each(data.begin(), data.end(), [&os, space](string &str){os<< str << space;});
cout << os.str() << endl;

os.str("");
auto printL=[&os, space](string&str){os<<str<<space;};
for_each(data.begin(), data.end(), printL);
cout << os.str() << endl;

os.str("");
auto printL=[](ostream &os, string&str, char space){os<<str<<space;};
for_each(data.begin(), data.end(), bind (printL, ref(os), placeholders::_1, space));
cout << os.str() << endl;

最后对比下bind和lambda, 当然二者是可以配合使用的, 怎么用还是看自己. 我的话, 怎么简洁怎么用. ref简单说下, 主要是有些变量不支持值传递, bind这里就要写成ref(os).

最后

真的是很实用的点, 让c++灵活了很多. 就像设计了while之后, 又设计了for, 设计了for之后又设计了for_each, 语言都是不断发展的, 学习新的真的很有必要.