21天学通C++:理解函数对象、Lambda表达式

news2024/11/15 12:15:07

第二十一章:理解函数对象

函数对象(也叫 functor)。

函数对象与谓词的概念

从概念上说,函数对象是用作函数的对象;

但从实现上说,函数对象是实现了 operator() 的类的对象。

虽然函数和函数指针也可归为函数对象,但实现了 operator() 的类的对象才能保存状态(即类的成员属性的值),才能用于标准模板库(STL)算法

C++程序员常用于 STL 算法的函数对象可分为下列两种类型。

• 一元函数:接受一个参数的函数,如 f(x)。如果一元函数返回一个布尔值,则该函数称为谓词。
• 二元函数:接受两个参数的函数,如 f(x, y)。如果二元函数返回一个布尔值,则该函数称为二元谓词。

所谓几元谓词,这和函数有几个参数有关,并且该函数需要返回 布尔值 才能被称为谓词。

函数对象的典型用途

如何在 C++编程中使用函数对象?

一元函数

只对一个参数进行操作的函数称为一元函数。

使用一元函数将集合的内容显示在屏幕上:

// A unary function 
template <typename elementType> 
void FuncDisplayElement (const elementType& element) 
{
	cout << element << ' '; 
};

函数 FuncDisplayElement 接受一个类型为模板化类型 elementType 的参数,并使用控制台输出语句 std::cout 将该参数显示出来。该函数也可采用另一种表现形式,即其实现包含在类或结构的operator()中:

// Struct that can behave as a unary function 
template <typename elementType> 
struct DisplayElement 
{ 
	void operator () (const elementType& element) const 
	{ 
		cout << element << ' '; 
	} 
};

在这里插入图片描述

这两种实现都可用于 STL 算法 for_each(),将集合中的内容显示在屏幕上,每次显示一个元素,代码如下:

#include <iostream> 
#include <algorithm>
#include <vector>
#include <list>
using namespace std;

template<typename elementType>
struct DisplayElement {
	void operator()(const elementType& element) const {
		cout << element << ' ';
	}
};


int main() {
	vector<int> numsInVec{ 0, 1, 2, 3, -1, -9, 0, -999 };
	cout << "Vector of integers contains: " << endl;

	for_each(numsInVec.begin(), numsInVec.end(), DisplayElement<int>());

	// Display the list of characters
	list<char> charInList{ 'a','z','k','d' };
	cout << endl << "List of characters contains: " << endl;
	for_each(charInList.begin(), charInList.end(), DisplayElement<char>());

	return 0;
}

在这里插入图片描述

上面代码中包含了函数对象DisplayElement,它实现了 operator( ) 完成了打印任务。

一元谓词

返回布尔值的一元函数是谓词。这种函数可供 STL 算法用于判断。

谓词判断输入元素是否为初始值的整数倍:

template<typename numberType>
struct IsMultiple {
	numberType Divisor;

	IsMultiple(const numberType& divisor) {
		Divisor = divisor;
	}

	bool operator() (const numberType& element) const {
		// 判断这个数字是否为另一个数字的整数倍
		return ((element % Divisor) == 0);
	}
};

这里的 operator( )返回布尔值,可用作一元谓词。该结构有一个构造函数,它初始化除数的值。然后用保存在对象中的这个值来判断要比较的元素是否可以被它整除,如 operator( )的实现所示,它使用数学运算取模%来返回除法运算的余数。然后将余数与零进行比较,以判断被除数是否为除数的整数倍。

一元谓词被大量用于 STL 算法中。例如,算法 std::partition()使用一元谓词来划分范围,算法
stable_partition() 也使用一元谓词来划分范围,但保持元素的相对顺序不变。诸如 std::find_if( )等查找函
数以及 std::remove_if( )等删除元素的函数也使用一元谓词,其中 std::remove_if( )删除指定范围内满足谓词条件的元素。

二元函数

如果函数 f(x, y)根据输入参数返回一个值,它将很有用。这种二元函数可用于对两个操作数执行运算,如加、减、乘、除等。

二元谓词

接受两个参数并返回一个布尔值的函数是二元谓词。这种函数常常用于诸如 std::sort( )等 STL 函数中。

对字符串进行不区分大小写排序的二元谓词:

class CompareStringNoCase {
public:
	bool operator() (const string& str1, const string& str2) const {
		string str1LowerCase;
		str1LowerCase.resize(str1.size());
		//把每个字符都转换成小写
		transform(str1.begin(), str1.end(), str1LowerCase.begin(), ::tolower);
		
		string str2LowerCase;
		str2LowerCase.resize(str2.size());
		transform(str2.begin(), str2.end(), str2LowerCase.begin(), ::tolower);

		return (str1LowerCase < str2LowerCase);
	}
};

在 operator( ) 中实现的二元谓词中,首先使用 std::transform( )将输入字符串转换为小写,然后使用字符串的比较运算符 < 进行比较,并返回结果。

很多 STL 算法都使用二元谓词。例如,删除相邻重复元素的 std::unique( )、排序算法 std::sort( )、排序并保持相对顺序的 std::stable_sort( )以及对两个范围进行操作的 std::transform( ),这些 STL 算法都需要使用二元谓词。

总结

本章介绍了函数对象(也叫 functor)。在结构或类中实现函数对象时,它将比简单函数有用得多,因为它也可用于存储与状态相关的信息本章还介绍了谓词,它是一类特殊的函数对象。另外,还通过一些实际示例说明了谓词的用途。

第二十二章:Lambda 表达式

lambda 表达式是一种定义匿名函数对象的简洁方式,这是 C++11 新增的。

lambda 表达式是什么

可将 lambda 表达式视为包含公有 operator( )的匿名结构(或类),从这种意义上说,lambda 表达式属于第 21 章介绍的函数对象。

深入分析如何编写 lambda 表达式前,先看一个在二十一章中提到过的示例程序:

// struct that behaves as a unary function 
template <typename elementType> 
struct DisplayElement 
{ 
	void operator () (const elementType& element) const 
	{ 
		cout << element << ' '; 
	} 
};

这个函数对象使用 cout 将 element 显示到屏幕上,通常用于 std::for_each()等算法中:

// Display every integer contained in a vector 
for_each (numsInVec.cbegin (), // Start of range 
			numsInVec.cend (), // End of range 
			DisplayElement <int> ()); // Unary function object

如果使用 lambda 表达式,可将上述代码(包括函数对象的定义)简化为下述 3 行:

// Display every integer contained in a vector using lambda exp. 
for_each (numsInVec.cbegin (), // Start of range 
			numsInVec.cend (), // End of range
			[](const int& element) {cout << element << ' ';});

编译器见到下述 lambda 表达式时:

[] (const int& element) { cout << element << ' '; });

自动将其展开为类似于结构 DisplayElement的表示:

struct NoName 
{ 
	void operator () (const int& element) const 
	{ 
		cout << element << ' '; 
	} 
};

lambda 表达式也叫 lambda 函数 。

如何定义 lambda 表达式

lambda 表达式的定义必须以方括号([])打头。这些括号告诉编译器,接下来是一个 lambda 表达式。方括号的后面是一个参数列表,该参数列表与不使用 lambda 表达式时提供给 operator( )的参数列表相同。

一元函数对应的 lambda 表达式

与一元 operator(Type)对应的 lambda 表达式接受一个参数,其定义如下:

[](Type paramName) {  
	// lambda expression code here
}

请注意,如果您愿意,也可按引用传递参数:

[] (Type& paramName){
	// lambda expression code here
}

在算法 for_each( )中使用 lambda 表达式而不是函数对象来显示容器中的元素:

#include <iostream> 
#include <algorithm>
#include <vector>
#include <list>

using namespace std;

int main() {
	vector<int> numsInVec{ 101, -4, 500, 21, 42, -1 };
	list<char> charsList{ 'a', 'h', 'z', 'k', 'l' };
	cout << "Display elements in a vector using a lambda: " << endl;
	
	// Display the array of integers
	for_each(numsInVec.cbegin(), numsInVec.cend(), [](const int& element) {cout << element << ' '; });
	cout << endl;

	// Display the list of characters
	for_each(charsList.cbegin(), charsList.cend(), [](auto& element) {cout << element << ' '; });

	return 0;
}

在这里插入图片描述

这里使用了两个 lambda 表达式,这两个 lambda 表达式很像,只是输入参数不同,因为根据两个容器包含的元素类型对它们进行了定制。第一个 lambda 表达式接受一个 int 参数,并使用它来显示整型 vector 中的元素,每次一个;第二个 lambda 表达式接受一个 char 参数(这是编译器自动推断出来的),并使用它来显示 std::list 中的 char 元素。

一元谓词对应的 lambda 表达式

谓词可帮助您做出决策。一元谓词是返回 bool 类型(true 或 false)的一元表达式。lambda 表达式也可返回值,例如,下面的 lambda 表达式在 num 为偶数时返回 true:

[] (int& num) { return ((num % 2) == 0); }

在这里,返回值的性质让编译器知道该 lambda 表达式的返回类型为 bool。

在算法中,可将 lambda 表达式用作一元谓词

在算法 std::find_if( )中,将 lambda 表达式用作一元谓词,以查找集合中的偶数:

int main() 
{ 
	vector<int> numsInVec{ 25, 101, 2017, -50 }; 

	auto evenNum = find_if(numsInVec.cbegin(), 
							numsInVec.cend(), // range to find in 
							[](const int& num){return ((num % 2) == 0); } ); 

	if (evenNum != numsInVec.cend()) 
		cout << "Even number in collection is: " << *evenNum << endl; 
 
return 0; 
}

输出如下:

Even number in collection is: -50

算法 find_if( )对指定范围内的每个元素调用该一元谓词;如果该谓词返回 true,find_if( )将返回一个指向相应元素的迭代器 evenNum,指出找到了一个满足条件的元素。这里的谓词是一个 lambda 表达式,当 find_if( )使用一个偶数调用它(即对 2 求模的结果为零)时,它将返回 true。

通过捕获列表接受状态变量的 lambda 表达式

在上面的程序中,您创建了一个一元谓词,它在整数能被 2 整除(即为偶数)时返回 true。如果要让它更通用,在数字能被用户指定的除数整除时返回 true,该如何办呢?为此,需要让 lambda 表达式接受该“状态”—除数:

int divisor = 2; // initial valueauto element = find_if (begin of a range, 
						end of a range, 
						[divisor](int dividend){return (dividend % divisor) == 0; } );

一系列以状态变量的方式传递的参数([…])也被称为 lambda 表达式的捕获列表(capture list)。

使用存储状态的 lambda 表达式来判断一个数字能否被另一个数字整除:

#include <iostream> 
#include <algorithm>
#include <vector>
#include <list>

using namespace std;

int main() {
	vector <int> numsInVec{ 25, 26, 27, 28, 29, 30, 31 };
	cout << "The vector contains: {25, 26, 27, 28, 29, 30, 31}";
	cout << endl << "Enter divisor (> 0): ";

	int divisor = 2;
	cin >> divisor;

	// Find the first element that is a multiple of divisor
	vector<int>::iterator  element;
	element = find_if(numsInVec.begin(), 
		numsInVec.end(), 
		[divisor](int dividend) {return (dividend % divisor) == 0; });

	if (element != numsInVec.end()) {
		cout << "First element in vector divisible by " << divisor;
		cout << ": " << *element << endl;
	}

	return 0;
}

在这里插入图片描述

divisor 是一个状态变量,相当于第二十一章程序中的 IsMultiple::Divisor,因此状态变量类似于 C++11 之前的函数对象类中的成员。您可以将状态传递给 lambda 表达式,并根据状态的性质相应地使用它。

注意:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中
换句话说,如果上面程序中的 lambda 表达式中中括号内的 divisor 变量去掉的话,该表达式后边引用 divisor 的地方就会报错。

lambda 表达式的通用语法

lambda 表达式总是以方括号打头,并可接受多个状态变量,为此可在捕获列表([…])中指定这些状态变量,并用逗号分隔:

[stateVar1, stateVar2](Type& param) { // lambda code here; }

如果要在 lambda 表达式中修改这些状态变量,可添加关键字 multable:

[stateVar1, stateVar2](Type& param) mutable { // lambda code here; }

这样,便可在 lambda 表达式中修改捕获列表([])中指定的变量,但离开 lambda 表达式后,这些修改将无效。要确保在 lambda 表达式内部对状态变量的修改在其外部也有效,应按引用传递它们:

[&stateVar1, &stateVar2](Type& param) { // lambda code here; }

lambda 表达式还可接受多个输入参数,为此可用逗号分隔它们:

[stateVar1, stateVar2](Type1& var1, Type2& var2) { // lambda code here; }

如果要向编译器明确地指定返回类型,可使用->,如下所示:

[stateVar1, stateVar2](Type1 var1, Type2 var2) -> ReturnType 
{ return (value or expression ); }

最后,复合语句({})可包含多条用分号分隔的语句,如下所示:

[stateVar1, stateVar2](Type1 var1, Type2 var2) -> ReturnType 
{ 
	Statement 1; 
	Statement 2; 
	return (value or expression); 
}

如果 lambda 表达式包含多行代码,您必须显式地指定返回类型

总之,lambda 表达式的存在目的就是为了使得代码编写整体更加简洁。

二元函数对应的 lambda 表达式

二元函数接受两个参数,还可返回一个值。与之等价的 lambda 表达式如下:

[...](Type1& param1Name, Type2& param2Name) { // lambda code here; }

二元谓词对应的 lambda 表达式

返回 true 或 false、可帮助决策的二元函数被称为二元谓词。这种谓词可用于 std::sort( )等排序算法中,这些算法对容器中的两个值调用二元谓词,以确定将哪个放在前面。与二元谓词等价的 lambda 表达式的通用语法如下:

[...](Type1& param1Name, Type2& param2Name) { // return bool expression; }

总结

在这里插入图片描述

本章介绍了 C++11 新增的一项非常重要的功能:lambda 表达式。lambda 是匿名的函数对象,可接受参数、存储状态、返回值以及跨越多行。您学习了如何在 find( )、sort( )、transform( )等 STL 算法中使用 lambda 表达式,而不是函数对象。lambda 表达式可提高 C++编程速度和效率,应尽可能使用它们。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1975870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构之八大排序(下)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 数据结构之八大排序&#xff08;上&#xff09;-CSDN博客 上面博客讲述了另外六中排序算法。 目…

仓颉 -- 标识符 , 变量以及数据类型详解

仓颉 – 标识符 , 变量以及数据类型 一. 标识符 1. 普通标识符 由数字 , 字母 , 下划线构成 – cangjie , cangjie_2024由英文字母开头&#xff0c;后接零至多个英文字母、数字或下划线。由一至多个下划线开头&#xff0c;后接一个英文字母&#xff0c;最后可接零至多个英文…

phpMyAdmin 漏洞复现教程

一.登陆 账号密码 是数据库的 二.日志文件拿到shell 在sql里执行命令 可以看到是关闭状态 我们再次执行命令 让它变成on 日志文件开启 再次执行上面的命令 可以看到已经开启了 然后我们更改日志保存路径 然后查看是否更改成功 显示 更改成功 然后我们插入一句话木马 访问一下…

完成订单业务

文章目录 概要整体架构流程技术细节小结 概要 完成订单是电子商务、外卖平台、在线零售等多个行业中的一项重要业务流程。这项功能允许商家或平台将订单状态更新为“已完成”&#xff0c;表明订单已经成功交付给客户。 需求分析以及接口设计 技术细节 1.Controller层: ApiOp…

C#类和结构体的区别

1、类class是引用类型&#xff0c;多个引用类型变量的值会互相影响。存储在堆&#xff08;heap&#xff09;上 2、结构体struct是值类型&#xff0c;多个值类型变量的值不会互相影响。存储在栈&#xff08;stack&#xff09;上 类结构关键字classstruct类型引用类型值类型存储…

Study--Oracle-07-ASM故障组管理(六)

一、ORACLE ASM提供的三冗余方式 1、三种模式&#xff1a;external、normal、high 一般情况下三种模式需要的最小磁盘组&#xff1a; external 1块 normal 3块 high 5块 2、外部冗余&#xff08;external redundancy&#xff09; 表示Oracle不帮你管理镜像&#xf…

计算机网络-http协议和https的加密原理

HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是用于在万维网&#xff08;World Wide Web&#xff09;上传输超文本的基础协议。它定义了客户端&#xff08;通常是浏览器&#xff09;和服务器之间的文本数据传输格式和规则。以下是HTTP的…

J030_TCP通信

一、需求描述 使用TCP协议进行通信 1.1 一发一收 1.1.1 Client package com.itheima.tcp1;import java.io.DataOutputStream; import java.io.OutputStream; import java.net.Socket;public class Client {public static void main(String[] args) throws Exception {//1、…

嵌入式初学-C语言-练习三

#部分题目可能在之前的博客中有&#xff0c;请谅解&#xff0c;保证常见题型均被发出# 1.计算n以内所有正奇数的和 ? n值通过键盘输入 代码&#xff1a; 1 /*2 需求&#xff1a;计算n以内所有正奇数的和 ? n值通过键盘输入3 */4 #include <stdio.h>5 6 int main()7 …

用手机剪辑视频素材从哪里找?用手机视频素材库分享

视频编辑是一门充满创意的艺术&#xff0c;无论是制作短片、广告还是个人Vlog&#xff0c;都离不开高质量的视频素材。如果自己拍摄的素材不能完全满足创作需求&#xff0c;或者需要更多样化的内容来丰富视频&#xff0c;那么优质的视频素材来源至关重要。下面推荐几个提供高品…

LinuxC++(8):GDB调试

下载gdb gdb需要使用yum下载 yum -y install gdb 编译注意 需要在后面加上 -g &#xff0c;证明是要给可调试文件。 开始调试 gdb函数名 修改主函数参数 set args //set args "小红" "小华" "爱你" 在linux中显示行号 在vi下&#xff0c;输入…

C++自定义接口类设计器之函数解析二

关键代码 // 解析为函数 bool FunctionCreator::parse(const QString& lineFunc) {auto trimFunc lineFunc.trimmed();auto list trimFunc.split(" ");bool bHasReturn false;// 返回值和函数名解析for (const auto& key : list) {auto trimKey key.trim…

麦田物语第十八天

系列文章目录 麦田物语第十八天 文章目录 系列文章目录一、(Editor)制作 [SceneName] Attribute 特性二、场景切换淡入淡出和动态 UI 显示一、(Editor)制作 [SceneName] Attribute 特性 在本节课我们编写Unity的特性Attribute来更好的完善我们项目,具体是什么呢,就是当…

一款简单且强大的免费开源图片压缩软件

图压是一款简单易用且功能强大的图片压缩工具&#xff0c;适用于Windows和macOS两大操作系统。它能够在几乎不损害图片清晰度的情况下&#xff0c;显著减小图片的体积&#xff0c;特别适合需要在网页、PPT、Word、PDF中使用的图片压缩。图压的操作界面简洁&#xff0c;用户可以…

Kettle同步数据时如何借助Shell通过SSH连接MySQL数据库

在实际开发中&#xff0c;经常会用到KettleSpoon来同步数据&#xff0c;比如&#xff1a;需要定时将MySQL库某张表前一天的数据同步到SQL Server&#xff08;MySQL&#xff09;库中等等。一般由于安全性都会提供基于秘钥的连接方式&#xff0c;这种情况下如何在Kettle中连接数据…

Wordpress建站问题记录

从一月到七月因为工作的情况没有进行太深入的开发,想着整理一下把做一个独立站把博客多个渠道发布一下,遇到几个问题在这里记录一下. 先写一下我的配置 系统: centos7 php: 7.4 wordpress: 6.6.1 mysql:8.0.6 1. HTTP 500 Internal 这个问题出现在我将wordpress的文件夹全部…

运维变革背景下的运维工具衍化探讨

在数字化转型的浪潮中&#xff0c;运维领域正经历着前所未有的变革。这一变革不仅重塑了业务形态&#xff0c;也对运维工具和运维组织模式产生了深远影响。随着基础设施云化、容器化、微服务化等技术的兴起&#xff0c;运维对象、运维流程、协同关系等各个方面都发生了深刻的变…

J.U.C 原子类之AtomicIntegerFieldUpdate

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

MySQL:数据库用户

数据库用户 在关系型数据库管理系统中&#xff0c;数据库用户&#xff08;USER&#xff09;是指具有特定权限和访问权限的登录账户。每个用户都有自己的用户名和密码&#xff0c;以便系统可以通过认证来识别他们的身份。数据库用户可以登录数据库&#xff0c;在其中执行各种类…

第二十天学习笔记2024.8.2

安装mysql 1.安装软件包 centos7 中安装 mysql 8.x_wffkg-CSDN博客 2.解压 tar -xf mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 3.卸载mariadb yum remove -y *mariadb* 4.安装&#xff08;缺什么依赖补什么&#xff09; mysql-community-server-8.0.33-1.el7.x86_64.rpm 5.…