C++11,可变参数模板,lambda表达式,包装器

news2024/12/23 4:19:40

可变参数模板

在C++11中模板也可以接收多个不定参数,就和int printf(const char *format, ...);函数一般模板也可以接收多个参数;

//												可变参数模板
template<class ...Args>
void testArgs(Args... args)
{
}
int main()
{
	testArgs(123, 'a', "abc", string("yes"), vector<int>(12));
	return 0;
}

看上面的testArgs函数接收了多个不同类型的参数(可变参数包),由于模板的参数包将这些类型打包,从而使得函数可以接收多个,且不确定的参数类型;模板定义了可变参数包类型,函数接收可变参数包类型的参数;

展开可变参数包 

递归展开:

//												可变参数模板
template<class T>
void getArgs(const T& val)
{
	cout << val << endl;
}
template<class T,class... Args>
void getArgs(const T& val,Args... args)
{
	cout << val << " ";
	getArgs(args...);
}
template<class ...Args>
void testArgs(Args... args)
{
	getArgs(args...);
}
int main()
{
	testArgs(123, 'a', "abc", string("yes"));
	return 0;
}

其实这种方式就是利用了参数包的特性,会将模板参数中除去显示接收的参数剩余的参数打包成为参数包;

利用数组展开:

//利用数组展开
template<class T>
int getArg(const T& val)
{
	cout << val << " ";
	return 0;
}
template<class ...Args>
void testArgs(Args... args)
{
	int arr[] = { getArg(args)... };
}
int main()
{
	testArgs(123, 'a', "abc", string("yes"));
	return 0;
}

我们通过初始化arr这个变长数组,从而会不断调用getArg函数使得包中的参数一个个被传递,将getArg函数的返回值展开成为逗号表达式的序列,直到包为空时,停止初始化,包也被解析完毕;

emplace接口

在stl容器中我们要是细心看的话,就可以找到emplace接口;

看基本上所有的容器都支持了这个emplace接口,那么这个emplace接口到底做了什么呢?

emplace其实和我们容器前面学的insert,push_back,push这样的接口是一样的,都是用来向容器插入数据的,但是呢,emplace的插入有所不同,它是通过直接构造节点然后再插入容器中的;而我们之前所学的插入都是通过深拷贝或者移动构造拷贝出一份节点来进行构造的;

//													emplace接口
class A {
public:
	A(int a = 1)
		: _a(a)
	{
		cout << "A(int a = 1)构造函数" << endl;
	}
	A(const A& it)
		:_a(it._a)
	{
		cout << "A(const A& it)拷贝构造" << endl;
	}
	A(A&& it)
		:_a(it._a)
	{
		cout << "A(A&& it)移动构造" << endl;
	}
private:
	int _a;
};

int main()
{
	A a(10);
	system("cls");
	vector<A> v;
	vector<A> v1;
	vector<A> v2;
	vector<A> v3;
	cout << "emplace: " << endl;
	v.emplace_back(a);
	cout << "emplace: " << endl;
	v1.emplace_back(10);
	cout << "---------------------" << endl;
	cout << "push_back: " << endl;
	v2.push_back(a);
	cout << "push_back: " << endl;
	v3.push_back(10);
	return 0;
}

其实也并没有很大的优化;

lambda表达式 

lambda产生原因

在C语言时我们传递函数需要使用函数指针来传递,到了c++98的时候,我们增加了一种方式仿函数,仿函数的传递使得我们的代码更加清晰明了,但仿函数毕竟也是定义在外部的函数,我们没法直接看到函数内部的实现,从而无法清楚直到函数功能,并且随着代码量的增加,仿函数也会随着增加,使得命名冲突也会出现;

如:不同类的仿函数比较,会导致增加很多不同的仿函数类,这些类的命名会困扰程序员(其实我觉得还好,也有可能是我接触的项目代码量太少了);

为了可以清楚的直到函数功能还可以将函数传递,lambda表达式就起到了它的作用;我们可以在传递函数参数的时候直接传递lambda表达式,使得代码功能十分清晰;

也就是说lambda是一种增加啊代码易读性的方式

lambda使用

我们先使用一下lambda方法再来讲解:

//											lambda
struct student {

	student(string name,int age,int score)
		:_name(name)
		,_age(age)
		,_score(score)
	{}

	string _name;
	int _age;
	int _score;
};

int main()
{
	vector<student> v{ {"张三", 18, 96 }, {"李四", 17, 95}, {"王五", 20, 99} };
	sort(v.begin(), v.end(), [](student& s1, student& s2)->bool {return s1._age > s2._age; });
	int tmp = 1;
	return 0;
}

lambda语法

 [](student& s1, student& s2)->bool {return s1._age > s2._age; }

1.[]捕捉列表可以用来捕获当前所处作用域的内容

int main()
{
	int a = 10, b = 20;
	cout << "a: " << a << "  b: " << b << endl;
	//[a, b]()->void {//这样会报错,因为a,b为const类型,如果想要能修改的话,要加上mutable才能修改
	//	int tem = a;
	//	a = b;
	//	b = tem;
	//	}();
	[a, b]()mutable->void {//这样就可以修改了
		int tem = a;
		a = b;
		b = tem;
		}();//记住了要加上()代表执行这个匿名对象的operator()函数
	[=]()mutable->void {
		int tem = a;
		a = b;
		b = tem;
		}();
	[&a, &b]()->void {
		int tem = a;
		a = b;
		b = tem;
		}();
	[&]()->void {
		int tem = a;
		a = b;
		b = tem;
		}();
	//[&, a]()->void {//这样是错误的因为a单独被列出,是拷贝的const变量
	//	b = 10;
	//	a = 100;
	//}
	[=,&a]()mutable->void {//只有a是引用其他为拷贝
		a = 100;//成功修改
		b = 200;//修改失败
		}();

	cout << "a: " << a << "  b: " << b << endl;
	return 0;
}

[]中为变量名时例如a,b,代表获得了当前作用域中的a,b的拷贝,并且此拷贝是const类型的,不能修改的;

如果需要修改的话要再参数列表()后加上mutable修饰符,且()不能省略;

[]中为=代表获得当前作用域中所有的变量拷贝;

[]中为&加变量名时例如&a,&b,代表获得了当前作用域中的a,b的引用

[]中为&时,代表获得当前作用域中,所有变量的引用;

[]中&与变量名混合出现时,代表此变量为拷贝,其他所有变量为引用

[]中=与变量名混合出现时,代表此变量为引用,其他所有变量为拷贝

2.参数列表

这个包含的是我们传递给lambda方法的参数,lambda可以通过其内部参数才进行操作;其实就是和函数的参数是一个性质;此参数列表可以省略;

//									参数列表
int main()
{
	auto D = [](int a, int b)->int {return a + b; };
	int a = 10, b = 20;
	cout << D(a, b);
	return 0;
}

 3.返回值与{}函数体

返回值通过

->(某类型)

这样标识,标识返回一个(某类型的数据),也可以省略;

{}里面包含的是lambda方法的行为,就和函数的函数内容是一样的没有任何区别;

lambda底层

1.lambda其实在底层编译时,它被编译成了仿函数类,而我们之所以不需要显示的创建类是因为,编译器将这个类自动生成了匿名对象;

2.编译器为了区分lambda方法会自动给这个匿名对象取一个随机的名字,为了区分产生的lambda类对象这些名字不会重复;

//											证明lambda方法其实是仿函数
struct fake
{
	void operator()()
	{}
};

int main()
{
	auto L = [] {};
	fake f;
	L();
	f();
	return 0;
}

包装器

  在我们前面的编程中,对于函数,仿函数,lambda,这三种函数虽然各有千秋,但作用非常相似,那么我们有没有什么办法可以将它们三者用相同的办法整合起来呢。今天包装器它来了,我们的函数指针与lambda的类型非常复杂,而仿函数又过于笨重;使用包装器,可以将它们三者用相同的方法进行调用;下面让我们看看实现的现象吧:

包装器的使用;

function<函数返回值类型(函数参数1,函数参数2,......)>  f=函数指针等类型;

//											包装器
#include<functional>
void FunPointerAdd(int a, int b)
{
	cout << a + b << endl;
}
struct StructAdd {
	void operator()(int a, int b)
	{
		cout << a + b << endl;
	}
};

int main()
{
	int a = 5, b = 10;
	//虽然这里用auto可以直接获取lambda类型,但是我们无法显式的获取
    //当然,使用前面的decltype也是可以推出类型
	auto LambdaAdd = [](int a, int b)->void {cout << a + b << endl; };
	map<string, function<void(int a, int b)>> m = { {"函数指针",FunPointerAdd},{"仿函数",StructAdd()} ,{"lambda",LambdaAdd}};
	m["函数指针"](a,b);
	m["仿函数"](a, b);
	m["lambda"](a, b);
}

现象:

我们可以看到,我们的三种函数都被包装器接收了,所以它们可以有同样的使用方式,一种方式使用三种不同的函数;

function的底层是仿函数

我们通过上面现象,我们可以发现function这个类在使用的时候,是和函数一样的:

void FunPointerAdd(int a, int b)
{
	cout << a + b << endl;
}


int main()
{
	int a = 5, b = 10;

	function<void(int a, int b)> f = FunPointerAdd;
	f(a,b);
}

现象:

上面的现象中f本身是一个类,但是却使用了f(a,b)函数的形式调用了包装的函数,所以说其实,包装器的底层其实也是封装了仿函数来包装我们所需要的函数的;

逆波兰表达式包装器解法

150. 逆波兰表达式求值 - 力扣(LeetCode)

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int>num;
        map<string,function<int(int,int)>> m=
        {
            {"+",[](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;}}
        };
        for(int i=0;i<tokens.size();i++)
        {
            if(m.count(tokens[i]))
            {
                int right=num.top();
                num.pop();
                int left=num.top();
                num.pop();
                num.push(m[tokens[i]](left,right));
            }
            else
            {
                num.push(stoi(tokens[i]));
            }
        }
        return num.top();
    }
};

我们利用包装器的办法,使得函数的调用变得更加清晰明了;

包装器包装类成员函数

当包装器包装的函数指针是成员函数时会有一些需要注意的地方:

//													包装器包装类成员函数
struct A {
	void a()
	{
		cout << "我是成员函数" << endl;
	}
};
int main()
{
	A b;
	function<void(A*)> f = &A::a;//由于a是成员函数,这里编译器有特殊处理需要带上&在类前面
	f(&b);

	function<void(A)> f1 = &A::a;//这样使用也可以,这又是一种特殊处理,可以直接转递类,和传递this指针是同样的效果
	f1(b);
}

1.包装器在包装A类中的成员函数a时会取地址+类名作为类域符:

 

这是编译器的一个特殊处理;

2.包装器包装成员变量时,参数需要将成员函数隐藏的this指针参数带上:

我们这里给f的参数this指针不能是匿名对象的地址,因为匿名对象是右值,无法取地址

也可以直接传递类,和传递this指针是同样的效果:

绑定函数bine

使用方法:

bine(函数名,palcholders::_1,palcholders::_2,......);

_1,_2代表着函数的第几个参数;_1,_2是封装在palcholders中的类型

绑定函数可以用来调整函数参数的顺序,和写死函数的某个参数;

1.调整函数参数顺序

//													绑定函数bind
void print(char a,char b)
{
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;
}
int main()
{
	char a = 'a', b = 'b';
	//我们使用bind交换了函数print的参数位置
	function<void(char, char)> f = bind(print, placeholders::_2, placeholders::_1);
	f(a, b);
}

现象: 

2.写死函数参数,修改函数参数个数:

 

 由此可见写死的顺序为,你要写死哪个参数就将第几个位置上写入你的值,其他剩余的参数按照1234...的顺序排序;

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

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

相关文章

【简单】 猿人学web第一届 第19题 乌拉乌拉

数据接口分析 数据接口为 https://match.yuanrenxue.cn/api/match/19 请求参数只需要携带 页码 cookie 只需要携带 sessionid 请求参数 和 cookie 都没有加密字段&#xff0c;直接用 python 请求 请求失败了 查看协议是 h2 的&#xff0c;再试试 httpx 请求 还是一样的结果…

Creating OpenAI Gym Environment from Map Data

题意&#xff1a;从地图数据创建 OpenAI Gym 环境 问题背景&#xff1a; I am just starting out with reinforcement learning and trying to create a custom environment with OpenAI gym. However, I am stumped with trying to create an environment (with roads and in…

【论文速读】| SEAS:大语言模型的自进化对抗性安全优化

本次分享论文&#xff1a;SEAS: Self-Evolving Adversarial Safety Optimization for Large Language Models 基本信息 原文作者: Muxi Diao, Rumei Li, Shiyang Liu, Guogang Liao, Jingang Wang, Xunliang Cai, Weiran Xu 作者单位: 北京邮电大学, 美团 关键词: 大语言模…

Python 全栈系列267 telegraf、influxdb和grafana

说明 没想到如此丝滑 本来是因为想稍微了解一下influxdb&#xff0c;然后发现和telegraf配套能干监控&#xff0c;然后正好之前又起了grafana,然后瞬间就通了。 内容 1 telegraf Telegraf 是一个开源的服务器代理&#xff0c;用于收集、处理和发送数据。它是 InfluxData 公司…

cowrie部署中遇到的坑

首先&#xff0c;这个cowrie已经比较老了&#xff0c;没有好看的展示界面&#xff0c;当前跟mhn结合使用的只能是2.2版本&#xff0c;不是迫切需要的话不建议布。 mhn也比较老了&#xff0c;界面太过简洁&#xff0c;推荐hfish&#xff0c;部署方便&#xff0c;好看。 坑1&…

STM32重定义printf,实现串口打印

在“usart.c”文件中加入以下代码 #ifdef __GNUC__#define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endifPUTCHAR_PROTOTYPE{HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);return ch; }…

鸿蒙图表MPChart自定义样式(五)左y轴显示数值,右y轴显示百分比

左y轴数值不变&#xff0c;右y轴改成百分比&#xff0c;需要通过自定义RightAxisFormatter实现IAxisValueFormatter接口&#xff0c;将右y轴的数值改成百分比文本&#xff0c;RightAxisFormatter类如下&#xff1a; class RightAxisFormatter implements IAxisValueFormatter …

navigator.mediaDevices.getUserMedia检查用户的摄像头是否可用,虚拟摄像头问题

在Web开发中&#xff0c;检查用户的摄像头是否可用是一个常见的需求&#xff0c;尤其是在需要视频聊天或录制视频的应用程序中。navigator.mediaDevices.getUserMedia() API 提供了这一功能&#xff0c;它允许你请求访问用户的媒体设备&#xff0c;如摄像头和麦克风。虽然这个A…

【PPT学习笔记】使用PPT制作动画/手书/视频等作品的适配性和可能性?

【PPT学习笔记】使用PPT制作动画/手书等作品的可能性&#xff1f; 背景前摇&#xff1a;&#xff08;省流可不看&#xff09; 最近找到另外一份新的实习工作&#xff0c;有很多需要用到PPT动画的地方。 然而&#xff0c;我们之前制作的理工科PPT全是摒弃了形式主义的艰苦朴素…

AUSD稳定币正式在Sui上线

继Agora在五月份的早期公告之后&#xff0c;AUSD稳定币现已正式在Sui上线。AUSD为Sui日益增加的原生资产列表增添了关键的一环。 Agora此前在以太坊和Avalanche上取得成功&#xff0c;迄今为止已铸造了近6000万美元的稳定币。如今&#xff0c;AUSD集成到Sui网络中&#xff0c;…

C语言基础——⑩③数据结构——②栈和队列

一、栈(Stack) 1、基本概念 栈是一种逻辑结构&#xff0c;是特殊的线性表。特殊在&#xff1a; 只能在固定的一端操作 只要满足上述条件&#xff0c;那么这种特殊的线性表就会呈现一种“后进先出”的逻辑&#xff0c;这种逻辑就被称为栈。栈 在生活中到处可见&#xff0c;比…

使用Python进行数据可视化:让你的数据“活”起来

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 安装与导入 要使用Matplotlib&#xff0c;首先需要安装。可以使用pip进行安装&#xff1a; pip install matplotlib安装完成后&#xff0c;可以在Python代码中导入Matplotlib库&#xff1a; import matplotlib.py…

2024跨境旺季营销:哪个平台是流量之王?

跨境电商的旺季即将来临&#xff0c;对于卖家们来说&#xff0c;如何进行有效的营销推广至关重要。在多渠道广告覆盖的策略下&#xff0c;选择合适的平台成为关键。那么&#xff0c;哪些平台是跨境旺季营销的首选呢&#xff1f; 一、社交媒体平台 1、Instagram 以图片和短视频…

华为达芬奇人像引擎2.0,人像体验有哪些升级

对于年轻人而言&#xff0c;拍照已成为生活中不可或缺的一部分&#xff0c;不仅是为了记录世界、更重要的是成为生活的主角&#xff0c;大胆表达自己。然而很多喜欢使用手机记录生活的人&#xff0c;既希望能够实现媲美单反的影像实力&#xff0c;同时还想呈现出真实、更具自然…

内存管理篇-21 虚拟内存管理:线性映射区

1.线性映射区的定义 这部分讲线性映射区的内容。一般老的嵌入式平台&#xff0c;它内存很小只有几百兆&#xff0c;都会直接把整个物理内存映射到线性映射区了&#xff0c;只有当物理内存大于1GB以上&#xff0c;线性映射区无法cover的时候就把剩下的放到高端内存。所以这个区域…

CST软件如何仿真Total Scan方向图的

本期将介绍如何在CST软件中得到Total Scan方向图。 CASE1 首先以两个dipole天线为例&#xff0c;如下图所示&#xff1a; 我们完成这个两单元阵的仿真&#xff0c;可以在远场结果看到各个频点的结果如下图所示&#xff1a; 我们可以在combine按钮下任意合成不同幅度相位下的结…

SpringBoot中实现全局异常处理,统一返回错误信息给前端

背景引入&#xff1a;最近实现了一个限流切面类&#xff0c;但是在限流方法中throw异常&#xff0c;会直接打印到控制台&#xff0c;报错500&#xff0c;对前端很不友好。因为是注解&#xff0c;又没办法捕获再处理。那么怎么才能将错误码返回给前端呢&#xff1f;原来是全局异…

linux下的Socket网络编程教程

套接字概念 Socket本身有“插座”的意思&#xff0c;在Linux环境下&#xff0c;用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。与管道类似的&#xff0c;Linux系统将其封装成文件的目的是为了统一接口&#xff0c;使得读写套接字和读写文件的操作…

什么是边缘计算?边缘计算网关有什么作用?

边缘计算是一种分布式计算范式&#xff0c;它将计算、存储和网络服务靠近数据源或用户的位置&#xff0c;以减少延迟、提高响应速度&#xff0c;并减轻中心数据中心的负担。边缘计算网关在这一过程中扮演着至关重要的角色。边缘计算网关的主要作用包括&#xff1a;1. 数据的收集…

问题合集更更更之cssnano配置导致打包重新计算z-index

前言 &#x1f44f;问题合集更更更之cssnano配置导致打包重新计算z-index~ &#x1f947;记得点赞关注收藏&#xff01; 1.问题描述 代码中写了样式代码&#xff0c;z-index层级关系 z-index&#xff1a;2029;进行打包之后&#xff0c;发布到环境中&#xff0c;发现层级变…