lambda表达式定义了一个匿名函数,并且可以捕获一定范围内得变量
语法:[capture](params)opt->ret{body}
该表达式中:capture是捕获列表;params是参数表;opt是函数选项;ret是函数返回类型,body是函数体。
举例大家看一下怎么用:
int main() {
auto fa = [](int a)->int {return a + a; };
int num = fa(10);
auto fb=[](int x,int y)->int{return x+y;}
int tmp=fb(12,12);
cout << num << " "<<tmp<<endl;
return 0;
}
很显然其输出结果是20和24,其使用其实和函数是一样的,比如用指针或者引用作为参数:
int main() {
int x = 10, y = 20;
auto fa = [](int& a)->int {return a += 10; };
auto fb = [](int* p)->int {return *p += 10; };
int num = fa(x);
cout << num << endl;
int num1 = fb(&x);
cout << num1 << endl;
return 0;
}
当然可以使用内置内置类型也可以使用自己定义得类来。
我们使用整型得包装类型为例
class Int
{
public:
Int(int val = 0) :value(val) {
cout << "Create Int" << endl;
}
Int(const Int& it) :value(it.value) {
cout << "Copy Crate Int" << endl;
}
Int& operator=(const Int&it) {
if (this != &it) {
value = it.value;
} cout << "operator=" << endl;
return *this;
}
~Int() {
cout << "Destory Int" << endl;
}
private:
int value;
};
lambda表达式可以通过捕获列表捕获一定范围内的变量:
我们举例来使用一下上面所有的捕获:
[]:
int g_max=0;
void func() {
int tmp=1;
g_max += 10;
}
void func(int x) {
g_max += 10;
int a = 10, b = 20;
auto fa = []()->int { g_max+=10;return g_max; };
int num=fa();
}
int main() {
func();
return 0;
}
该代码中在函数func中使用lambda表达式对全局变量进行了操作,而捕获方式是不捕获任何变量,捕获是针对局部区域内的,所以不存在对全局变量进行捕获,可以在lambda表达式中对全局变量进行操作,但是如果将g_max变成a变量,就会报错,因为使用的是[],不捕获任何变量,但是可以使用形参和表达式函数体中的变量。
[=]:
void func(int x, int y)
{
Int a(10), b(20);
auto fa = [=]()->void {
int z = x + y;
//x += 10;//err
int c = a.GetValue();
a.Print();
//a.SetValue(100);//error
};
fa();
}
int main() {
int a = 10, b = 20;
func(a,b);
}
我们观察上面代码,用[=]的lambda表达式是按值捕获局部所有的变量,但是不能进行修改,只能读取,而且在lambda表达式中调用了哪个外部捕获的变量,是拷贝了一份副本来进行操作,不是从本身进行调用函数。我们可以发现上面的表达式函数体张x不能进行改变,也不能对对象a进行修改成员变量。也可以从运行结果看出:
在调用lambda表达式结束的时候,拷贝的对象就会被析构。
[&]:
void func(int x, int y)
{
Int a(10), b(20);
auto fa = [&]()->void {
a.Print();
a.SetValue(100);
a.Print();
};
fa();
}
int main() {
int a = 10, b = 20;
func(a,b);
}
可以发现我们使用了该方法之后,可以对捕获的值进行修改,而且没有进行拷贝构造,也可以从运行结果看出:
[bar,&va]
void func(int x, int y)
{
Int a(10), b(20);
auto fa = [a,&b]()->void {
a.Print();
int tmp = a.GetValue() + b.GetValue();
cout << tmp << endl;
b.SetValue(1);
};
fa();
}
int main() {
int a = 10, b = 20;
func(a,b);
}
我们可以发现只捕获了a,b变量,不会捕获其他变量,而且a只读,b可以进行修改。运行结果也可以看出:
当然这些方法都是相通的,都可以进行套用,比如:[=,&x]:以值形式捕获局部所有变量,以引用形式捕获x变量;[&,x]:以引用形式捕获所有局部变量,以值得形式捕获x变量。
注意:以值得形式捕获的所有变量,只有在函数体中使用才会被拷贝一份副本来使用!!!
[this]
该函数在Int类内定义:
void func(int a, int b) {//const Object* const this
auto fa0 = []() {return 10; };
auto fa1 = [a, b]() {return a + b; };
auto fa2 = [=]() {
this->Print();
this->SetValue(200);
this->Print();
this->add();
};
fa2();
}
可以发现我们可以通过传this指针对this之内的所有函数调用操作。
说了这么多,那么lambda表达式在编写程序中有哪些应用呢?又存在什么好处呢?
我们在讲简单应用之前需要了解一下for_each函数,该函数用于,遍历数据,遍历一下调用一次函数。具体实现代码很简单:
template<class InputIt,class unaryFunction>
unaryFunction my_for_each(InputIt first, InputIt last, unaryFunction fy) {
for (; first != last; ++first) {
fy(&first);
}
return fy;
}
简单的使用一下该函数:
void print(int x) {
if (x % 2 == 0) {
cout << x << endl;
}
}
int main() {
std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
my_for_each(ar.begin(), ar.end(), print);
}
我们可以发现通过该函数讲容器中的偶数进行了打印。这是怎么实现的呢?
我们首先要知道了解函数指针,是这样的,一个函数的函数名代表函数的 入口地址,函数名加括号代表调用该函数,我们传参也就是传的是print函数的函数首地址(函数指针),将函数指针传入之后通过形参加括号来调用函数。
这里可以简单的说一下函数指针,我们知道变量有不同的类型,其实函数也有类型,函数的类型是根据返回值和参数的不同区分的,
函数指针使用:
void it(int* a,int* b);
void jt(int* x,int* y);
//这两个函数其返回值和类型名都一样,那么久可以用函数指针:
void(*ls)(int,int)=nullptr;//函数指针ls,类型为void(int,int)类型,然后初始化为nullptr
ls=it;//函数指针指向it函数,
(*ls)(1,2);//调用函数
ls(1,2);//调用函数
为什么说for_each函数呢,他就可以使用我们的lambda表达式,不需要调用函数,因为lambda表达式可以理解为匿名的内敛函数,效率高。
int main() {
std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
my_for_each(ar.begin(), ar.end(), [](int& x)->void {
if (x % 2 == 0) cout << x << endl;
});
}
也可以计算其中的偶数个数
int main() {
std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
int count = 0;
my_for_each(ar.begin(), ar.end(), [&count](int& x)->void {
if (x % 2 == 0) count++;
});
cout << count << endl;
}
也可以这样计算:
class CountEven {
int& count;
public:
CountEven(int& c) :count(c) {}
void operator()(int val) const {
if (val % 2 == 0) ++count;
}
};
int main() {
std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
int count = 0;
my_for_each(ar.begin(), ar.end(),CountEven(count));
cout << count << endl;
}
我们创建一个list容器,并且初始化,删除其中的偶数,就可以用remove_if函数,该函数的参数中使用lambda表达式返回是否为偶数,是偶数就进行删除,不为是偶数就不删除。
int main() {
list<int> ar = { 1,2,3,4,5,6,7,8,9,10 };
for (auto& x : ar) { cout << x << " "; }
cout << endl;
//ar.remove(3);
ar.remove_if([](auto& x)->bool {
return (x % 2 == 0);
});
for (auto& x : ar) { cout << x << " "; }
cout << endl;
return 0;
}
而为什么vector一般不这样做呢?因为其底层是顺序表,删除元素不是尾删的话效率是很低的,使用vector删除只能这样删除,而且不建议这样的做法:
int main() {
vector<int> ar = { 1,2,3,4,5,6,7,8,9,10 };
for (auto& x : ar) { cout << x << " "; }
cout << endl;
//ar.remove(3);
ar.erase(std::remove_if(ar.begin(),ar.end(),[](auto& x)->bool {
return (x % 2 == 0);
}));
for (auto& x : ar) { cout << x << " "; }
cout << endl;
return 0;
}
void func() { cout << "func" << endl; }
struct Foo {
void operator()()const {
cout << "Foo:operator()" << endl;
}
};
struct Bar {
using pFun = void(*)(void);
static void func(void) {
cout << "Bar::func==>static" << endl;
}
operator pFun() {//operator void(*)(void)
return func;
}
};
struct Test{
int a;
void memfunc(void) {
cout << "Test::memfunc" << endl;
}
};
int main() {
void(*pfun)(void) = func;//定义函数指针,指向func函数
pfun();//通过函数指针调用函数
Foo foo;
foo();//仿函数调用
Bar bar;
bar();//首先对象名加括号,系统会判断是不是仿函数(括号重载),
//没找到括号重载就会调用强转,将func强转为函数指针,通过函数指针调用函数。
//bar.operator Bar::pFun();
void(Test:: * mpfun)(void) = &Test::memfunc;//定义了Test类内的函数指针,
Test t;
(t.*mpfun)();//使用函数指针调用函数,为了防止系统将其认为是成员函数,所以将其函数指针前加上解引用
}
为了增强代码的可用性,我们又引进了这样的写法:
int main() {
std::function<void(void)>fra = func;
fra();
Bar bar;
fra = bar;
fra();
Foo foo;
fra = foo;
fra();
Test t;
fra = std::bind(&Test::memfunc,t);
fra();
}
类似于函数指针调用,而Test对象呢,是将其和对象绑定在一块来通过函数指针调用函数。