1.前言
Lambda表达式着重解决的是在某种场景下使用仿函数困难的问题,而function着重解决的是函数指针的问题,它能够将其简单化。
本章重点:
本章将着重讲解lambda表达式的规则和使用场景,以及function的使用场景及bind函数的相关使用方法。
2.为什么要有Lambda表达式
在C++98中,对自定义类型进行排序时,需要自己写仿函数,并传递给sort库函数
但是如果每次要按照自定义类型的不同成员变量进行排序的话,就要写很多个仿
函数,十分的不方便,于是C++11给出了一个新玩法:
cpp
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price < g2._price; });//按照价格升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price > g2._price; });//按照价格降序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate < g2._evaluate; });//按照评价升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate > g2._evaluate; });//按照评价降序
后面那一坨就完美的代替了仿函数。他其实就是传说中的lambda表达式
上述代码具体分析如下:
3.Lambda表达式的语法
lambda 表达式书写格式: [capture-list] (parameters) mutable -> return-type { statement
}
表达式部分说明:
[capture-list] : 捕捉列表 ,该列表总是出现在 lambda 函数的开始位置, 编译器根据[]来
判断接下来的代码 是否为lambda函数 , 捕捉列表能够捕捉上下文中的变量供 lambda
函数使用 。
(parameters) :参数列表。与 普通函数的参数列表一致 ,如果不需要参数传递,则可以
连同 () 一起省略
mutable :默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量
性。 使用该修饰符时,参数列表不可省略(即使参数为空)。
->returntype :返回值类型 。用 追踪返回类型形式声明函数的返回值类型 ,没有返回
值时此部分可省略。 返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导
{statement} :函数体 。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
注意:
在 lambda 函数定义中, 参数列表和返回值类型都是可选部分 ,而捕捉列表和函数体可以为
空 。因此 C++11 中 最简单的 lambda 函数为: []{} ; 该 lambda 函数不能做任何事情。
例:
cpp
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
通过上述例子可以看出, lambda 表达式实际上可以理解为无名函数,该函数无法直接调
用,如果想要直接调用,可借助 auto 将其赋值给一个变量。
4.Lambda表达式的捕捉列表
lambda表达式的捕捉列表[ ]可以捕捉父作用域的变量供自己使用。
规则如下:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int x = 10;
int y = 20;
std::vector<int> v = {1, 2, 3, 4, 5};
// 混合捕获
std::vector<int> filtered;
std::copy_if(v.begin(), v.end() [x, &y](int z) { return z > x && z < y; });
// 输出过滤后的结果
for (int n : filtered) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
在这个例子中,[x, &y]
捕获 x
的值和 y
的引用。
注意:
lambda表达式之间不能相互赋值,即使看起来类型是相同的。
lambda表达式的使用方法和仿函数非常相似,实际在底层编译器对于lambda表达式的处理方式完全就是按照函数对象的方式处理的即:如果定义了一个lambda表达式,
编译器会自动生成一个类,在该类中重载了operator()
mutable关键字详解:
由于lambda表达式是具有常属性的,所以在通常的情况下是无法被修改的,因此在C++14中引入了mutable
关键字,可以用于Lambda表达式中,以允许Lambda表达式修改捕获的变量。
例:
cpp
#include <iostream>
int main() {
int x = 10;
auto lambda = [=]() mutable { ++x; }; // 值捕获,允许修改
lambda();
std::cout << x << std::endl; // 输出: 10
return 0;
}
分析:为什么这里用了mutable之后,输出还是10呢?
简单理解就是:类比函数传参,你在这里传的只是x的副本,并不是真正的x,所以外部的x并没有被修改。
mutable
关键字的作用是允许 Lambda 表达式修改通过值捕获的变量。然而,值捕获的本质是复制外部变量的值到 Lambda 表达式的内部环境,
5.function包装器
function 包装器 也叫作适配器。 C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要 function 呢?
cpp
ret = func(x);
// 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?
//也有可能是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型
//可能会导致模板的效率低下!
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double{ return d/4; }, 11.11) << endl;
return 0;
}
这样的话一份useF就实例化出了三份代码,这样就比较low了。
那么如果用function的话,那么就可以提高效率了。
Function函数的使用方法:
第一个int表示返回值,()里面的int表示参数的类型。
回到上述要解决的问题,解决方式如下:
cpp
#include <functional>
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名
std::function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
// lamber表达式
std::function<double(double)> func3 = [](double d)->double{ return d /
4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
6.function包装器的使用场景
例如:1.可以存储在容器中,使得可以动态地管理和调用函数。
cpp
#include <iostream>
#include <vector>
#include <functional>
int main() {
std::vector<std::function<void()>> functions;
functions.push_back([]() { std::cout << "Function 1" << std::endl; });
functions.push_back([]() { std::cout << "Function 2" << std::endl; });
for (auto& func : functions) {
func();
}
return 0;
}
在这个例子中,functions
容器存储了多个 std::function<void()>
类型的函数,并在运行时依次调用这些函数。
在操作系统中,不同的线程要执行不同的函数的话,那么就可以用这种方式 来进行封装并且调用函数。
2.函数适配器
std::function
可以用于创建函数适配器,使得可以将不同类型的函数适配为统一的接口。
cpp
#include <iostream>
#include <functional>
void functionA(int x) {
std::cout << "Function A called with " << x << std::endl;
}
void functionB(double x) {
std::cout << "Function B called with " << x << std::endl;
}
int main() {
std::function<void(int)> adapterA = functionA;
std::function<void(double)> adapterB = functionB;
adapterA(10);
adapterB(3.14);
return 0;
}
在这个例子中,
adapterA
和adapterB
分别适配了不同类型的功能函数,使得它们可以统一调用。
7.bind函数
std::bind 函数定义在头文件中, 是一个函数模板,它就像一个函数包装器 ( 适配器 ) , 接受一个可
调用对象( callable object ),生成一个新的可调用对象来 " 适应 " 原对象的参数列表 。一般而
言,我们用它可以把一个原本接收 N 个参数的函数 fn ,通过绑定一些参数,返回一个接收 M 个( M
可以大于 N ,但这么做没什么意义)参数的新函数。同时,使用 std::bind 函数还可以实现参数顺
序调整等操作。
cpp
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来"适应"原对象的参数列表。
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的
callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中
的参数。
在正式使用bind函数之前,先介绍一下 std::placeholders
占位符
std::placeholders
提供占位符 _1
, _2
, _3
等,用于表示函数调用时的参数位置。
例如:
cpp
#include <iostream>
#include <functional>
void print(int a, int b) {
std::cout << "a: " << a << ", b: " << b << std::endl;
}
int main() {
auto bound_func = std::bind(print, std::placeholders::_2, std::placeholders::_1);
bound_func(20, 10); // 输出: a: 10, b: 20
return 0;
}
在这个例子中,std::bind
将 print
函数的参数位置交换了,使得 _2
即func里面的第二个参数作为print的第一个参数,_1
即func里面的第一个参数作为第二个参数传递给 print
函数。
简单总结一下:
std::bind
是 C++ 标准库中的一个函数模板,用于绑定函数和对象,以便创建新的可调用对象。std::bind
可以用于创建适配器,将函数、成员函数、甚至是函数对象绑定到特定的参数,从而生成新的可调用对象。
8.bind函数的使用场景
1. 绑定带有默认参数的成员函数
cpp
#include <iostream>
#include <functional>
class MyClass {
public:
void print(int a, int b = 0) {
std::cout << "a: " << a << ", b: " << b << std::endl;
}
void bindAndCall() {
// 使用 std::bind 绑定成员函数和 this 指针
auto bound_func = std::bind(&MyClass::print, this, std::placeholders::_1, 20);
// 调用绑定后的函数
bound_func(10); // 输出: a: 10, b: 20
}
};
int main() {
MyClass obj;
obj.bindAndCall();
return 0;
}
2.绑定函数对象
cpp
#include <iostream>
#include <functional>
class PrintFunc {
public:
void operator()(int a, int b) {
std::cout << "a: " << a << ", b: " << b << std::endl;
}
};
int main() {
PrintFunc pf;
auto bound_func = std::bind(pf, std::placeholders::_1, 20);
bound_func(10); // 输出: a: 10, b: 20
return 0;
}
9.总结
lambda表达式和function包装器以及bind函数到这就讲解完毕了。