Lambda函数

Lambda表达式是一种用于创建匿名函数的语法构造。它可以在需要函数对象的地方使用,而无需显式定义一个命名函数。

Lambda表达式有如下优点:

  • 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。

  • 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。

  • 在需要的时间和地点实现功能闭包,使程序更灵活。

python中的Lambda

在Python中,Lambda表达式用于创建匿名函数。Lambda表达式的语法如下:

lambda arguments: expression
  • lambda关键字表示Lambda表达式的开始。

  • arguments表示参数列表,可以是零个或多个参数,参数之间使用逗号分隔。

  • expression表示Lambda函数的返回值,即函数体的表达式。

1. 将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。

# 不带参数
greeting = lambda: "Hello, world!"
print(greeting()) # 输出: Hello, world
# 带参数
add = lambda x, y: x + y
print(add(3, 4)) # 输出: 7

2. 将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。

# 为了把标准库time中的函数sleep的功能屏蔽(Mock)
time.sleep=lambda x:None
time.sleep(3) #程序不会休眠3秒钟,而是什么都不做

在后续代码中调用time库的sleep函数将不会执行原有的功能

3. 将lambda函数作为参数传递给其他函数。

numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, numbers)
print(list(squared)) # 输出: [1, 4, 9, 16, 25]

部分Python内置函数接受函数作为参数,典型的此类内置函数有这些:

filter函数

filter(lambda x: x % 3 == 0, [1, 2, 3])
# 将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]。

sorted函数

sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))
# 将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序
# 其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]

map函数

map(lambda x: x+1, [1, 2,3])
#将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]

reduce函数

reduce(lambda a, b: '{}, {}'.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])
# 将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是'1, 2, 3, 4, 5, 6, 7, 8, 9'

排序:

students = [
{"name": "Alice", "score": 80},
{"name": "Bob", "score": 90},
{"name": "Charlie", "score": 70}
]
students.sort(key=lambda student: student["score"])
print(students)
# 输出:
# [{'name': 'Charlie', 'score': 70}, {'name': 'Alice', 'score': 80}, {'name': 'Bob', 'score': 90}]

在上述示例中,Lambda表达式用于创建匿名函数,并在不同的上下文中使用。Lambda表达式可以在需要函数对象的地方直接使用,无需显式定义一个命名函数。

C++中的Lambda

Lambda 表达式的各个部分

下面是作为第三个参数 std::sort() 传递给函数的简单 lambda:

#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}

下图显示了 lambda 语法的各个部分:

  1. capture 子句(在 C++ 规范中也称为 Lambda 引导。)

  2. 参数列表(可选)。 (也称为 Lambda 声明符)

  3. mutable 规范(可选)。

  4. exception-specification(可选)。

  5. trailing-return-type(可选)。

  6. Lambda 体。

捕获列表 [ ]

捕获列表是零或多个捕获符的逗号分隔符列表,可选地以默认捕获符开始(仅有的默认捕获符是 & 和 = )。默认情况下,从lambda生成的类都包含一个对应该lambda所捕获变量的数据成员。类似任何普通类地数据成员,lambda的数据成员也在lambda对象创建时被初始化。类似参数传递,变量的捕获方式也可以是值或引用。

值捕获:

void func()
{
int i = 100;//局部变量
//将i拷贝到明位f的可调用对象
auto f = [i] { return i; };
i = 0;
int j = f(); //j=100,因为i是创建时拷贝的
}

引用捕获:

void func()
{
int i = 100;//局部变量
//对象f包含i的引用
auto f = [&i] { return i; };
i = 0;
int j = f(); //j=0,传递的是引用
}

除了自己列出捕获列表的变量,还可以让编译器根据lambda中代码来推断我们要使用哪些变量(隐式捕获),用过使用&或=指示编译器推断捕获列表。&则采用引用捕获的方式,=则采用值捕获的方式。混合使用隐式捕获和显示捕获,则两者须使用不同的方式,一个为引用捕获,一个为值捕获。

lambda捕获列表:

  • [ ]。空捕获列表,lambda不能使用所在函数中的变量。

  • [=]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。

  • [&]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。

  • [this]。函数体内可以使用Lambda所在类中的成员变量。

  • [a]。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。

  • [&a]。将a按引用进行传递。

  • [=,&a, &b]。除a和b按引用进行传递外,其他参数都按值进行传递。

  • [&, a, b]。除a和b按值进行传递外,其他参数都按引用进行传递。

悬垂引用:

若以引用隐式或显式捕获非引用实体,而在该实体的生存期结束之后调用lambda对象的函数调用运算符,则发生未定义行为。C++ 的闭包并不延长被捕获的引用的生存期。这同样适用于被捕获的this指针所指向的对象的生存期。

形参列表 ( )

lambda形参列表和一般的函数形参列表类似,但不允许默认实参(C14 前)。当以 auto 为形参类型时,该 lambda 为泛型 lambda(C14 起)。与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。

void func()
{
int i = 10, j = 10;
//加上mutable才可以在lambda函数中改变捕获的变量值
auto f = [i, &j]() mutable {
i = 100, j = 100;
};
i = 0, j = 0;
f();
//输出:0 100
std::cout << i << " " << j << std::endl;
}

说明符

允许以下说明符:

  • mutable:允许 函数体修改各个复制捕获的对象,以及调用其非 const 成员函数;

  • constexpr:显式指定函数调用运算符为 constexpr 函数。此说明符不存在时,若函数调用运算符恰好满足针对 constexpr 函数的所有要求,则它也会是 constexpr; (C++17 起)

  • consteval:指定函数调用运算符为立即函数。不能同时使用 consteval 和 constexpr。(C++20 起)

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。假如我们希望能改变一个被捕获的变量的值,就必须在参数列表后面加上关键字mutable。而一个引用捕获的变量则不受此限制。

void func()
{
int i = 10, j = 10;
//加上mutable才可以在lambda函数中改变捕获的变量值
auto f = [i, &j]() mutable {
i = 100, j = 100;
};
i = 0, j = 0;
f();
//输出:0 100
std::cout << i << " " << j << std::endl;
}

返回类型 ->

当我们需要为一个lambda定义返回类型时,需要使用尾置返回类型。返回类型若缺省,则根据函数体中的 return 语句进行推断(如果有多条return语句,需要保证类型一直,否则编译器无法自动推断)。默认情况下,如果一个lambda函数体不包含return语句,则编译器假定返回void。

void func()
{
auto f = []() ->double {
if (1 > 2)
return 1;
else
return 2.0;
};
std::cout << f() << std::endl;
}

如果不显示指定返回类型,则int和double两种返回类型会导致推断冲突。

函数体 { }

略,同普通函数的函数体。

综合示例

#include <iostream>
#include <set>
#include <vector>
#include <algorithm>
#include <functional>

using namespace std;

// [introducer](optional)mutable throwSpec->retType {}
// mutable决定[]能够被改写 mutable throwSpec retType都是选择的,只要有一个存在就得写()
// retType 返回类型
// ()放参数
// []放外面变量 passed by value or reference


class UnNamedLocalFunction {
private:
int localVar;
public:
UnNamedLocalFunction(int var) : localVar(var) {}

bool operator()(int val) {
return val == localVar;
}
};

class Person {
public:
string firstname;
string lastname;
};

class LambdaFunctor {
public:
LambdaFunctor(int a, int b) : m_a(a), m_b(b) {}

bool operator()(int n) const {
return m_a < n && n < m_b;
}

private:
int m_a;
int m_b;
};

class X {
private:
int __x, __y;
public:
X(int x, int y) : __x(x), __y(y) {}

int operator()(int a) { return a; }

int f() {
// 下列 lambda 的语境是成员函数 X::f
// 对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针
return [&]() -> int {
return operator()(this->__x + __y); // X::operator()(this->x + (*this).y)
// 拥有类型 X*
}();
}


int ff() {
return [this]() {
return this->__x;
}();
}
};

int main() {

[] {
cout << "hello" << endl;
}();

auto I = [] {
cout << "hello" << endl;
};
I();
int id = 0;
// 先看前面的id 如果没有mutable error: increment of read-only variable ‘id’
auto f = [id]()mutable {
cout << "id=" << id << endl;
++id;
};
id = 42;
f(); // 0
f(); // 1
f(); // 2
cout << id << endl;

// 上述lambda就相当于
// class Functor {
// private:
// int id;
// public:
// void operator() {
// cout << "id=" << id << endl;
// ++id;
// }
// };
// Functor f;

int id1 = 0;
// 加不加mutable没影响,且传引用只要后面id1被修改了,就会使用修改后的值进行操作
auto f1 = [&id1]() {
cout << "id1=" << id1 << endl;
++id1;
};
id1 = 42;
f1(); // 42
f1(); // 43
f1(); // 44
cout << id1 << endl;


// 传参与返回
int id2 = 0;
auto f2 = [&id2](int &param) {
cout << "id2=" << id2 << endl;
++id2;
++param;
cout << "param=" << param << endl;
static int x = 5;
return id2;
};
int param = 1;
f2(param);
cout << "param=" << param << endl;

// [=] =表示默认以by value传递外部所有变量
// [&] &表示默认以by reference传递外部所有变量
auto f3 = [&]() {
cout << "id=" << id << endl;
cout << "id1=" << id1 << endl;
cout << "id2=" << id2 << endl;
cout << "param=" << param << endl;
};
f3();

// 一部分传引用,其余传值
cout << "id=" << id << endl;
auto f4 = [=, &id]() { // =不可以放在&id后面
cout << "id=" << id << endl;
id++;
cout << "id1=" << id1 << endl;
cout << "id2=" << id2 << endl;
cout << "param=" << param << endl;
};
f4();

// 一部分传值,其余传引用
cout << "id=" << id << endl;
auto f5 = [&, id]() { // &不可以放在id后面
cout << "id=" << id << endl;
cout << "id1=" << id1 << endl;
cout << "id2=" << id2 << endl;
cout << "param=" << param << endl;
};
f5();
// this 指针
X x_(1, 2);
cout << "x_.f()=" << x_.f() << endl; // 1+2=3
cout << "x_.ff()=" << x_.ff() << endl; // 1



// 下面lambda函数等价于上述的UnNamedLocalFunction
int tobefound = 5;
auto lambda1 = [tobefound](int val) {
return val == tobefound;
};
bool b1 = lambda1(5);
UnNamedLocalFunction lambda2(tobefound);
bool b2 = lambda2(5);
cout << b1 << " " << b2 << endl;

auto ll1 = [](int x) -> int {
return x + 10;
};
// lambda 匿名函数
function<int(int x)> ll = [](int x) -> float {
return x + 10.0;
};
cout<<ll(1)<<endl;

// decltype+lambda
// 比大小
function<bool(const Person&p1,const Person&p2)> cmp = [](const Person &p1, const Person &p2) {
return p1.lastname < p2.lastname;
};


// 对于lambda,我们往往只有object,很少有人能够写出它的类型,而有时就需要知道它的类型,要获得其type,就要借助其decltype
set<Person, decltype(cmp)> col(cmp);

// 要申明lambda对象的类型,可以使用template或者auto进行自动推导。
// 如果需要知道类型,可以使用decltype,比如,让lambda函数作为关联容器或者无序容器的排序函数或者哈希函数。
// 上面代码给出了事例(decltype的第三种用法中的事例),定义了一个lambda函数用cmp表示,用来比较Person对象的大小,传入到Set容器中去,
// 但根据右边的set容器的定义,我们传入的不仅是cmp(构造函数),还要传入模板的cmp类型(Set内部需要声明cmp类型),
// 所以必须使用decltype来推导出类型。
// (如果没有向构造函数传入cmp,调用的是默认的构造函数,也就是set() : t(Compare()), 这里会报错, 现在不会出问题了!
// 因为Compare()指的是调用默认的lambda构造函数,而lambda函数没有默认构造函数和赋值函数)


vector<int> vec{5, 28, 50, 83, 70, 590, 245, 59, 24};
int x = 30, y = 100;
// 函数对象是很强大的,封装代码和数据来自定义标准库的行为,但需要写出函数对象需要写出整个class,这是不方便的,而且是非本地的,
// 用起来也麻烦,需要去看怎样使用,另外编译出错的信息也不友好,而且它们不是inline的,效率会低一些(算法效率还是最重要的)。
// vec.erase(remove_if(vec.begin(), vec.end(), LambdaFunctor(x, y)), vec.end());
// for(auto i:vec) cout<<i<<" ";
// cout<<endl;
// 而lambda函数的提出解决了这个问题,简短有效清晰,上面的事例很好的说明了这个问题,用lambda要简短许多,功能一样,很直观。
vec.erase(remove_if(vec.begin(), vec.end(), [x, y](int n) { return x < n && n < y; }), vec.end());
for_each(vec.begin(), vec.end(), [](int i) { cout << i << " "; });
cout << endl;
return 0;

}

Reference

[1] C++ Lambda表达式的基本使用: https://blog.csdn.net/gongjianbo1992/article/details/105128849

[2] python中的lambda函数用法: https://zhuanlan.zhihu.com/p/58579207

[3] C++ 那些事: https://github.com/Light-City/CPlusPlusThings

------------------------------- 本文结束啦❤感谢您阅读-------------------------------
赞赏一杯咖啡