闭包有很多种定义, 一种说法是, 闭包是带有上下文的函数。 说白了, 就是有状态的函数,就是有自己的变量。更直接一些, 就是一个类 换了个名字而已。
一个函数, 带上了一个状态, 就变成了闭包了。 那什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量, 这些个变量的值是创建闭包的时候设置的, 并在调用闭包的时候, 可以访问这些变量。
函数是代码, 状态是一组变量, 将代码和一组变量捆绑 (bind
) , 就形成了闭包。 闭包的状态捆绑, 必须发生在运行时。
之前我们在学 std::list
等容器模拟实现的时候讲到了仿函数,其实就是用一个类,在类中重载 operator()
即可达到类似函数的调用方式,本质上就是一个重载 ()
的函数!
#include<iostream>
class MyFunctor
{
public:
MyFunctor(int tmp)
:round_(tmp)
{}
int operator()(int tmp) // 仿函数,重载operator()
{
return round_ + tmp;
}
private:
int round_; // round_就是这个闭包的状态
};
int main()
{
int round = 2;
MyFunctor mf(round); // 调用构造函数
// 调用仿函数
std::cout << "result = " << mf(1) << std::endl; // operator()(int tmp)
return 0;
}
// 运行结果:
result = 3
C++ 11中的std::bind和std::function std::function与std::bind使用总结 包装器和绑定器std::bind和std::function的回调技术
C++
中函数指针的用途非常广泛,例如回调函数,接口类的设计等,但函数指针始终不太灵活,它只能指向全局或静态函数,对于类成员函数、lambda
表达式或其他可调用对象就无能为力了,因此,C++11
推出了 std::function
与 std::bind
这两件大杀器。
C++
中, 可调用实体主要包括:函数、函数指针、函数引用、可以隐式转换为函数指定的对象,或者实现了 opetator() 的对象。
而 C++11
中, 新增加了一个 std::function
类模板, 它是对 C++
中现有的可调用实体的一种类型安全的包裹。 通过指定它的模板参数, 它可以用统一的方式处理函数、 函数对象、 函数指针,并允许保存和延迟执行它们。
std::function
对象最大的用处就是在实现函数回调,使用者需要注意,虽然它不能被用来检查相等或者不相等,但是可以与 NULL
或者 nullptr
进行比较。
💥💥💥当然,任何东西都会有优缺点,std::function
填补了函数指针的灵活性,但会对调用性能有一定损耗,经测试发现,在调用次数达10亿次时,函数指针比直接调用要慢 2
秒左右,而 std::function
要比函数指针慢 2
秒左右,这么少的损耗如果是对于调用次数并不高的函数,替换成 std::function
绝对是划得来的。
#include <functional>
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
/*
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
*/
下面我们写一段代码来测试一下它的功能!
测试一:(代码中的宏 __LINE__
代表的是当前所在的行数,__func__
表示的是当前调用的是哪一个函数。)
#include<iostream>
#include<functional>
// 1、普通函数
void Func()
{
std::cout << __LINE__ << ":" << __func__ << std::endl;
}
// 2、类中静态函数
class Test
{
public:
static int Func(int tmp)
{
std::cout << __LINE__ << ":" << __func__ << "(" << tmp << ")->:";
return tmp;
}
};
// 3、类中仿函数
class MyFunctor {
public:
MyFunctor(int tmp)
:round_(tmp)
{}
int operator()(int tmp)
{
std::cout << __LINE__ << ":" << __func__ << "(" << tmp << ")->:";
return tmp + round_;
}
private:
int round_;
};
int main()
{
// 1、绑定普通函数
std::function<void(void)> f1 = Func;
f1(); // 等价于 func()
// 2、绑定类中的静态函数,需要使用 类名::函数 的方式进行绑定
std::function<int(int)> f2 = Test::Func;
std::cout << f2(10) << std::endl; // Test::Func(10)
//3、绑定类中的仿函数,绑定对象,仿函数调用obj
MyFunctor obj(100);
std::function<int(int)> f3 = obj;
std::cout << f3(20) << std::endl;
return 0;
}
// 运行结果
158:Func
166:Func(10)->:10
178:operator()(20)->:120
测试二:
typedef std::function<void()> PrintFinFunction; // 将包装器重命名一下
// using PrintFinFunction = function<void()>; // 也可以用c++11的using来重命名包装器
// 回调函数
void print(const char* text, PrintFinFunction callback)
{
printf("%s\n", text);
if (callback) // 不为空则去回调
callback();
}
// test1普通函数
void printFinCallback()
{
cout << "Normal callback" << endl;
}
// test2类静态函数
class Test
{
public:
static void printFinCallback() {
cout << "Static callback" << endl;
}
};
// test3仿函数,重载()运算符
struct Functor
{
void operator() () {
cout << "Functor callback" << endl;
}
};
int main()
{
print("test 1", printFinCallback);
print("test 2", Test::printFinCallback); // 类静态函数需要用 类名::函数名 来传递参数去绑定
print("test 3", Functor()); // 仿函数要创建对象
print("test 4", []() {
cout << "Lambda callback" << endl;
});
}
// 运行结果
test 1
Normal callback
test 2
Static callback
test 3
Functor callback
test 4
Lambda callback
总的来说,function
包装器就是用来实现对函数指针、仿函数、lambda
表达式等进行统一的表示,我们可以看看下面的例子就能看到一些问题:
#include <iostream>
#include <functional>
using namespace std;
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;
// lambda表达式
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
return 0;
}
// 运行结果:
count:1
count:00007FF607272574
5.555
count:1
count:00007FF607272578
3.70333
count:1
count:00007FF60727257C
2.7775
可以很明显的发现,程序中我们用 useF
函数模板来作为一个中转函数,分别去调用普通函数、仿函数、lambda
表达式,但是它们的 count
改变的时候,其实对各自是没有影响的,并且地址都是不同的,这说明什么❓❓❓
说明 useF
函数模板被实例化为了三份不同的函数,这其实就导致了没必要的内存开销等等!
那么这个问题我们就可以通过包装器 function
来解决,包装器就是为了统一我们调用这些函数的类型,也就是说,如果我们用 function
去包装我们调用的函数,那么 useF
函数模板怎么识别都是只有一个类型也就是 function
类型,那么这样子就只会实例化一份函数,大大的减少了开销!
下面是使用 function
和 不使用 function
的区别:
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;
cout << "################################################################################" << endl;
// 函数名
function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl;
// cout << useF(function<double(double)>(f), 11.11) << endl; // 也可以写成这样子
// 函数对象
function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
// lamber表达式
function<double(double)> func3 = [](double d)->double { return d / 4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
// 运行结果:
count:1
count:00007FF607272574
5.555
count:1
count:00007FF607272578
3.70333
count:1
count:00007FF60727257C
2.7775
################################################################################
count:1
count:00007FF607272580
5.555
count:2
count:00007FF607272580
3.70333
count:3
count:00007FF607272580
2.7775
明显发现,我们的三份函数就是同一份!
💥除此之外,这里还要强调一些使用包装器的时候一些细节:
我们在绑定仿函数的时候,需要绑定的是一个对象,而不是这个仿函数的类名。
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
int main()
{
function<int(int, int)> func1 = Functor; // ❌错误
function<int(int, int)> func2 = Functor(); // 👍正确
cout << func2(1, 2) << endl;
return 0;
}
一般在绑定仿函数的时候,我们不能通过()
来进行初始化赋值,而要通过 =
号来进行赋值,这可能是编译器在识别 ()
内定义的函数对象把这个函数对象识别成了函数指针,导致我们如果调用了通过()
初始化的 function
对象的时候,编译器会报错(vs
编译器、gcc
编译器都会报错),而对于普通函数等,通过()
来初始化绑定 function
是可以通过编译并且正常运行的!,比如下面的代码:
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
int main()
{
std::function<int(int, int)> func1(Functor());
cout << func1(1, 2) << endl; // ❌错误
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl; // 👍正确
return 0;
}
对于类的非静态成员函数,我们在绑定的时候需要特别处理:
this
指针,所以 我们在绑定的时候第一个参数必须是该类的类名,注意不是传递该类对象的指针,因为 this
指针其实是不允许我们显式去调用的,所以这里语法规定我们需要传一个该类的类名来占位。&类名::成员函数
,前面这个 &
是不能省略的,因为我们需要的是一个指向成员的指针,所以必须取地址!对于类的静态成员函数,会相对简单一点,因为它是没有 this
指针的,只不过我们还是需要通过 &类名::成员函数
来进行绑定,其中不同的是静态成员函数可以不必加 &
,但是为了防止与非静态成员函数必须加 &
的语法规定混淆,我们推荐静态成员函数一样要加 &
。
class Plus
{
public:
static int static_add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
};
int main()
{
// 静态成员函数
function<int(int, int)> func1 = &Plus::static_add;
cout << func1(1, 2) << endl;
// 非静态成员函数
function<double(Plus, double, double)> func2 = &Plus::add; // 第一个参数用类名来占位表示this指针
cout << func2(Plus(), 1.1, 2.2) << endl; // 第一个参数传递要传一个类的对象
// 如果不想非静态成员函数这样子调用加上对象来占位的话,可以使用lambda表达式来捕捉(或者通过下面讲的绑定器来实现)
Plus plus;
function<double(double, double)> func3 = [&plus](double x, double y)->double { return plus.add(x, y); };
cout << func3(1.1, 2.2) << endl;
return 0;
}
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 利用包装器和lambda,映射运算符与函数的关系
map<string, function<int(int, int)>> hash = {
{"+", [](int a, int b)->int{ return a + b; }},
{"-", [](int a, int b)->int{ return a - b; }},
{"*", [](int a, int b)->int{ return a * b; }},
{"/", [](int a, int b)->int{ return a / b; }}
};
stack<int> st;
for(int i= 0; i < tokens.size(); ++i)
{
if(hash.count(tokens[i]) == 0) // 说明不是运算符
{
st.push(stoi(tokens[i]));
}
else
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
st.push(hash[tokens[i]](left, right));
}
}
return st.top();
}
};
std::bind
函数定义在头文件 <functional>
中,是一个函数模板,一般而言,我们用它可以把一个原本接收 N
个参数的函数 fn
,通过绑定一些参数,返回一个接收 M
个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用 std::bind
函数还可以实现参数顺序调整等操作。
std::bind
可以预先把指定可调用实体的某些参数绑定到已有的变量, 产生一个新的可调用实体, 这种机制在回调函数的使用过程中也颇为有用。
C++98
中,有两个函数 bind1st
和 bind2nd
,它们分别可以用来绑定 functor
的第一个和第二个参数,它们都是只可以绑定一个参数,由于各种限制,使得 bind1st
和 bind2nd
的可用性大大降低。
在 C++11
中, 提供了 std::bind
, 它绑定的参数的个数不受限制, 绑定的具体哪些参数也不受限制, 由用户指定, 这个 bind
才是真正意义上的绑定。
// 原型如下:
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
中的参数。
arg_list
中的参数可能包含形如 _n
的名字,其中 n
是一个整数,这些参数是 “占位符”,表示 newCallable
的参数,它们占据了传递给 newCallable
的参数的“位置”。数值 n
表示生成的可调用对象中参数的位置:_1
为 newCallable
的第一个参数,_2
为第二个参数,以此类推……
下面我们写一段代码来测试一下它的功能!
#include<iostream>
#include<functional>
void Func(int x, int y)
{
std::cout << x << " " << y << std::endl;
}
int main() {
std::bind(Func, 10, 20)(30, 40); // 输出10,20
std::bind(Func, std::placeholders::_1, std::placeholders::_2)(11, 21, 31); // 输出11,21
using namespace std::placeholders; // 下面引入std::placeholders,节省代码量
std::bind(Func, _1, 11)(10, 20, 30); // 输出10,11
std::bind(Func, _1, _2)(11, 21); // 输出11,21
std::bind(Func, _2, _1)(12, 22); // 先输出_2就是22,在输出_1就是12
// std::bind(Func,_2,22)(11); // ❌参数不匹配,没有第二个参数
auto b1 = std::bind(Func, _2, 22);
b1(11, 0); // 输出0,22
cout << typeid(b1).name() << endl;
auto b2 = std::bind(Func, _3, 22);
b2(0, 1, 2); // 输出3,22
return 0;
}
// 运行结果
10 20
11 21
10 11
11 21
22 12
0 22
class std::_Binder<struct std::_Unforced,void (__cdecl&)(int,int),struct std::_Ph<2> const & __ptr64,int>
2 22
其中 std::placeholders::_1
是一个占位符, 代表这个位置将在函数调用时, 被传入的第一个参数所替代。
刚才也说道,std::function
可以指向类成员函数和函数签名不一样的函数,其实,这两种函数都是一样的,因为类成员函数都有一个默认的参数:this
,作为第一个参数,这就导致了类成员函数不能直接赋值给 std::function
,这时候我们就需要 std::bind
了!
简言之,std::bind
的作用就是转换函数签名,将缺少的参数补上,将多了的参数去掉,甚至还可以交换原来函数参数的位置!
#include<iostream>
#include<functional>
using namespace std;
typedef std::function<void(int)> PrintFinFunction; // 将包装器重命名
void print(const char *text, PrintFinFunction callback)
{
printf("%s\n", text);
if (callback) // 不为空则去回调
callback(1);
}
// 类成员函数
class Test
{
public:
void printFinCallbackInter(int res)
{
cout << "Class Inter callback" << endl;
}
};
// 函数签名不一样的函数
void printFinCallback2(int res1, int res2)
{
cout << "Different callback " << res1 << " " << res2 << endl;
}
int main()
{
Test testObj;
function<void(int)> callback5 = bind(&Test::printFinCallbackInter, testObj, placeholders::_1); // 函数模板只有一个参数,这里需要补充this参数
print("test 5", callback5);
// 也可以用auto省略function的类型
auto callback6 = bind(&printFinCallback2, placeholders::_1, 100); // 这里需要补充第二个参数
print("test 6", callback6);
// 也可以直接调用
print("test 7", bind(&printFinCallback2, placeholders::_1, 200));
}
// 运行结果:
test 5
Class Inter callback:1
test 6
Different callback:1 100
test 7
Different callback:1 200
从上面的代码中可以看到,std::bind
的用法就是第一个参数是要被指向的函数的地址,为了区分,这里 std::bind
语句的左值函数为原函数,右值函数为新函数,那么 std::bind
方法从第二个参数起,都是新函数所需要的参数,缺一不可,而我们可以使用 std::placeholders::_1
或 std::placeholders::_2
等等占位符来使用原函数的参数,_1
就是原函数的第一个参数,如此类推。
值得注意的有两点:
bind
补充了缺失的参数,那么以后每次调用这个 function
时,那些原本缺失的参数都是一样的,举个栗子,上面代码中 callback6
,我们每次调用它的时候,第二个参数都只会是 100
。iOS
程序中使用 std::bind
传入一个缺省参数,那么我们转化后的那个 function
会持有那些缺省参数,这里我们需要防止出现循环引用导致内存泄漏。通过 std::bind
和 std::function
配合使用, 所有的可调用对象均有了统一的操作方法。
// 更加简约的实例
#include <iostream>
#include <functional>
using namespace std;
int Plus(int a, int b)
{
return a + b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
// 表示绑定函数plus参数分别由调用func1的第一、二个参数指定
function<int(int, int)> func1 = bind(Plus, placeholders::_1, placeholders::_2);
cout << func1(1, 2) << endl;
// func2的类型为function<int(int, int)>与func1类型一样
// 表示绑定函数 plus 的第一,二为:1, 2
auto func2 = bind(Plus, 1, 2);
cout << func2() << endl;
Sub s;
// func3绑定成员函数,无需添加类名和lei'du来占位
function<int(int, int)> func3 = bind(&Sub::sub, s, placeholders::_1, placeholders::_2);
// func4与func3的参数调换顺序
function<int(int, int)> func4 = bind(&Sub::sub, s, placeholders::_2, placeholders::_1);
cout << func3(1, 2) << endl;
cout << func4(1, 2) << endl;
// 利用包装器和绑定器将less比较器改成greater比较器,只需要用绑定器调整参数位置即可
function<bool(int, int)> func5 = bind(less<int>(), _1, _2);
function<bool(int, int)> func6 = bind(less<int>(), _2, _1);
cout << func5(1, 2) << endl;
cout << func6(1, 2) << endl;
return 0;
}
// 运行结果:
3
3
-1
1
1
0
lambda
表达式可以看下一个部分的内容,专门讲 lambda
表达式!
引入 lambda
表达式其实是为了解决一些比较棘手的问题比如要排序的数据是自定义类型的话,需要用户定义排序时的比较规则,那么以前我们是有两种方法的,第一种就是函数指针,第二种就是仿函数:
struct Goods
{
string _name;
double _price;
Goods(const char* str, double price)
:_name(str)
,_price(price)
{}
};
// 仿函数
struct Compare
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price <= gr._price;
}
};
int main()
{
vector<Goods> gds= { { "苹果",2.1 }, { "相交",3 }, { "橙子",2.2 }, {"菠萝",1.5} };
sort(gds.begin(), gds.end(), Compare());
return 0;
}
随着 C++
语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个 algorithm
算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在 C++11
语法中出现了 lambda
表达式。
int main()
{
vector<Goods> gds= { { "苹果",2.1 }, { "相交",3 }, { "橙子",2.2 }, {"菠萝",1.5} };
sort(gds.begin(), gds.end(), [](const Goods& l, const Goods& r)->bool
{ return l._price < r._price; });
return 0;
}
上述代码就是使用 C++11
中的 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
函数不能做任何事情,没有意义。
🐰 下面我们先来写一些简单的 lambda
表达式:
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
int a = 3, b = 11;
// 实现两个数相加的lambda表达式(返回值也可以省略,让编译器推导)
auto add1 = [](int x, int y)->int{ return x + y; };
cout << add1(a, b) << endl;
// 若想定义和add1一样的变量,则有两种方法:auto或者decltype
auto add2 = add1;
decltype(add1) add3 = add1;
cout << typeid(add2).name() << endl;
cout << typeid(add3).name() << endl;
return 0;
}
//运行结果
14
class `int __cdecl main(void)'::`2'::<lambda_2>
class `int __cdecl main(void)'::`2'::<lambda_2>
那么这个 捕捉列表 和 mutable
是干什么的呢?我们平时交换两个数可能是这样子写的:
int main()
{
int a = 10, b = 20;
// 两数交换的lambda表达式
auto swap = [](int& x, int& y) {
int z = x;
x = y;
y = z;
};
swap(a, b);
cout << a << " " << b << endl;
return 0;
}
//运行结果
20 10
这里要注意的一个点就是要传引用或者指针接收,不然的话传值是无法达到交换的效果的,和函数是一样的!
但是对于 lambda
表达式来说就不需要再这么复杂了,因为有了捕捉列表:
int main()
{
int a = 10, b = 20;
// 两数交换的lambda表达式
auto swap = [a, b]{
int c = a;
a = b;
b = c;
};
swap(a, b);
cout << a << " " << b << endl;
return 0;
}
但是这里就有问题了,编译器报错,说函数体中的 a
和 b
必须是可以修改的左值,也就是说他们的属性是 const
,那么这个时候 mutable
就派上用场了:
int main()
{
int a = 10, b = 20;
// 两数交换的lambda表达式
auto swap = [&a, &b]()mutable{
int c = a;
a = b;
b = c;
};
swap();
cout << a << " " << b << endl;
return 0;
}
除此之外,这里要达到交换 a
和 b
,必须要 传引用捕捉它们!并且还可以发现当我们传引用去捕捉后,我们是可以不加 mutable
的,因为我们捕捉的其实是地址,所以可以改变!
还有要注意的是 使用 mutable
的话,不能省略其前面的参数列表!
捕获列表说明:捕捉列表描述了上下文中那些数据可以被 lambda
使用,以及使用的方式传值还是传引用。
[var]
:表示值传递方式捕捉变量 var
[=]
:表示 值传递 方式捕获所有父作用域中的变量(包括this
)[&var]
:表示引用传递捕捉变量 var
[&]
:表示 引用传递 捕捉所有父作用域中的变量(包括this
)[this]
:表示值传递方式捕捉当前的 this
指针注意:
lambda
函数的语句块[=, &a, &b]
:以引用传递的方式捕捉变量 a
和 b
,值传递方式捕捉其他所有变量。而 [&,a, this]
表示值传递方式捕捉变量 a
和 this
,引用方式捕捉其他变量 。[=, a]
中 =
已经以值传递方式捕捉了所有变量,捕捉 a
重复。lambda
函数捕捉列表必须为空。lambda
函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。lambda
表达式之间不能相互赋值,即使看起来类型相同,但可以将 lambda
表达式赋值给相同类型的函数指针。void (*PF)();
int f = 10, g = 100;
// 全局的lambda捕捉不了变量
// auto cat1 = [&a, &b] {};
// auto cat2 = [f, g] {};
auto cat3 = [] {cout << "just only empty!" << endl; };
int main()
{
int a = 1, b = 2, c = 3;
auto change1 = [&, b] {
a = 10;
//b = 20; b无法被修改因为是传值接收的,而其余变量是传引用接收
c = 30;
};
change1();
cout << a << " " << b << " " << c << endl;
auto f1 = [] {cout << "hello world" << endl; };
auto f2 = [] {cout << "hello world" << endl; };
// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
// f1 = f2; // 编译失败--->提示找不到operator=()
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
f3();
// 可以将lambda表达式赋值给相同类型的函数指针
PF = f2;
PF();
// 调用全局中的lambda
cat3();
// 不能捕捉重复属性的!
// auto func1 = [=, a]() {};
// auto func2 = [&, &a]() {};
return 0;
}
//运行结果
10 2 30
hello world
hello world
just only empty!
函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了 operator()
运算符的类对象。
class Rate
{
public:
Rate(double rate) : _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
// 函数对象
double rate = 0.49;
Rate r1(rate);
cout << r1(10000, 2) << endl;;
// lamber
auto r2 = [=](double money, int year)->double { return money * rate * year; };
cout << r2(10000, 2) << endl;
return 0;
}
// 运行结果
9800
9800
从使用方式上来看,函数对象与 lambda
表达式完全一样。
函数对象将 rate
作为其成员变量,在定义对象时给出初始值即可,lambda
表达式通过捕获列表可以直接将该变量捕获到。
实际在底层编译器对于 lambda
表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个 lambda
表达式,编译器会自动生成一个类,在该类中重载了 operator()
。
底层原理:其实是被处理成一个 lambda_uuid
的一个仿函数类!
这就好比与 范围for 的本质其实就是迭代器,而 lambda
表达式 的本质就是 仿函数!