C++11中的可变参数模板/lambda表达式

目录
  • 1.可变参数模板
    • 递归函数方式展开参数包
    • 逗号表达式展开参数包
  • 2.lambda表达式
    • 先来看看lambda表达式的例子:
    • lambda表达式语法

1.可变参数模板

C++11的新特性可变参数模板能够让我们创建可以接受可变参数的函数模板和类模板,相比C++98和C++03,类模板和函数模板中只能含固定数量的模板参数,可变参数模板无疑是一个巨大的改进。可是可变参数模板比较抽象,因此这里只会写出够我们使用的部分。

下面是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数
包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

递归函数方式展开参数包

//递归终止函数
template <class T>
void showList(const T& t)
{
	cout << y << endl;
}
//展开函数
template<class T,class ...Args>
void showList(T value, Args... args)
{
	cout << value << " ";
	showList(args...);
}
int main()
{
	showList(1);
	showList(1,'A');
	showList(1,'A',std::string("sort"));

	return 0;
}

代码分析:

①showList(1): 如果只有一个参数,那会直接调用第一个showList函数。

②showList(1,'A'): 匹配到第二个showList函数后,先将1打印出来。然后通过递归,看看args里面有多少个参数,如果只有一个,比如这里的'A',那么就会去调用第一个showList函数。

③showList(1,'A',"sort"): 匹配到第二个showList函数后,先将1打印出来。然后通过递归,看看args里面有多少个参数,这里有两个,那么继续调用第二个showList函数,此时的value变成了'A',依次类推。

逗号表达式展开参数包

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

expand函数中的逗号表达式:(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数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

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;
}

STL容器中的empalce相关接口函数:

template <class... Args>
void emplace_back (Args&&... args)

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和
emplace系列接口的优势到底在哪里呢

int main()
{
	std::list< std::pair<int, char> > mylist;
	// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
	// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
	mylist.emplace_back(10, 'a');
	mylist.emplace_back(20, 'b');
	mylist.emplace_back(make_pair(30, 'c'));
	mylist.push_back(make_pair(40, 'd'));
	mylist.push_back({ 50, 'e' });
	for (auto e : mylist)
		cout << e.first << ":" << e.second << endl;
	return 0;
}

2.lambda表达式

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法

int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	// 如果需要降序,需要改变元素的比较规则
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	return 0;
}

如果待排序元素为自定义类型,还需要我们定义排序时的比较规则。随着C++语法的发展,人们开始觉得这种写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

先来看看lambda表达式的例子:

//自定义类型
struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
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; });
	return 0;
}

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数。

lambda表达式语法

ambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement}

lambda表达式各部分说明:

[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。

(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略

mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

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

int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};
	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {return a + 3; };
	// 省略了返回值类型,无返回值类型
	auto fun1 = [&](int c) {b = a + c; };
	fun1(10)
		cout << a << " " << b << endl;
	// 各部分都很完善的lambda函数
	auto fun2 = [=, &b](int c)->int {return b += a + c; };
	cout << fun2(10) << endl;
	// 复制捕捉x
	int x = 10;
	auto add_x = [x](int a) mutable { x *= 2; return a + x; };
	cout << add_x(10) << endl;
	return 0;
}

通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。

捕获列表说明:

捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

[var]:表示值传递方式捕捉变量var

[=]:表示值传递方式捕获所有父作用域中的变量(包括this)

[&var]:表示引用传递捕捉变量var

[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

[this]:表示值传递方式捕捉当前的this指针

注意:
a. 父作用域指包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

d. 在块作用域以外的lambda函数捕捉列表必须为空。

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同

到此这篇关于C++11中的可变参数模板/lambda表达式的文章就介绍到这了,更多相关C++11 lambda表达式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈C++11新引入的lambda表达式

    ISO C++ 11 标准的一大亮点是引入Lambda表达式.基本语法如下: [capture list] (parameter list) ->return type { function body } 简单的讲一下各个部分的作用 1.[capture list]捕获列表,捕获到函数体中,使得函数体可以访问 2.(parameter list)参数列表,用来表示lambda表达式的参数列表 3.->return type函数返回值 {function body}就是函数体 lambda表达式

  • 结合C++11新特性来学习C++中lambda表达式的用法

    在 C++ 11 中,lambda 表达式(通常称为 "lambda")是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法. Lambda 通常用于封装传递给算法或异步方法的少量代码行. 本文定义了 lambda 是什么,将 lambda 与其他编程技术进行比较,描述其优点,并提供一个基本示例. Lambda 表达式的各部分 ISO C++ 标准展示了作为第三个参数传递给 std::sort() 函数的简单 lambda: #include <algorit

  • C++11 lambda表达式在回调函数中的使用方式

    目录 一.lambda表达式在C++异步框架中的应用 二.如何在C-style注册回调函数中使用lambda表达式? 在回调函数中使用lambda表达式的好处,在于可以利用C++的RAII机制来做资源的自动申请释放,避免手动管理出错. 一.lambda表达式在C++异步框架中的应用 1. 一个boost asio的例子 // // async_tcp_echo_server.cpp // ~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-202

  • 深入解析C++11 lambda表达式/包装器/线程库

    目录 零.前言 一.lambda表达式 1.lambda的引入 2.lambda表达式语法 3.捕获列表说明 4.函数对象与lambda表达式 二.包装器 1.function包装器 2.bind 概念: 三.线程库 1.线程的概念及使用 2.线程函数参数 3.原子性操作库(atomic) 4.lock_guard与unique_lock 1.mutex的种类 2.lock_guard 3.unique_lock 5.两个线程交替打印奇数偶数 零.前言 本章是讲解学习C++11语法新特性的第三篇

  • 浅析C++11新特性的Lambda表达式

    lambda简介 熟悉Python的程序员应该对lambda不陌生.简单来说,lambda就是一个匿名的可调用代码块.在C++11新标准中,lambda具有如下格式: [capture list] (parameter list) -> return type { function body } 可以看到,他有四个组成部分: 1.capture list: 捕获列表 2.parameter list: 参数列表 3.return type: 返回类型 4.function body: 执行代码

  • C++11 lambda(匿名函数)表达式详细介绍

    目录 前言 概念及基本用法 捕获变量 lambda表达式类型 声明式的编程风格 总结 前言 Lambda(匿名函数)表达式是C++11最重要的特性之一,lambda来源于函数式编程的概念,也是现代编程语言的一个特点. 优点如下: 声明式编程风格:就地匿名定义目标函数或函数对象,有更好的可读性和可维护性. 简洁:不需要额外写一个命名函数或函数对象,,避免了代码膨胀和功能分散. 更加灵活:在需要的时间和地点实现功能闭包. 概念及基本用法 lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的

  • ​​C++11系列学习之Lambda表达式

    目录 一.为什么要有lambda表达式? 二.使用语法 捕获列表 mutable影响lambda表达式 std::bind和lambda表达式结合 三.std::function 和lambda表达式选择 前言: 终于在C++11中引入了lambda表达式,lambda最早来源于函数式编程,现代语言慢慢都引入了这个语法,C++也不甘落后,在新标准中加入了lambda表达式. 一.为什么要有lambda表达式? 使用方便,就地声明函数或函数对象,尤其是和bind配合食用更佳 简洁,可以匿名创建,语

  • 一文读懂c++11 Lambda表达式

    1.简介 1.1定义 C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等.本质上, Lambda 表达式是一个可调用的代码单元[1]^{[1]}[1].实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数.代理等功能.Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了C

  • C++11中的可变参数模板/lambda表达式

    目录 1.可变参数模板 递归函数方式展开参数包 逗号表达式展开参数包 2.lambda表达式 先来看看lambda表达式的例子: lambda表达式语法 1.可变参数模板 C++11的新特性可变参数模板能够让我们创建可以接受可变参数的函数模板和类模板,相比C++98和C++03,类模板和函数模板中只能含固定数量的模板参数,可变参数模板无疑是一个巨大的改进.可是可变参数模板比较抽象,因此这里只会写出够我们使用的部分. 下面是一个基本可变参数的函数模板 // Args是一个模板参数包,args是一个

  • C#中Lambda表达式的用法

    目录 1.参数 2.多行代码 3.闭包 4.使用foreach语句的闭包 从C#3.0开始,可以使用lambda表达式把实现代码赋予委托.lambda表达式与委托(https://www.jb51.net/article/244051.htm)直接相关.当参数是委托类型时,就可以使用lambda表达式实现委托引用. static void Main() { string mid = ", middle part,"; Func<string, string> anonDel

  • 深入浅出理解Java Lambda表达式之四大核心函数式的用法与范例

    目录 1.四大核心函数式接口 1.1 Consumer<T> : 消费型接口 1.2 Supplier<T> : 供给型接口 1.3 Function<T, R> : 函数型接口 1.4 Predicate<T> : 断言型接口 2.方法引用 2.1 对象 :: 实例方法 2.2 类 :: 静态方法 2.3 类 :: 实例方法 3.构造器引用 4.数组引用 1.四大核心函数式接口 上一篇文章中说到了Lambda表达式中的基本语法,以及我们如何自定义函数式接口

  • C++11中bind绑定器和function函数对象介绍

    目录 一. bind1st和bind2nd 1.C++ STL中的绑定器 2.bind1st和bind2nd的底层原理实现 二. 模板的完全特例化和非完全特例化 三. function函数对象 四. bind和function实现线程池 五. lambda表达式 1.lambda表达式的实现原理 2.lambda表达式的应用实践 一. bind1st和bind2nd 1.C++ STL中的绑定器 bind1st:operator()的第一个形参变量绑定成一个确定的值 bind2nd:operat

  • Java Lambda表达式详解和实例

    简介 Lambda表达式是Java SE 8中一个重要的新特性.lambda表达式允许你通过表达式来代替功能接口. lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块). Lambda表达式还增强了集合库. Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包. 流(stream)就如同迭代器(iterator),但附加了许多额外的功能.

  • Java函数式编程(一):你好,Lambda表达式

    第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化. 我们每天的工作将会变成更简单方便,更富表现力.Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了.这些新特性引入Java后,我们可以写出更简洁,优雅,表达性更强,错误更少的代码.我们可以用更少的代码来实现各种策略和设计模式. 在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程.在使用这种全新的优雅的方式进行设计编码之前,我们先来看下它到底好在哪里. 改变了你的思考方式 命令式风格--

  • Lambda表达式原理及示例

    Lambda表达式   Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性. Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中). 使用 Lambda 表达式可以使代码变的更加简洁紧凑. 1. 需求分析    创建一个新的线程,指定线程要执行的任务 public static void main(String[] args) { // 开启一个新的线程 new Thread(new Runnable() { @Override public voi

  • lambda表达式与传统接口函数实现方式对比详解

    目录 在本号之前写过的一些文章中,笔者使用了lambda表达式语法,一些读者反映说代码看不懂.本以为java 13都已经出了,java 8中最重要特性lambda表达式大家应该都掌握了,实际上还是存在大量的程序员没有使用java8,还有的使用了java8也不会使用lambda表达式.所以,写这篇文章还是有必要的,如果您觉得我的文章对您有帮助,期待您的关注. Lambda表达式是Java 8最流行最常用的功能特性.它将函数式编程概念引入Java,函数式编程的好处在于可以帮助我们节省大量的代码,非常

随机推荐