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

2024-09-07 07:20

本文主要是介绍C++11,可变参数模板,lambda表达式,包装器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

可变参数模板

在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...的顺序排序;

这篇关于C++11,可变参数模板,lambda表达式,包装器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

SpringBoot集成EasyPoi实现Excel模板导出成PDF文件

《SpringBoot集成EasyPoi实现Excel模板导出成PDF文件》在日常工作中,我们经常需要将数据导出成Excel表格或PDF文件,本文将介绍如何在SpringBoot项目中集成EasyPo... 目录前言摘要简介源代码解析应用场景案例优缺点分析类代码方法介绍测试用例小结前言在日常工作中,我们经

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的