【C++练级之路】【Lv.23】C++11——可变参数模板、lambda表达式和函数包装器



快乐的流畅:个人主页


个人专栏:《算法神殿》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 一、可变参数模板
    • 1.1 参数包的概念
    • 1.2 参数包的展开
    • 1.3 emplace系列
  • 二、lambda表达式
    • 2.1 lambda的格式
    • 2.2 捕捉列表
    • 2.3 lambda的原理
    • 2.4 lambda的优势
  • 三、函数包装器
    • 3.1 function
    • 3.2 bind

一、可变参数模板

C++11更新后,可以创建接受可变参数的函数模板和类模板。

1.1 参数包的概念

以下是基本可变参数的函数模板:

template <class... Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}

void test()
{
 	ShowList();
 	ShowList(1);
 	ShowList(1, 2.4);
 	ShowList(1, 2.4, 'g');
 	ShowList(1, 2.4, 'g', 3.56);
}
  • Args是一个模板参数包,args是一个函数参数包
  • 参数包中可以包含0到任意个模板参数
  • sizeof…运算符可以获取可变参数模板中参数的数量

ps:参数前面有省略号,就是一个可变模版参数,我们把带省略号的参数称为“参数包”
ps:对于可变参数模板,编译器会从函数的实参推断模板参数类型。同时,编译器还会推断包中参数的数目。

1.2 参数包的展开

可变模版参数的一个主要特点,便是无法直接获取参数包中的每个参数,所以只能通过展开参数包的方式来获取参数包中的每个参数。


递归方式:

// 递归终止函数
void _ShowList()
{
	cout << endl;
}
// 展开函数
template <class T, class... Args>
void _ShowList(const T& val, Args... args)
{
	cout << val << " ";
	_ShowList(args...);
}

template <class... Args>
void ShowList(Args... args)
{
	_ShowList(args...);
}

利用子函数_ShowList每次获取参数包的第一个元素并打印,然后继续传递参数包,直到参数包没有参数,调用递归终止函数。


数组方式:

template<class T>
int PrintArgs(T val)
{
	cout << val << " ";
	return 0;
}

template<class... Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArgs(args)...};
	cout << endl;
}

利用数组初始化的过程展开参数包并打印。

以下有一种更简洁的写法,运用了逗号表达式和折叠表达式(C++17)

template<class T>
void PrintArgs(T val)
{
	cout << val << " ";
}

template<class... Args>
void ShowList(Args... args)
{
	(PrintArgs(args), ...);
	cout << endl;
}

1.3 emplace系列

STL容器中emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对于insert系列,emplace系列接口的优势到底在哪里呢?

接口展示:

//emplace系列
template <class... Args>
 	void emplace_back (Args&&... args);
//insert系列
template <class T>
	void push_back (const T& val);
	void push_back (T&& val);

单参数:

void test()
{
	list<my::string> lt;//此处运用my::string,便于调试和观察
	my::string s1 = "1111";
	lt.push_back(s1);
	lt.push_back(move(s1));

	my::string s2 = "2222";
	lt.emplace_back(s2);
	lt.emplace_back(move(s2));

	cout << endl;
	lt.push_back("3333");
	lt.emplace_back("3333");
}

其实,插入s1和s2的过程没有任何区别,有一个细微的区别在于直接插入"3333"时,push_back是构造+移动构造,而emplace_back是构造,相比之下只是少了一个移动构造,差别不大。

那么,为什么emplace_back是构造呢?因为参数包层层往下传递,直到节点的构造函数,在初始化列表中才解析出具体类型,所以就可以直接在节点上进行构造


多参数:

void test()
{
	list<pair<my::string, int>> lt;
	lt.push_back(make_pair("1111", 1));
	lt.push_back({ "2222",2 });

	lt.emplace_back(make_pair("3333", 3));
	lt.emplace_back("4444", 4);
}

因为emplace_back的形参是参数包,所以可以写成多参数的形式传入

二、lambda表达式

2.1 lambda的格式

lambda表达式书写格式

  • [capture-list] (parameters) mutable -> return-type { statement }
  1. [capture-list] : 捕捉列表。捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  2. (parameters):参数列表
  3. mutable:修饰符。mutable可以取消其常量性,让传值捕捉的变量(默认为const)可以被修改。
  4. ->returntype:返回值类型
  5. {statement}:函数体。在该函数体内,可以使用参数和捕获的变量

ps:若不需要传参,则可省略参数列表。
ps:使用mutable修饰,则不可省略参数列表。
ps:返回值类型明确时,可省略,编译器自动推导。

2.2 捕捉列表

捕捉方式分两种,传值捕捉和传引用捕捉。

传值捕捉:

void test()
{
	int x = 1, y = 2;
	//[var]
	auto f1 = [x, y] {cout << x << " " << y << endl; };
	//[=]
	auto f2 = [=] {cout << x << " " << y << endl; };
}

class A
{
public:
	void print()
	{
		//[this]
		auto f3 = [this] {cout << _a1 << " " << _a2 << endl; };
	}
private:
	int _a1, _a2;
};
  • [var]:传值捕捉var变量
  • [=]:传值捕捉所有变量(包括this)
  • [this]:传值捕捉this指针

传引用捕捉:

void test()
{
	int x = 1, y = 2;
	//[&var]
	auto f1 = [&x, &y] {cout << x << " " << y << endl; };
	//[&]
	auto f2 = [&] {cout << x << " " << y << endl; };
}
  • [&var]:传引用捕捉var变量
  • [&]:传引用捕捉所有变量(包括this)

ps:可以混合捕捉,但不能重复捕捉。
ps:只能捕捉父作用域中的局部变量(父作用域,指包含lambda的语句块)。

2.3 lambda的原理

lambda表达式,底层原理就是仿函数(类似于范围for的底层是迭代器)。

先看看以下代码:

void test()
{
	auto f1 = [](int x) {cout << x << endl; };
	f1(1);
	cout << typeid(f1).name() << endl;

	auto f2 = [](int x) {cout << x << endl; };
	f2(2);
	cout << typeid(f2).name() << endl;
}

对于用户,lambda是匿名函数对象,所以用auto接收。但是即使定义完全相同的两个lambda,其函数类型还是不同。所以 lambda表达式之间不能相互赋值,它们之间的类型是互不相同的。


再看看以下代码:

class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};

void test()
{
	double rate = 0.49;
	// 函数对象
	Rate r1(rate);
	r1(10000, 2);
	// lambda
	auto r2 = [=](double monty, int year)->double 
		{return monty * rate * year;};
	r2(10000, 2);
}

使用方式相同:

  • 函数对象将rate作为其成员变量,在定义对象时给出初始值即可。
  • lambda表达式通过捕获列表可以直接捕获到该变量。

底层实现相同:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

2.4 lambda的优势

对于一个商品类Goods:

struct Goods
{
	string _name;
	double _price;
	int _evaluate;

	Goods(const char* str, double price, int evaluate)
		: _name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

我们想按不同方式取比较,进行排序。

仿函数:

struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};

struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};

void test()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}

lambda表达式:

void test()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });
}

仿函数和lambda表达式比较:

  • 仿函数的方式比较笨重,且类名必须取的清晰,否则容易混淆不清。
  • lambda表达式则简洁清晰,在原本传参的位置定义,可以清楚知道比较的方式。

三、函数包装器

目前,我们学习了三种回调函数的方式:函数指针、仿函数和lambda表达式。而在传参时,函数模板会因不同的类型而产生多份实例化,导致效率低下。所以,我们希望有统一的类型可以接收可调用对象,以达到包装的目的,提高效率。

3.1 function

function是一个模板类,提供了一种通用的、多态的函数封装,是一个函数包装器(适配器)

template <class T> function;     // undefined
template <class Ret, class... Args> class function<Ret(Args...)>;

ps:Ret是被调用函数的返回类型,Args是被调用函数的参数类型。


请看看以下代码:

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

struct SwapFunctor
{
	void operator()(int& x, int& y)
	{
		int tmp = x;
		x = y;
		y = tmp;
	}
};

void test()
{
	//函数指针
	function<void(int&, int&)> f1 = Swap;
	//函数对象
	function<void(int&, int&)> f2 = SwapFunctor();
	//lambda表达式
	function<void(int&, int&)> f3 = [](int& x, int& y)
		{
			int tmp = x;
			x = y;
			y = tmp;
		};
}

function提供了统一的类型来接收不同类型的可调用对象,包括函数指针、仿函数和lambda表达式等,实现了函数包装。


比较特殊的,是function接收类的成员函数:

class Plus
{
public:
	static int plusi(int x, int y)
	{
		return x + y;
	}

	double plusd(double x, double y)
	{
		return x + y;
	}
};

int main()
{
	//类的静态成员函数
	function<int(int, int)> f1 = Plus::plusi;
	//类的普通成员函数
	function<double(Plus*, double, double)> f2 = &Plus::plusd;
	Plus p;
	f2(&p, 1.1, 2.2);

	function<double(Plus, double, double)> f3 = &Plus::plusd;
	f3(Plus(), 1.1, 2.2);
	return 0;
}
  • function接收类的成员函数,要& + 类域(静态成员函数不用&)。
  • 因为类的成员函数有隐含的参数this指针,所以function内部的类型要加上类指针。
  • 由于传入指针比较麻烦,所以编译器做了特殊处理,可以传入类。

leetcode 150.逆波兰表达式求值

逆波兰表达式(function化简版):

class Solution
{
public:
    int evalRPN(vector<string>& tokens)
    {
        stack<int> st;
        unordered_map<string, function<int(int, int)>> hash = 
        {
            {"+", [](int x, int y){return x + y;}},
            {"-", [](int x, int y){return x - y;}},
            {"*", [](int x, int y){return x * y;}},
            {"/", [](int x, int y){return x / y;}}
        };

        for(auto& str : tokens)
        {
            if(hash.count(str))
            {
                int right = st.top();st.pop();
                int left = st.top();st.pop();
                st.push(hash[str](left, right));
            }
            else st.push(stoi(str));
        }

        return st.top();
    }
};

3.2 bind

bind是一个模板函数,可以接收一个可调用对象,进行函数参数绑定,返回一个绑定后的对象

//simple(1)	
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);

ps:fn是可调用对象,args是需要进行绑定的参数列表。


bind的绑定方式分两种,绑定值和绑定占位符

int Sub(int x, int y)
{
	return x - y;
}

void test()
{
	function<int(int, int)> f1 = Sub;
	f1(10, 5);
	//绑定值——调整参数个数
	function<int()> f2 = bind(Sub, 20, 10);
	f2();
	//绑定占位符——调整参数顺序
	function<int(int, int)> f3 = bind(Sub, placeholders::_2, placeholders::_1);
	f3(10, 5);
	//同时绑定值和占位符
	function<int(int)> f4 = bind(Sub, 20, placeholders::_1);
	f4(10);
}
  • placeholders是与bind一起使用的工具,用于指定绑定表达式中的占位符。
  • _1代表函数调用实参的第一个位置,依此类推至 _n。
  • 每次函数调用时,传入的实参会对应到bind的参数列表,再传入调用的函数。
  • 绑定值后,function内的类型要相应的变化(参数类型个数要减少)

对于之前function接收类的成员函数,我们可以用bind进行化简:

class Plus
{
public:
	double plusd(double x, double y)
	{
		return x + y;
	}
};

void test()
{
	//function<double(Plus, double, double)> f3 = &Plus::plusd;
	//f3(Plus(), 1.1, 2.2);

	function<double(double, double)> f3 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
	f3(1.1, 2.2);
}

运用bind将Plus()参数固定绑死,这样在传参时就不用每次传入,变得更加简洁。


真诚点赞,手有余香

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

二级指针简单介绍

我们之前学习的&#xff1a;变量的地址是存入指针变量中的&#xff0c;然而指针变量也是变量&#xff0c;是变量就有地址&#xff0c;那么指针变量的地址存放在哪里 &#xff1f; 这也就是二级指针 #include<stdio.h> int main() {int a10;int*p&a;int**pp&p;re…

假指纹与活体指纹检测

目录 1. 假指纹简介 2. 假指纹制作流程 3. 活体指纹检测 4. 活体指纹检测竞赛 1. 假指纹简介 随着科学技术的发展&#xff0c;指纹技术以各种各样的形式进入了我们的生活。在大多数情况下&#xff0c;指纹识别应用于移动设备和桌面设备解决方案&#xff0c;以提供安全方便的…

Linux命令篇(一):文件管理部分

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 1、cat命令常用参…

2.3Docker部署java工程

2.3Docker部署java工程 1.导入jar包 2.在Docker部署jdk&#xff08;容器名为myjdk17&#xff09; 3.修改jar包名 mv 原包名 新包名4. 配置启动脚本 Dockerfile是一个文本文件&#xff0c;其中包含了构建 Docker 镜像所需的一系列步骤和指令。通过编写 Dockerfile 文件&…

华为交换机的基本配置

实验拓扑&#xff1a; 实验目的&#xff1a;认识二层交换机和二层交换技术的工作原理&#xff1b;认识三层交换和三层交换技术。 三层功能简而言之就是了具有路由的功能&#xff0c;设备可以充当网关和路由器。 实验要求&#xff1a;公司的两个部门用vlan进行划分&#xff0c…

Vitis HLS 学习笔记--HLS流水线类型

目录 1. 简介 2. 优缺点对比 2.1 Stalled Pipeline 2.2 Free-Running/Flushable Pipeline 2.3 Flushable Pipeline 3. 设置方法 4. FRP的特殊优势 5. 总结 1. 简介 Vitis HLS 会自动选择正确的流水线样式&#xff0c;用于流水打拍函数或循环。 停滞的流水线&#xff…

MaxKey本地运行实战指南

MaxKey 本地运行总结 概述开发环境准备 主页传送门 &#xff1a; &#x1f4c0; 传送 概述 MaxKey单点登录认证系统&#xff0c;谐音为马克思的钥匙寓意是最大钥匙&#xff0c;是业界领先的IAM-IDaas身份管理和认证产品&#xff1b;支持OAuth 2.x/OpenID Connect、SAML 2.0、J…

Layui2.5.6树形表格TreeTable使用

1、问题概述? Layui2.5.6的树形表格-TreeTable终于用明白了,步骤详细,提供源码下载。 如果你使用的是Layui2.8+版本,那么点个赞,赶紧去官网看吧,官网更行了。 更新地址:树表组件 treeTable - Layui 文档 最近在项目中需要使用到树形表格,用来显示菜单的层级关系,当…

集成Google Authenticator实现多因素认证(MFA)

目录 参考1、应用背景2、多因素认证3、谷歌google authenticator集成用法3.1、原理3.2、 MFA绑定3.2.1、 用户输入用户名密码登录3.2.2、检查是否已经绑定MFA&#xff08;检查数据库是否保存该用户的google secret&#xff09;3.2.3、谷歌身份证认证器扫描绑定3.2.4、手动测试验…

016、集合_命令

集合(set)保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。如图集合user:1:follow包含着"it"、“music”、“his”、"sports"四个元素,一个集合最多可以存储2(32)-1个元…

基于web的网上村委会业务办理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;办事记录管理&#xff0c;办事申请管理&#xff0c;党员发展管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;补贴活动通知…

14.1 Go语言代码格式、gofmt工具、配置编辑器、命名约定

1. Go语言代码格式 代码格式指的是在语法正确的前提下&#xff0c;源代码的书写和组织风格。比如什么时候缩进&#xff0c;什么时候换行&#xff0c;什么时候加空格&#xff0c;表示块边界的花括号是跟上一行放在一起还是自己独占一行等等。这些看似无关紧要的细节其实问题颇多…

LabVIEW齿轮调制故障检测系统

LabVIEW齿轮调制故障检测系统 概述 开发了一种基于LabVIEW平台的齿轮调制故障检测系统&#xff0c;实现齿轮在恶劣工作条件下的故障振动信号的实时在线检测。系统利用LabVIEW的强大图形编程能力&#xff0c;结合Hilbert包络解调技术&#xff0c;对齿轮的振动信号进行精确分析…

Vector - CAPL - XCP介绍_04

目录 xcpGetCalPage & OnXcpGetCalPage xcpUpload & OnXcpUoad 代码示例 xcpSendRaw & OnXcpSendRaw 代码示例 xcpGetCalPage & OnXcpGetCalPage 功能&#xff1a;如果XCP从设备支持校准数据页面切换&#xff0c;则该命令获取当前页面和访问模式。 回调返回…

数据在计算机内的表示和存储

目录 一.C语言中强制类型转换 二.数据的存储方式 1.大小端模式 2.边界对齐 一.C语言中强制类型转换 1.无符号数与有符号数&#xff1a;不改变数据内容&#xff0c;只是改变了计算机的解释方式 例如下图&#xff0c;短整型转为无符号短整型再赋值给y&#xff0c;yx1110 11…

打靶笔记w1r3s.v1.0

打靶笔记w1r3s.v1.0 nmap扫描与分析 主机发现 nmap -sn 192.168.218.0/24 历史版本为-sP(已经被放弃) n 不进行端口扫描192.168.218.155 创建文件夹保存端口信息 指定最低1万速率扫描所有端口 nmap -sT --min-rate 10000 -p- 192.168.218.155 nmapscan/ports-sS SYN扫描是快…

ubuntu安装notion

一、背景&#xff1a; 不用windwos系统&#xff0c;完全可以&#xff0c;然后基本软件都有&#xff0c;怎么安装notion呢 二、步骤 1. 更新源 echo "deb [trustedyes] https://apt.fury.io/notion-repackaged/ /" | sudo tee /etc/apt/sources.list.d/notion-repa…

【一小时学会Charles抓包详细教程】Charles移动端APP抓包之iOS手机端数据提取实战篇 (8)

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;程序猿、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 Charles 移动端…

详解C/C++动态内存函数(malloc、free、calloc、realloc)

1. malloc和free 为解决静态内存开辟存在的问题&#xff0c;C语言提供了一个动态内存开辟的函数&#xff1a; malloc为memory allocation的简写&#xff0c;意为内存分配。 这个函数的作用是向内存申请一块连续可用的空间&#xff0c;并返回指向这块空间的指针。 比如&#xf…

Adversarial Nibbler挑战:与多元社区持续开展开放红队测试

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…