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

news2024/9/23 9:36:29

文章目录

  • 📖 前言
  • 1. 可变参数模板
    • 1.1 万能模板:
    • 1.2 完美转发:
    • 1.3 可变参数模板的使用:
    • 1.4 emplace_back:
  • 2. lambda表达式
    • 2.1 lambda表达式的定义:
    • 2.2 lambda表达式的用法:
      • 2.2 - 1 捕捉列表的用法:
    • 2.3 lambda表达式的类型名称:
  • 3. 包装器
    • 3.1 bind绑定:

📖 前言

上一篇我们已经将C++11开了个头,详细讲述了C++11中的 { } 列表初始化和右值引用,本章将继续讲解C++11中的一些新的且比较实用的功能。

列表初始化 + 右值引用复习:👉 传送门


1. 可变参数模板

C++11的新特性可变参数模板:

  • 能够让创建可以接受可变参数的函数模板和类模板,相比C++98 / C++03
  • 类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进
  • 然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的

1.1 万能模板:

万能模板的有样式:
在这里插入图片描述

  • 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值
  • 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力
  • 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值

很显然这并不是我们想要的,因为无论传的是左值还是右值,都将退化成左值:

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

//std::forward<T>(t)在传参的过程中保持了t的原生类型属性。

template<typename T>
void PerfectForward(T&& t)
{
	//完美转发,按照原封不动的方式进行转发
	//Fun(t);
	Fun(std::forward<T>(t));
}

int main()
{
	PerfectForward(10);           //右值

	int a;
	PerfectForward(a);            //左值
	PerfectForward(std::move(a)); //右值

	const int b = 8;
	PerfectForward(b);		      //const 左值
	PerfectForward(std::move(b)); //const 右值

	return 0;
}

在这里插入图片描述

如图所示全部都是调用左值引用了,这可能是编译器底层实现的原因,但是这显然不是一个好的现象。


1.2 完美转发:

我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的

完美转发:
在这里插入图片描述
在这里插入图片描述
C++11新提供的forward函数模板,可以解决上述问题,使得传右值不会退化成左值,。


1.3 可变参数模板的使用:

首先在学C语言的时候,我们用的printf函数是一个可变参数函数,printf从语法上说是可以写多个参数,然后该函数自己识别。

可变参数模板:
在这里插入图片描述
解释:

  • 可变模板参数,源自于printf(可变参数函数),…代表可变参数
  • Args是一个模板参数包,args是一个函数形参参数包
  • 声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数

模板的可变类型参数,不仅不知道有多少个,还不知道类型,要推演出来。

template <class ...Args>
void ShowList1(Args... args)
{
	//参数个数
	cout << sizeof...(args) << endl;
}

//不一定非要用Args也可以取别的名字
template <class ...X>
void ShowList2(X... y)
{
	cout << sizeof...(y) << endl;
}

int main()
{
	ShowList1(1, 'x', 1.1);
	ShowList2(1, 2, 3, 4, 5);

	return 0;
}

运行结果是:3 5

参数包展开:

//方法一:

//递归到最后一个,就找最匹配的那一个
template <class T>
void ShowList(const T& val)
{
	cout << val << "->" << typeid(val).name() << "end" << endl;
}

//编译时递归去推
//整个推导的过程是在编译的时候进行的(这是个编译的过程 -- 编译时决议)
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << sizeof...(args) << endl;
	cout << val << "->" << typeid(val).name() << endl;

	//去递归解析
	ShowList(args...);
}

//方法二:

//0个模板参数走这个 -- 作为递归的终结
void ShowList()
{}

int main()
{
	ShowList(1, 'x', 1.1);
	cout << endl;

	//有几个参数都能推
	ShowList(1, 2, 3, 4, 5);

	return 0;
}

在这里插入图片描述

  • 类似于递归的一个过程,但是不是递归,最后都有一个结束条件,参数包中的模板参数是一个一个减少的。
  • 整个推导的过程是在编译的时候进行的(这是个编译的过程 – 编译时决议)

用参数包定义数组:

template <class ...Args>
void ShowList(Args... args)
{
	//*列表初始化 -- 这种方法不通用
	//C++只允许数组里面是同一个类型的
	int arr[] = { args... };
	cout << endl;
}

int main()
{
	//ShowList(1, 'x', 1.1, vector<int>{2, 2});这种就不适用 -》 int arr[] = { args... };
	//cout << endl;

	ShowList(1, 2, 3, 4, 5);
	ShowList(10, 20, 30);

	return 0;
}

在这里插入图片描述

template <class T>
int PrintArg(const T& t)
{
	cout << t << " ";

	return 0;
}

template <class ...Args>
void ShowList(Args... args)
{
	//列表初始化
	//int arr[] = { (PrintArg(args), 0)...};
	int arr[] = { PrintArg(args)... };

	cout << endl;
}

int main()
{
	ShowList(1, 'x', 1.1, string("hello world"));
	cout << endl;

	ShowList(1, 2, 3, 4, 5);
	//ShowList(10, 20, 30);

	return 0;

在这里插入图片描述


1.4 emplace_back:

在这里插入图片描述
C++11STL中新增了一个尾插的方法:

  • emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
  • 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
  • 可以不传pair,可以直接传参数
  • 拿到参数自己构建对象(保证了底层一定是右值 —— 直接去构造的)

在这里插入图片描述

push_back():

  • 参数如果给的是左值是:构造 + 拷贝构造
  • 参数如果给的是右值是:构造 + 移动构造
  • 函数传值返回也是个右值

emplace_back():

  • 直接构造

这两者其实差距不大

2. lambda表达式

在仿函数那一节我们知道,如果我们用每次为了实现一个sort算法,因为每次比较的数据类型不同,都要重新去写一个类(仿函数),如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便,因此,在C++11语法中出现了Lambda表达式。

2.1 lambda表达式的定义:

定义了一个可以调用的对象 / 匿名函数,一般定义在局部,特点是可以深度绑定了局部的数据。

lambda表达式书写格式:

  • 普通函数有函数名,lambda函数没有函数名,他是一个整体
    在这里插入图片描述
  • lambda表达式,实际上是一个匿名函数,实际上是在定义一个函数(局部的函数)
  • lambda表达式通常是用来定义小函数
  • lambda表达式在局部是很好用的,最特别的就是捕捉列表

auto Add1 = [](int x, int y)->int{return x + y; }; Add叫lambda表达式的对象 – lambda定义的是一个对象。

lambda表达式各部分说明:

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters): 参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  • mutable: 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype: 返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的量。

注意:

  • 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。
  • 因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

2.2 lambda表达式的用法:

int main()
{
	int a = 0, b = 200;
	
	//一般是局部匿名函数  也可以写到全局(一般都不写返回值)
	//参数列表(无参的时候)和返回值(表达式自动推导)也可以省略掉
	auto Add1 = [](int x, int y)->double {return (x + y) / 3.0; };
	auto Add2 = [](int x, int y)->int {return (x + y) / 3.0; };

	//参数列表可以省略了
	auto Add3 = [a, b] {return (a + b) / 3.0; };

	//调用是和普通的函数一样的
	cout << Add1(a, b) << endl;
	cout << Add2(a, b) << endl;

	//调用没有区别,但是没有实参,因为捕捉了
	cout << Add3() << endl;
}
  • 一般是局部匿名函数 也可以写到全局(一般都不写返回值)
  • 参数列表(无参的时候)和返回值类型(表达式自动推导)也可以省略掉
  • 和普通函数调用没区别,但是没有实参,因为捕捉了

2.2 - 1 捕捉列表的用法:

用lambda表达式来实现交换两个数:

int main()
{
	int a = 0, b = 200;

	//方法一:
	auto Swap1 = [](int& x, int& y)->void {
		int tmp = x;
		x = y;
		y = tmp;
	};

	Swap1(a, b);
	cout << a << " " << b << endl;

	//方法二:
	//mutable 只是让传值捕捉变量const属性去掉了
	//mutable要和()参数列表配在一起
	//可以认为里面的a,b还是外面a,b的拷贝 -- 里面改变外面还是不变
	//所以mutable实际没什么价值
	//这样写还是没有交换 -- 只是编译通过了但是达不到我们想要的效果
	//auto Swap2 = [a, b]()mutable->void {
	//	int tmp = a;
	//	a = b;
	//	b = tmp;
	//};

	//用引用的方式捕捉(按值捕捉不能改变)
	auto Swap2 = [&a, &b]()->void {
		int tmp = a;
		a = b;
		b = tmp;
	};

	Swap2();
	cout << a << " " << b << endl;
}

方法一:

  • 就像普通函数那样传引用传参

方法二:

  • 不能通过传值传参,首先捕捉列表捕捉的数据具有const属性,不能修改
  • 其次mutable可以让传值捕捉变量const属性去掉了,即使去掉了
    • mutable要和()参数列表配在一起
    • 所以mutable实际没什么价值
  • 可以认为里面的a,b还是外面a,b的拷贝 ,里面改变外面还是不变

所以只能按照引用的方式捕捉

各种混合捕捉:

int main()
{
	int c = 2, d = 3, e = 4, f = 5, g = 6, ret;

	//传值的方式捕捉全部对象
	auto Func1 = [=] {
		return c + d * e / f + g;
	};

	cout << Func1() << endl;

	//传引用捕捉全部对象
	auto Func2 = [&] {
		ret = c + d * e / f + g;
	};

	Func2();
	cout << ret << endl;

	//混着捕捉
	auto Func3 = [c, d, &ret] {
		ret = c + d;
	};

	Func3();
	cout << ret << endl;

	//ret传引用捕捉 其他全部传值捕捉
	auto Func4 = [=, &ret] {
		ret = c + d * e / f + g;

		//传值捕捉默认是加了const的
		//c = 1;
	};

	Func4();
	cout << ret << endl;

	return 0;
}

2.3 lambda表达式的类型名称:

每个lambda表达式类型的名字都是不一样的,先来看一段程序:

int main()
{
	auto Add = [](int x, int y)->int { return x + y; };

	cout << typeid(Add).name() << endl;
	return 0;
}

在这里插入图片描述
仿函数的名称后面叫uuid,一组随机字符串,通过某个算法得到的,使得每个lambda表达式的名字都不同。

lambda表达式的底层和仿函数的底层其实是一样的。

可以将lambda表达式赋值给相同类型的函数指针
在这里插入图片描述
不建议这样写。

3. 包装器

function包装器:
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
在这里插入图片描述
模板参数说明:

  • Ret: 被调用函数的返回类型
  • Args…: 被调用函数的形参

看下面一段程序:

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;

	return f(x);
}

double f(double i)
{
	return i / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	//函数名
	cout << useF(f, 11.11) << endl;
	//函数对象
	cout << useF(Functor(), 11.11) << endl;
	//lamber表达式
	cout << useF([](double d)->double{ return d / 4; }, 11.11) << endl;

	return 0;
}

在这里插入图片描述

  • func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lamber表达式对象?
  • 所以这些都是可调用的类型!

如此丰富的类型,可能会导致模板的效率低下!

包装器可以很好的解决上面的问题:

在这里插入图片描述

  • function可以认为是一个类模板,包装可调用对象
  • 经过包装器包装之后得到的都是一个统一的类型function
  • 最后count的地址相同,并且count到3了,说明包装器是统一了类型

注意,特殊的包装:
在这里插入图片描述


3.1 bind绑定:

在这里插入图片描述
简介:

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。 一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

bind是个函数模板,调整了调用对象参数,用来调整个数和顺序,生成一个新的可调用对象:

包装器的特点就是统一了类型,但是值得注意的是包装时一定要注意匹配,如下:
在这里插入图片描述
这就在包装时不匹配,所在包装时一定要匹配。

下面的场景我们存一个map,这时就要求function包装器要统一了:
在这里插入图片描述
不过不免会有一些特殊情况:
在这里插入图片描述
func1和func2是可以的,但是func3就难受了呀,不匹配啊…

此时我们就可以通过bind函数来调整一下参数个数:

在这里插入图片描述
在这里插入图片描述
通过bind函数来调整顺序:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
调整顺序,用处不大。

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

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

相关文章

manacher算法详解

例题 求一个字符串的最长回文子串的长度 O(N2)O(N^2)O(N2)的解法很容易想&#xff0c;就是从每个字符位置向左右同时拓展&#xff0c;然后检查当前是不是回文&#xff0c;更新长度&#xff0c;可以简单写一下代码 int solve(string &ss){int ans 0;int n ss.length();s…

从参数数量视角理解深度学习神经网络算法 DNN, CNN, RNN, LSTM 以python为工具

从参数数量视角理解深度学习神经网络算法 DNN, CNN, RNN, LSTM 以python为工具 文章目录1. 神经网络数据预处理1.1 常规预测情景1.2 文本预测场景2.全连接神经网络 DNN3.卷积神经网络CNN4.循环神经网络 RNN5.长短期记忆神经网络 LSTMʚʕ̯•͡˔•̯᷅ʔɞʚʕ̯•͡˔•̯᷅ʔ…

【数据结构】详解空间复杂度

Yan英杰的博客 悟已往之不谏 知来者之可追 目录 空间复杂度 ​案例1:计算BubbleSort的空间复杂度&#xff1f; 案例2:计算斐波那契额数列的前N项的空间复杂度 案例3:计算阶乘递归Fac的空间复杂度&#xff1f; 案例4:F1和F2两函数是否使用的同一块空间 案例5:计算该…

git团队合作 - branch分支的使用、主分支合并、冲突处理方案

情景例子开发部3人&#xff0c;组长man&#xff0c; 组员devA&#xff0c;devB&#xff1b;1&#xff09;组长man负责代码合并、冲突处理、检查代码、合并代码到master主分支&#xff1b;2&#xff09;组员devA负责开发3&#xff09;组员devB负责开发git仓库主次分支安排1&…

windows下qt creator 配置编译环境gcc,g++,gdb,cmake

MSVC&#xff1a;即Microsoft Visual C Compiler&#xff0c;即微软自己的编译器 MinGW&#xff1a;我们都知道GNU在Linux下面鼎鼎大名的gcc/g&#xff0c;MinGW则是指Minimalist GNU for Windows的缩写 这里我们选择MinGW&#xff0c;至于Qt中&#xff0c;这两种模式的区别&…

Python入门自学进阶-Web框架——34、富文本编辑器KindEditor、爬虫初步

KindEditor是一个轻量级的富文本编辑器&#xff0c;应用于浏览器客户端。一、首先是下载&#xff1a;http://kindeditor.net/down.php&#xff0c;如下图下载后是解压缩后&#xff1a;红框选中的都可以删除到&#xff0c;这些主要是针对不同的语言编写的示例&#xff0c;因为我…

一文教会你如何简单使用Fegin进行远程服务调用

文章目录1、fegin的基本介绍2、fegin的基本使用步骤3、项目中的实际运用4、测试前言在分布式微服务中&#xff0c;少不了会进行不同服务之间的相互调用&#xff0c;比如A服务要调用B服务中的接口&#xff0c;如何简单方便的实现呢&#xff1f;fegin可以来帮助。 1、fegin的基本…

如何禁止删除或修改RAR压缩包里的文件?很多人不知道这个功能

你是否有过这样的需求&#xff1f;把文件压缩成RAR格式后&#xff0c;需要对压缩包里的文件进行保护&#xff0c;以防别人或者自己误删文件&#xff0c;或者不小心修改了文件内容。 有些小伙伴可能会给压缩包里的文件都设置上“限制编辑”&#xff0c;这虽然也能防止随意更改内…

Stable Diffusion 个人推荐的各种模型及设置参数、扩展应用等合集(不断更新中)

一、说明 | 表示或者 表示 以上 二、模型 适用风景、房子、车子等漫画类风格 模型的VAE不要用模型附带的&#xff0c;好像就是naifu的官方vae&#xff0c;很老了&#xff0c;用 vae-ft-mse-840000-ema-pruned.ckpt 或者是 kl-f8-anime2.ckpt&#xff1b; 嵌入模型要下载作者…

免费集装箱箱号识别API,人工智能企业CIMCAI集装箱识别检测人工智能平台全球4千企业用户,支持API集成二次开发人工智能企业

免费集装箱箱号识别API&#xff0c;人工智能企业CIMCAI集装箱识别检测人工智能平台全球4千企业用户&#xff0c;支持API集成二次开发。箱信息识别及铅封号识别功能免费&#xff0c;顶尖AI集装箱识别率99.98%&#xff0c;全球No.1集装箱人工智能企业CIMCAI打造。中国上海人工智能…

chrome如何查看和修改除了密码,付款方式,地址意外的自动填充表单内容

这种自动填写的内容似乎无法设置。 软件地址&#xff1a;https://sqlitebrowser.org/dl/ 去这里查看地址 https://chromium.googlesource.com/chromium/src//master/docs/user_data_dir.md 比如我是windows&#xff0c;则地址为&#xff1a;C:\Users\用户名\AppData\Local\Go…

【Java】SpringBoot中实现异步编程

前言 首先我们来看看在Spring中为什么要使用异步编程&#xff0c;它能解决什么问题&#xff1f; 什么是异步&#xff1f; 首先我们先来看看一个同步的用户注册例子&#xff0c;流程如下&#xff1a; 异步的方式如下&#xff1a; 在用户注册后将成功结果返回&#xff0c;…

java:UUID和雪花生成算法

目录 UUID生成不重复命名方法 在实际项目中的运用 UUID算法的缺点 什么是雪花算法&#xff1f; UUID生成不重复命名方法 我们在做项目的时候可能需要用到全局唯一ID的场景&#xff0c;这种时候为了防止ID冲突可以使用36位的UUID UUID可以自动生成唯一的id。是java.util中自…

面朝大海,春暖花开丨2023年Kaadas凯迪仕全国经销商大会成功召开

3月8日&#xff0c;We——2023年Kaadas凯迪仕全国经销商大会将在中国青岛星光岛会议中心隆重举行&#xff0c;盛会汇聚了超过1000名优秀合作伙伴&#xff0c;规模空前。Kaadas凯迪仕品牌创始人&集团总裁苏志勇先生、集团董事长苏祺云先生以及各高层领导均莅临现场。 大会伊…

万字长文:Stable Diffusion 保姆级教程

万字长文&#xff1a;Stable Diffusion 保姆级教程 2022年绝对是人工智能爆发的元年&#xff0c;前有 stability.ai 开源 Stable Diffusion 模型&#xff0c;后有 Open AI 发布 ChatGPT&#xff0c;二者都是里程碑式的节点事件&#xff0c;其重要性不亚于当年苹果发布iPhone&a…

蓝库云|告诉你传统产业该如何进行数字化转型

在后疫情时代下&#xff0c;企业该如何在面临生存危机的情形下&#xff0c;投入「数字化转型」、提升公司竞争力&#xff0c;已成为许多公司的当务之急&#xff0c;但到底什么是数字化转型呢&#xff1f;传统产业又如何着手进行数位转型&#xff1f; 数字化转型是什么&#xf…

Uipath Excel 自动化系列13-ForEachExcelSheet(遍历Sheet)

活动描述 ForEachExcelSheet(遍历Sheet)&#xff1a;遍历Excel中的工作表&#xff0c;可以对 Excel 工作簿中的每个工作表重复一个或多个活动,该活动需与Use Excel File 活动选择的 Excel 文件一起使用。 使用场景&#xff1a;当处理包含多张工作表的 Excel 文件&#xff0c;…

项目管理工具DHTMLX Gantt灯箱元素配置教程:如何验证

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的大部分开发需求&#xff0c;具备完善的甘特图图表库&#xff0c;功能强大&#xff0c;价格便宜&#xff0c;提供丰富而灵活的JavaScript API接口&#xff0c;与各种服务器端技术&am…

【FPGA】Verilog:时序电路设计 | 二进制计数器 | 计数器 | 分频器 | 时序约束

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;计数器与分频器 ​​ 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器&#…

做自媒体真的能赚到钱吗?真的能赚到几十万吗?

自媒体在当今社会已经成为一个热门话题&#xff0c;越来越多的人开始尝试做自媒体&#xff0c;希望能够通过自媒体赚到钱。但是&#xff0c;做自媒体真的能赚到钱吗&#xff1f;能赚到几十万吗&#xff1f;下面我们来一一解答。 首先&#xff0c;做自媒体确实可以赚到钱。随着互…