C++技能系列( 1 ) - 使用Lambda表达式【详解】

news/2024/11/6 7:05:01/

系列文章目录

C++高性能优化编程系列
深入理解软件架构设计系列
高级C++并发线程编程
C++技能系列

期待你的关注哦!!!
在这里插入图片描述

现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.

详解使用Lambda表达式

  • 系列文章目录
  • 一、lambda表达式 - 定义
  • 二、lambda表达式 - 捕获列表
  • 三、lambda表达式 - 延时调用易出错细节分析
  • 四、lambda表达式 - 如何使用mutable
  • 五、lambda表达式 - 作为匿名的类类型对象
  • 六、lambda表达式 - 在for_each和find_if中使用
  • 七、小结

lambda表达式是C++11引入的一个很重要的的特性, lambda表达式也是 一个可调用对象,它定义了一个 匿名函数,并且可以 捕获一定范围内的变量

一、lambda表达式 - 定义

lambda表达式一般形式:

[捕获列表](参数列表)-> 返回类型 { 函数体; };

auto f = [](int a) -> int{return a + 1;
}
std::cout << f(1) << std::endl;

(1) 参数列表也可以有默认值:

auto f = [](int a = 6) -> int{return a + 1;
}
std::cout << f(1) << std::endl;

(2) 没有参数的时候,参数列表可以省略,甚至"()"也可以省略,所以如下代码是合法的:

auto f1 = ()[]{return 1;};
auto f2 = []{return 2;};
std::cout << f(1) << std::endl;
std::cout << f(2) << std::endl;

(3) 捕获列表[ ]和函数体不能省略,必须时刻包含。
(4) lambda表达式的调用方法和普通函数相同,都是使用"( )"这种函数调用运算符。
(5) lambda表达式可以不返回任何类型,返回任何类型就是返回void。
(6) 函数体末尾的分号不能省。

二、lambda表达式 - 捕获列表

lambda表达式通过捕获列表捕获一定范围内的变量,那么,这个范围究竟是什么意思呢?

(1) [ ]: 不捕获任何变量

看如下范例:

int i = 9;
auto f1 = []{//报错(无法捕获外部变量),不认识这个i在哪里定义,//看来lambda表达式毕竟是匿名函数,按常规理解是不行。return i;
};

⚠️ 但不包括静态局部变量,lambda可以直接使用静态局部变量。例如,上面的int i = 9;修改为static int i = 9; 是可以在lambda表达式中使用的。

(2) [&]: 捕获外部作用域中所有变量,并作为引用在函数体内使用

看如下范例:

int i = 9;
auto f1 = [&]{//因为&的存在,允许给i赋值,从而也就改变了i的值i = 5;return i;
};
//5,调用了lambda表达式,所以i的发生改变
std::cout << f1() << std::endl;
//5,i值发生改变,现在i=5
std::cout << i < std::endl; 

⚠️ 既然引用,那么在调用这个lambda表达式的时候,就必须确保该lambda表达式里的引用的变量没有超过这个变量的作用域(保证有效性)。

(3) [=]: 捕获外部作用域中所有变量,并作为副本(按值)在函数中使用,也就可以用它的值,但不能给它赋值

看如下范例:

int i = 9;
auto f1 = [=]{//这就非法了,不可以给它赋值,因为是以值方式捕获//使用该值(返回该值),就可以//i = 5;return i;
}
//9, 调用了lambda表达式
std::cout << f1() << std::endl;

⚠️ 不可以给它赋值,因为是以值方式捕获,使用该值(返回该值),就可以。

(4) [this]:一般用于类中,捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限。如果已经使用了"&“或者”=",则默认添加了此项(this项)。也就是说,捕获this的目的就是在lambda表达式中使用当前类的成员函数和成员变量。

看如下范例:

class CT{public:int m_i = 5;void myfuncpt(int x, int y){//无论用this还是&,=都可以读取成员变量的值auto mylambda1 = [this]{//是获取不到形参x,y的值的//有this,这个访问才合法,有&、=也可以return m_i;};std::cout << mylambda1() << std::endl;}
};
//main函数使用如下:
CT ct;
//5
ct.myfuncpt(3, 4);

⚠️ 针对成员变量,[this] 或者[=]可以读取,但不可以修改,如果想修改,可以使用[&]。

(5) [变量名]:按指捕获 和 [& 变量名]:按引用捕获:

[变量名]:按值捕获(不能修改)变量名所代表的变量,同时不能捕获其他变量。
[& 变量名]:按引用捕获(可以修改)变量名所代表的变量,同时不能捕获其他变量。

在前面CT类的myfuncpt成员函数中,因为没有捕获形参x和y的值,所以无法在lambda表达式中使用形参x和y。
如果lambda表达式使用x和y的值,可以如下修改:

//不能在lambda表达式中修改x,y值
auto mylambda1 = [this, x, y]{...};

也可以修改如下这样:

//不能在lambda表达式中修改x,y值
auto mylambda1 = [=]{...};
//可以在lambda表达式中修改x,y值
auto mylambda1 = [&]{...};

对于按引用捕获变量名所代表的变量,看看如下范例:

//只可以使用修改x的值
auto mylambda1 = [&x]{...};
//只可以使用修改x和y的值
auto mylambda1 = [&x, &y]{...};

(6) [=, & 变量名]:按值捕获所有外部变量,但按引用&中所指的变量,这里的=必须写在开头的位置,开头的位置表示默认捕获的方式

看如下CT类的myfuncpt成员函数中的lambda表达式:

auto mylambda1 = [this, &x, y]{x = 8;...return m_i;
}

⚠️ auto mylambda1 = [this, &x, y] 也可以写成 auto mylambda1 = [=, &x]也可以。

(7) [&, 变量名]:按引用捕获所有外部变量,但按值捕获变量名所代表的变量。这里的&必须写在开头的位置,开头的位置表示默认捕获的方式

下面这样是不行的:

//这样不行,开始制定了默认捕获,后来又指定引用捕获,编译器汇报错
auto f = [&, &x]{...}

修改为正确如下:

auto f = [&, x]{...}

三、lambda表达式 - 延时调用易出错细节分析

看如下范例:

int x = 5;
auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了return x;
};
x = 10;
//5, return的x是5而不是10
std::cout << f() << std::endl;

⚠️ auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了,所以打印的是5而不是10。

那怎么办呢?
办法是按引用方式捕获:

auto f = [&]{...}

四、lambda表达式 - 如何使用mutable

mutable(易变的)并不陌生,mutable关键字,它的作用就是不管是不是一个常量属性的变量,只要mutable在,就能修改其值。

int x = 5;
auto f = [=]() mutable {//没有mutable,这个x是不允许修改的x = 6;return x;
};
x = 10;
//6, return 的x是6而不是10
std::cout << f() << std::endl;

⚠️ 正常lambda表达式没有参数时()是可以省略的,但是如果要是使用mutable,lambda表达式中就算没有参数时()是也不可以省略()的,必须写出来。

下面是不合法的:

auto f = [=] mutable {...}; //即便没有参数,也不可以把mutable前面的()省略

五、lambda表达式 - 作为匿名的类类型对象

lambda表达式的类型被称为闭包类型。闭包先理解成:函数内的函数(可调用对象)。

这个lambda表达式是一种比较特殊的、匿名的、类型(闭包类)的对象,也就是说有定义了一个类类型,有生成一个匿名的该类的对象(闭包)。可以认为它是一个带有operator()的类类型对象,也就是仿函数(函数对象)或者说是可调用对象。

所以,也可以使用std::function和std::bind来保存和调用lambda表达式。每个lambda都会出发编译器生成一个独一无二的类类型(及所返回的该类类型对象)。

(1)lambda表达式在std::function的使用

看如下两个范例:

范例1:

std::function<int(int)> fc1 = [](int tv){return tv;}
std::cout << fc1(15) << std::endl; //15

范例2:

std::vector<std::function<bool(int)>> gv;
void func(){srand((unsigned)time(NULL));int tmpvalue =  rand % 6gv.push_back([=](int tv){  //如果是引用[&],会不会造成未定义行为?思考一下。if (tv % tmpvalue == 0)return true;return false;});
}
int main(){func();std::cout << gv[0](10) << std::endl;
}

(2)lambda表达式在std::bind的使用

看如下范例:

//bind第一个参数是函数指针,第二个参数开始就是真正的函数参数
std::function<int(int)> fc2 = std::bind([](int tv){return tv;}, std::placeholders::_1);
std::cout << fc2(15) << std::endl; //15

在不捕获任何变量,也就是捕获列表为空(因为类是有this的概念,普通函数是没有这个概念的),lambda表达式可以转换成一个普通的函数指针,看如下范例:

using functype = int (*)(int); //定义一个函数指针类型
functype fp = [](int tv){return tv;};
std::cout << fp(17) << std::endl;  //17

六、lambda表达式 - 在for_each和find_if中使用

(1)for_each中lambda表达式

for_each 其实是一个函数模版,一般是用来配合函数对象使用的,第三个参数就是一个函数对象(可以给进去一个 lambda 表达式)。

看如下范例:

    std::vector<int> myvector = { 10, 20, 30, 40, 50};int isum = 0;std::for_each(myvector.begin(), myvector.end(), [&isum](int value){isum += value;std::cout << value << std::endl;});std::cout << "sum = " << isum << std::endl;

输出结果:

10
20
30
40
50
sum = 150

(2)find_if中lambda表达式

find_if 其实也是一个函数模版,一般用来查找一个什么东西,要查什么取决于他的第三个参数,第三个参数也是一个函数对象(也可以给进去一个 lambda 表达式)。

看如下范例:
只要返回false, find_if 就不停地遍历myvector,一直返回true为止。

    auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){std::cout << value << std::endl;//只要返回false, find_if就不停地遍历myvector,一直返回true为止return false;});

利用 find_if返回 true 停止这个特性,就可以寻找myvector中第一个值”>15“的元素。
⚠️ find_if的调用返回一个迭代器,只向第一个满足条件的元素。如果这样的元素不存在,则这个迭代器会指向myvector.end()。
修改后代码如下:

    auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){if (value > 15)return true;return false;});if (result == myvector.end())std::cout << "没找到" << std::endl;else std::cout << "没找到了, 结果为:" << *result<< std::endl; //找到了,结果为20

七、小结

lambda的优点:
善用lambda,让代码更简洁、更灵活、更强大、提高开发效率、可维护性等。


http://www.ppmy.cn/news/442395.html

相关文章

macbook pro M1 外接4K显示器模糊

macbook pro M1 外接4K显示器模糊 macos ventura 13.0 外接4k显示器模糊 之前没有任何问题&#xff0c;午睡后外接显示器突然模糊&#xff0c;默认分辨率为1920*1080P 30HZ。调节显示器分辨率没有用&#xff0c;并且M1的ventura显示器没有缩放功能&#xff01;&#xff01;&a…

nexus7二代刷Linux,大饱眼福: Nexus 7二代全拆解

Nexus 7二代外观非常Sexy&#xff0c;所以看它拆解也是一种享受。iFixit 给Nexus 7二代的可修复得分为7分(10分是最容易修复的分值)。iFixit称&#xff0c;Nexus 7的外壳非常容易撬开&#xff0c;平板电脑的电池的更换难度也很低&#xff0c;根本不需要焊接&#xff0c;甚至连螺…

Melodyne 5.2现在原生支持苹果M1

在新的5.2版本中&#xff0c;Melodyne现在同时支持英特尔芯片和苹果电脑上新的M1处理器&#xff0c;使Melodyne可以在最新的Mac上原生运行&#xff0c;并充分利用它们的处理能力。这适用于Melodyne的所有版本&#xff0c;无论是独立模式还是作为插件-也可以通过ARA在几乎所有兼…

Mac M1芯片用Nexus搭建Maven私服

由于本人也是第一次运用Mac进行开发工作,所以对于后端的很多环境配置也是一路的摸爬滚打,废话不多说,直接上干货. 首先,我看到很多博主说Mac 可以使用Brew 命令去安装Nexus,但是在我和Nexus官方的技术人员沟通过之后,他们告诉我,Homebrew已经单方面把Nexus锁死了,估计就是不能用…

魅族的android m l,魅族两款新机齐曝光:其中一款竟然采用原生 Android

原标题&#xff1a;魅族两款新机齐曝光&#xff1a;其中一款竟然采用原生 Android 魅族 15 还没发布多久&#xff0c;然而 魅族 16 就要来了 根据前段时间国家专利局公布的一项魅族专利外观来看&#xff0c;狗叔猜测这就是下半年魅族要发布的 魅族 16 在外观设计上&#xff0c;…

双11新机种草:搭载One Mind的魅族手机成用户心头好

明天就是一年一度的双11了&#xff0c;很多朋友早已填满购物车蓄势待发。除了日用品&#xff0c;人们消费最多的可能就是3C产品&#xff0c;特别是手机&#xff0c;趁着双11折扣力度空前&#xff0c;想必不少手机爱好者更是摩拳擦掌&#xff0c;跃跃欲试。。然而现阶段智能手机…

又搭载8核MTK!魅族或推机海战术应战4G

一众期货手机搞了一轮狂欢节后&#xff0c;抢去了大片目光&#xff1b;堪称手机界的海天盛筵&#xff01;两家公司加起来单日狂吸了接近30亿人民币&#xff0c;占2013年天猫双十一接近10%&#xff0c;成绩可谓十分闪亮&#xff01;按这种模式继续发展下去&#xff0c;下一波的狂…

Nexus的详细介绍以及安装

转载自&#xff1a;http://blog.csdn.net/jiuqiyuliang/article/details/49407455 简介 Nexus是Maven仓库管理器&#xff0c;也可以叫Maven的私服。Nexus是一个强大的Maven仓库管理器&#xff0c;它极大地简化了自己内部仓库的维护和外部仓库的访问。利用Nexus你可以只在一个地…