【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器

这篇具有很好参考价值的文章主要介绍了【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


我们紧接着上一节的讲解来进行

一,可变参数模板

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

下面来看一下可变参数的模板函数:


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

Args是一个模板参数包,args是一个函数形参参数包
声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。

我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

注:语法不支持使用args[i]这样方式获取可变参数

所以我们用下面的方式来解析参数包:
靠编译器时递归推演来解析参数包


void _Show() {
	cout << endl;
}

template<class T,class ...Args>
void _Show(T& val, Args ...args) {
	cout << val << " ";
	_Show(args...);
}

template<class ...Args>
void Show(Args... args) {
	
	_Show(args...);
}

int main() {

	Show(1, 2, 3,4);
	return 0;
}

在_Show(T& val, Args …args) 这里会解析val为第一个参数,args是剩下的参数构成新的参数包,再继续递归解析。直到最后一个参数时,调用最上面的函数来结束。


这里用逗号表达式也可以来展开参数包
,逗号表达式就是用逗号隔开的多个表达式。从左向右依次执行。整个表达式的结果是最后一个表达式的结果

template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。因为逗号表达式会按顺序执行逗号前面的表达式。

在(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了上一节说的C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, 将{(printarg(args), 0)…}展开成((printarg(arg1),0),(printarg(arg2),0),(printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

二,lambda表达式

然后我们来讲解lambda表达式。
我们在之前讲过仿函数,如果想要对一个自定义的元素进行排序,可以使用std的sort方法
但是需要传入不同的类(仿函数)来控制默认的排序顺序。对于编程来说还是比较麻烦的

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;
	}
};
int main()
{
	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());
}

所以C++11引入了lambda表达式
格式是:

[](int x)->int {  //...  };

如果用lambda表达式来改造上述场景,就是这样:

int main()
{
	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; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
		return g1._evaluate < g2._evaluate; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
		return g1._evaluate > g2._evaluate; });
}

这样我们就可以做到按照我们想要的方式去排序,而且不用写很多的类实现仿函数了


现在我们依次来介绍一下lambda表达式:
【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器,C++,c++,c++11,lambda表达式,包装器
lambda表达式其实相当于于不用自己写的仿函数,其底层也是仿函数

下面来看看用法:
用法1:
遇到一个lambda表达式,底层会生成一个类,调用时像仿函数一样


auto f1 = [](int x) { cout << x << endl; return 0; };
f1(2);

用法2:

int x = 1, y = 2;
cout << x << " " << y << endl;

auto f2 = [x, y]() mutable
{
	int tmp = x;
	x = y;
	y = tmp;
};

f2();
cout << x << " " << y << endl;

注意:这里在lambda表达式后面要加上mutable,这是因为默认生成的类中成员变量不可修改
这里是一个传值捕捉,在[]捕捉列表中捕捉这个域内的x,y,其实是拷贝一下,然后去初始化自己的成员x,y。

但是输出后我们看到这里的x ,y没有发生交换:
【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器,C++,c++,c++11,lambda表达式,包装器

这是因为捕捉列表捕捉这个域内的x,y是拷贝去初始化自己的成员x,y 但是没有真正的改变这个域中的x,y

所以要进行引用捕捉,也就是拿外面变量(这两个变量本身)去初始化lambda表达式自己的成员变量。

auto f3 = [&x, &y]()//可以不加mutable
{
	int tmp = x;
	x = y;
	y = tmp;

};
f3();
cout << x << " " << y << endl;

现在就可以交换了。
【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器,C++,c++,c++11,lambda表达式,包装器

这里再补充一下:

也可以 [=]进行捕捉,意思是传值捕捉这个域中的全部
[&] 表示引用捕捉这个域的全部
[=,&a,&b] 表示还可以搭配使用,传值捕捉全部,但是其中的a,b是引用捕捉

三,包装器

现在来说包装器

function包装器 也叫作适配器。包装器实际上是一个类模板,包含了可调用对象(函数指针,仿函数,lambda表达式)

为什么需要包装器呢?

这是因为对于函数指针,比较反人类设计就劝退一大部分的C/C++玩家了,然后仿函数用的时候比较麻烦,要写一个重载()的类,而lambda表达式又取不到类型

来看看包装器的用法:
使用时要加上头文件: < functional >
格式是:

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

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

下面分别是三种可调用对象,用包装器来使用:

//函数
int f(int a, int b) {
	return a + b;
}

//仿函数
struct func {
	int operator()(int a, int b) {
		return a + b;
	}
};

//lambda表达式
auto lambda = [](int a, int b) { return a + b; };


int main() {
	function<int(int, int)> fun1 = f;//函数指针
	cout << fun1(1, 2) << endl;
	
	function<int(int, int)> fun2 = func();//仿函数
	cout << fun2(1, 2) << endl;

	function<int(int, int)> fun3 = lambda;//lambda表达式
	cout << fun3(1, 2) << endl;

	return 0;
}

还可以对类成员函数进行包装:
这里有一个包含static修饰的静态成员函数,和一个普通成员函数

struct Plus {
	static int plusi(int a, int b) {
		return a + b;
	}

	double plusd(double a, double b) {
		return a + b;
	}
};


对于包装类成员函数需要 :
1.加指定的类域 (Plus:: )
2.加&取地址(静态成员函数可以不加)

int main() {
	function<int(int, int)> fun4 = &Plus::plusi;//对于静态成员
	cout<<fun4(1, 2)<<endl;

对于非静态类成员函数要加this指针:

	function<double(Plus*, double, double)> fun6 = &Plus::plusd;
	Plus ps;
	cout << fun6(&ps, 1.1, 2.2) << endl;

这里还有个特殊处理:也可以传入一个Plus()匿名对象

	
	function<double(Plus, double, double)> fun5 = &Plus::plusd;
	cout << fun5(Plus(), 1.1, 2.3) << endl;//

	return 0;
}

这里还有一个很有意思的用法,大家可以自己看一下,如果有疑问欢迎大家来提问

int main() {
	map<string, function<int(int , int )>> cmdOp =
	{
		{"函数指针",f},
		{"仿函数",func()},
		{"lambda表达式",lambda}

	};

	auto ret = cmdOp["函数指针"](1, 2);
	cout << ret << endl;

	cmdOp["仿函数"](1, 2);
	cmdOp["lambda表达式"](1, 2);


	return 0;
}

四,绑定bind

bind包装器实际上是一个函数模板,只不过用来调整可调用对象参数的顺序和个数

我们直接来看用法:

int Sub(int a, int b) {
	return a - b;
}


正常使用包装器时:

int main() {
	//正常使用
	function<int(int, int)> f1 = Sub;
	cout << f1(10, 5) << endl;

如果我们想要调整参数顺序,那么就可以:

	function<int(int, int)> f2 = bind(Sub, placeholders::_2,placeholders::_1);
	cout << f2(10, 5) << endl;

其中的 placeholders::_2placeholders::_1 分别代表下面 f2 函数中传入的第一个参数和第二个参数
【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器,C++,c++,c++11,lambda表达式,包装器
也可以调整参数个数:只要传一个参数即可

	function<int(int)> f3 = bind(Sub, 20, placeholders::_1);
	cout << f3(5) << endl;//

	return 0;
}

【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器,C++,c++,c++11,lambda表达式,包装器

五,其他新特性

我们在前面C++入门时介绍过auto,可以进行自动类型的推导
但是如果我们拿到一个auto的类型,想要知道其具体类型呢?
比如想拿auto的ret去定义一个vector

int main() {
	auto i = 1;
	int j = 2;
	auto ret = i + j;
	vector<>

	return 0;
}

我们是不可以用auto去定义的
我们可以打印来看一下auto的类型:

cout << typeid(ret).name() << endl;

但是总不可能每次要知道的时候就打印一下吧,所以这里有一个关键字decltype可以推导类型并且可以使用

int main() {
	auto i = 1;
	int j = 2;
	auto ret = i + j;
	cout << typeid(ret).name() << endl;
	vector<decltype(ret)> v;

	return 0;
}

这样我们就可以去正常使用了。

六,总结

我们关于C++11的讲解就先到这里,当然还有很多新特性没有说到,像智能指针还有一些线程相关的,我们在后面再进行介绍。这里只是介绍了一些主要的更新。如果大家感兴趣可以自己去探索。文章来源地址https://www.toymoban.com/news/detail-847344.html

到了这里,关于【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】C++11语法 ~ lambda 表达式

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 目前状态:大三非科班啃C++中 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉

    2024年01月20日
    浏览(62)
  • 不可变集合、Lambda表达式、Stream流

    不能被修改的集合 应用场景 如果某个数据不能被修改,把它防御性的拷贝到不可变集合中是个很好的实践。 当集合对象被不可信的库调用时,不可变形式是安全的。 创建不可变集合 在List、Set、Map接口中,都存在静态的of方法,可以获取一个不可变的集合。 方法名称 说明

    2024年02月10日
    浏览(49)
  • 【C++】C++11可变参数模板

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》 《数据结构》 《蓝桥杯试题》 《LeetCode刷题笔记》 《实训项目》 《C++》 《Linux》 《算法》 🌝 每一个不曾起舞的日子,都是对生命的辜负 目录 前言 可变参数模板的定义方式 可变参数模板的使用  编译时递归展开参数包

    2024年04月10日
    浏览(41)
  • 【C++干货铺】C++11新特性——lambda表达式 | 包装器

    ========================================================================= 个人主页点击直达:小白不是程序媛 C++系列专栏:C++干货铺 代码仓库:Gitee ========================================================================= 目录 C++98中的排序 lambda表达式 lambda表达式语法 表达式中的各部分说明 lambda表达式的使

    2024年01月21日
    浏览(44)
  • C++ 11新特性之可变参数模板

    概述         随着C++ 11标准的发布,C++语言获得了许多强大的新特性,其中一项显著提升灵活性和实用性的创新便是可变参数模板。这一特性极大地扩展了模板在处理不定数量类型或值参数时的能力,为开发者提供了更为强大且灵活的泛型编程工具。 工作机制       

    2024年02月22日
    浏览(41)
  • 【C++】C++11语法 ~ 可变参数模板

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 目前状态:大三非科班啃C++中 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉

    2024年02月19日
    浏览(38)
  • 【C++杂货铺】C++11新特性——可变参数模板

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

    2024年02月03日
    浏览(42)
  • c++(8.29)auto关键字,lambda表达式,数据类型转换,标准模板库,list,文件操作+Xmind

    封装一个学生的类,定义一个学生这样类的vector容器, 里面存放学生对象(至少3个) 再把该容器中的对象,保存到文件中。 再把这些学生从文件中读取出来,放入另一个容器中并且遍历输出该容器里的学生。  1.模板类 2.异常(异常情况为取钱时取的钱小于0或者大于余额)

    2024年02月11日
    浏览(45)
  • 进阶JAVA篇- Lambda 表达式与 Lambda 表达式的省略规则

    目录         1.0 什么是 Lambda 表达式?         1.1 既然跟匿名内部类相关,先来回顾匿名内部类。          1.2 Lambda 表达式与匿名内部类之间的关系。         1.3 函数式接口         1.4 在具体代码中来操作一下         2.0 Lambda 表达式省略规则          Lambda 表达

    2024年02月08日
    浏览(55)
  • C++11 lambda表达式

    lambda表达式是C++11或者更新版本的一个 语法糖 ,本身不是C++开发的。但是因其便利,很值得我们学习和使用。lambda有很多叫法,有lambda表达式、lambda函数、匿名函数,本文中为了方便表述统一用 lambda表达式 进行叙述。 在C++中,为了实现泛型编程,在一个类中,我们难免遇到

    2024年02月08日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包