C++11新特性【下】{lambda表达式、可变模板参数、包装器}

一、lambda表达式

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

1.1 lambda表达式语法:

lambda表达式书写格式:[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实现两数相加auto add = [](int a, int b) {return a + b; };cout << add(1, 2) << endl;//lambda实现swapauto swap = [](int& a, int& b){int temp = a;a = b;b = temp;};
}
捕获列表说明:

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

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

捕捉列表还可以混合捕捉:

	int a = 1;int b = 2;int c = 3;int d = 4;//除了a b传值捕捉外,其他的变量都是引用捕捉auto func = [&, a, b]() mutable{a++;b++;c++;d++;};func();cout << a << " " << b <<" " << c << " " << d << " " << endl;// 1 2 4 5

注意:

  • 父作用域指包含lambda函数的语句块
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空。
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同

ps:lambda表达式的类型不是直接由语言定义的具名类型,而是编译器为每一个lambda表达式生成的唯一的、匿名的非联合类类型(non-union class type)。这个类型没有名字,因此你不能用它来直接声明变量或作为函数的参数类型。但是,你可以通过auto关键字或者模板来间接地引用这个类型。即使两个表达式的内容完全相同,其类型都是不一样的

1.2 函数对象与lambda表达式

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的 类对象。

class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};
int main()
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);return 0;
}

从使用方式上来看,函数对象与lambda表达式完全一样。 函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可 以直接将该变量捕获到。

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如 果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

二、新的类功能

默认成员函数

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。 C++11 新增了两个:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
强制生成默认函数的关键字default:

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原 因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以 使用default关键字显示指定移动构造生成。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name),_age(p._age){}Person(Person&& p) = default;private:bit::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}
禁止生成默认函数的关键字delete:

如果能想要限制某些默认函数的生成,在C++98中,是J将该函数设置成private,并且只声明补不定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即 可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;
private:bit::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

三、可变模版参数

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比 C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改 进。由于可变模版参数比较抽象难懂,本篇博客主要讲解其基本特性和使用方法

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

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

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

递归函数方式展开参数包
template<class T>
void _Show_list(const T& val)
{cout << val<<endl;
}template<class T,class ...Args>
void _Show_list(T val,Args... args)
{cout << val << " ";_Show_list(args...);
}template<class ...Args>
void Show_list(Args... args)
{_Show_list(args...);
}int main()
{Show_list(1);Show_list(1,"abc");Show_list(1,"abc",2.2);return 0;
}

底层解释:

逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在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 print(const T& val)
{cout << val << " ";
}template<class ...Args>
void Show_list(Args... args)
{int arr[] = { (print(args),0)... };cout << endl;
}int main()
{Show_list(1,"abc",2.2);return 0;
}
STL容器中的empalce相关接口函数:
template <class... Args>
void emplace_back (Args&&... args);

可见emplace系列使用了可变模版参数和万能引用,相比于emplace系列,push_back的参数存在左值引用和右值引用两个版本,那emplace与push_back有什么区别吗?

如果插入的数据是一个对象的话,不论是传左值还是传右值,两者的效率是一摸一样的:

emplace系列函数可以直接传构造对象的参数包,该参数包会一直往下传,到最底层时直接构造,相比于push_back传右值来说省了一次移动构造的消耗

总结:

1.对于深拷贝的类,push_back传右值和emplace传构造对象的参数包来说,效率差不多,因为对于深拷贝来说,多一次数据交换的影响不大

2.对于浅拷贝的类来说,push_back传右值要经历构造和拷贝构造,emplace传构造对象的参数包仅一次构造,此时效率提升比较大

3.推荐用emplace系列函数代替push和insert系列函数,此外emplace系列函数能传参数包就传参数包

四、包装器

function包装器

对于可调用对象一般有三种:函数指针、仿函数、lambda表达式,function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。包装器一般就是包装这三种类型的。

std::function在头文件<functional>// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
包装器的使用
// 使用方法如下:
#include <functional>
int f(int a, int b)
{return a + b;
}
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};
class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{// 函数名(函数指针)std::function<int(int, int)> func1 = f;cout << func1(1, 2) << endl;// 函数对象std::function<int(int, int)> func2 = Functor();cout << func2(1, 2) << endl;// lamber表达式std::function<int(int, int)> func3 = [](const int a, const int b){return a + b; };cout << func3(1, 2) << endl;// 类的成员函数std::function<int(int, int)> func4 = &Plus::plusi;cout << func4(1, 2) << endl;std::function<double(Plus, double, double)> func5 = &Plus::plusd;cout << func5(Plus(), 1.1, 2.2) << endl;std::function<double(Plus*, double, double)> func6 = &Plus::plusd;Plus plus;cout << func6(&plus, 1.1, 2.2) << endl;return 0;
}
注意:

包装器也可以包装类成员函数,不过要添加函数所属类域,在c++中一般还要在类名前加一个&,对于静态成员函数,按照函数声明传参即可,对于非静态成员函数,要注意函数存在一个隐藏的this指针,我们可以传一个对象,也可以传一个对象的地址,底层会根据这个对象或者地址从而找到这个函数

包装器的意义
#include<iostream>
using namespace std;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 << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;cout << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;cout << endl;return 0;
}

通过上面的程序验证,我们会发现useF函数模板实例化了三份,如此丰富的可调用类型,可能会导致模板的效率低下。而包装器就可以很好的解决上述问题。

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

这样useF就仅实例化出一份,比较节省空间

bind绑定

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

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对 象来“适应”原对象的参数列表。 调用bind的一般形式:auto newCallable = bind(callable,arg_list); 其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的 callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中 的参数。

通过bind改变函数的参数顺序

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示 newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对 象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。这些占位符存在placehoders的命名空间中

通过bind改变函数的传参个数
void func(string s, int a, int b)
{cout << s << " " << a << " " << b << endl;
}int main()
{auto f1 = bind(func,"abc" ,placeholders::_1, placeholders::_2);f1(1, 2);auto f2 = bind(func, placeholders::_1,666, placeholders::_2);f2("def", 1);
}结果:
//abc 1 2
//def 666 1

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

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

相关文章

1-讯飞星火大模型API调用示例解析

1官网链接 比赛官网&#xff1a;2024 iFLYTEK A.I.开发者大赛-讯飞开放平台 (xfyun.cn)&#xff1b;控制台官网&#xff1a;控制台-讯飞开放平台 (xfyun.cn)&#xff1b; 2星火模型python调用示例 示例链接&#xff1a;https://xfyun-doc.xfyun.cn/lc-sp-PythonDemo-17163704…

了解MySQL【事务】的功能:确保数据完整性的关键技术

在数据库管理中&#xff0c;事务是确保数据完整性和一致性的核心机制。特别是对MySQL这样广泛应用的开源数据库系统&#xff0c;掌握事务的使用至关重要。在这篇文章中&#xff0c;我们将全面探讨MySQL事务的工作原理、ACID属性、隔离级别以及最佳实践&#xff0c;从而帮助开发…

宝塔Linux面板配置环境 + 创建站点

一、安装 &#xff08;1&#xff09;进入宝塔官网 https://www.bt.cn/new/index.html &#xff08;2&#xff09;点击“ 立即免费安装 ”&#xff0c;选择 Centos安装脚本 &#xff08;3&#xff09;进入 ssh 输入以下命令安装宝塔 yum install -y wget && wget -O …

实验三 SQL Server SSMS工具添加数据

1、打开ecommerce数据库&#xff0c;向表中分别录入以下数据信息 &#xff08;1&#xff09;商品类别表category catno catname describe 101 手机 各种品牌、型号手机 201 激光打印机 各种激光打印机 202 喷墨打印机 各种喷墨打印机 301 平板电脑 各种平板电脑…

思看科技募资额骤降:对赌压力下巨额分红,还购买 7项商业房产

《港湾商业观察》施子夫 6月11日&#xff0c;证监会网站披露思看科技&#xff08;杭州&#xff09;股份有限公司&#xff08;以下简称&#xff0c;思看科技&#xff09;的首轮审核问询函回复意见并更新2023年财务数据&#xff0c;继续推进上市进程。 公开信息显示&#xff0c…

深度之眼(二十九)——神经网络基础知识(四)-循环神经网络

文章目录 一、 学习目标二、序列数据三、语言模型四、循环神经网络4.1 RNN的反向传播 五、门控循环单元-GNU5.1 候选隐藏状态 六、长短期记忆网络-LSTM七、回顾 一、 学习目标 二、序列数据 序列数据是常见的数据类型&#xff0c;前后数据通常具有关联性 三、语言模型 综合…

【技术杂谈】如何访问Github | 解决无法连接Github的问题

访问网页的过程 什么是域名&#xff1f;什么是IP地址&#xff1f;- 域名是网站的名称。 - IP地址是服务器在互联网上的逻辑地址。域名往往是固定的&#xff0c;但是IP地址很有可能是会改变的。计算机通过Host文件检查本地缓存是否有域名对应IP地址 Host文件路径 C:\Windows\Sy…

C#反射基本应用

1、反射 反射是.NET Framework的一个特性&#xff0c;它允许在运行时获取类型的信息以及动态创建对象&#xff0c;调用方法&#xff0c;以及访问字段和属性。 2、代码 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Sy…

探究Executors创建的线程池(如newFixedThreadPool)其核心线程数等参数的可调整性

java中提供Executors类来创建一些固定模板参数的线程池&#xff0c;如下图&#xff08;newWorkStealingPool除外&#xff0c;这个是创建ForkJoinPool的&#xff0c;这里忽略&#xff09;&#xff1a; 拿newFixedThreadPool方法创建线程池为例&#xff0c;newFixedThreadPool是…

白杨SEO:打粉是啥?打粉引流怎么做?打粉引流犯法吗?小红书代发效果好吗?

文章大纲&#xff1a; 1、打粉是什么意思&#xff1f; 2、打粉有哪些方法&#xff1f; 3、打粉一般怎么变现&#xff1f; 4、打粉引流是违法犯罪吗&#xff1f; 5、小红书代发是啥&#xff1f; 6、小红书批量代发效果好吗&#xff1f; 打粉是什么意思&#xff1f; 打粉这…

C语言-初探指针

初探指针 指针概念指针和指针类型指针类型意义 野指针如何避免 指针运算指针-整数指针-指针指针的关系运算 指针和数组二级指针指针数组 指针概念 指针是内存中一个最小单元(1个字节)的编号&#xff0c;也就是地址平时口语中说的指针&#xff0c;通常指的是指针变量&#xff0…

(免费领源码)java#springboot#mysql校园医院预约挂号系统32236-计算机毕业设计项目选题推荐

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对校园医院管理等问题&#xff0c;对校园医院…

初识单片机

单片机 英文 Micro Controller Unit&#xff08;MCU&#xff09; 1.内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能 2.单片机的任务是信息采集&#xff08;依靠传感器&#xff09;、处理&#xff08;依靠CPU&#xff09;和硬件设备&#…

【嵌入式单片机】之RS-232、RS-485、RS-422比较

1. RS422是什么 RS422,正式名称为TIA/EIA-422,是一种串行通信标准,专为实现长距离、高可靠性的数据传输而设计。它采用差分信号传输技术,通过两对双绞线实现全双工通信,即发送和接收可以同时进行。RS422在工业自动化和远程监控系统中曾经扮演着重要角色,以其出色的抗干扰…

MySQL—常用的数据类型

数据类型 整型 1.创建一个含有无符号/有符号整型的字段的表 CREATE TABLE L1(id tinyint unsigned #无符号 ) CREATE TABLE L2(id tinyint #默认为有符号 ) 数值型(bit) 2.数值型(bit)的使用 小数 3.数值型(小数)的基本使用 字符串 4.字符串的基本使用 #演示字符串类型…

【学习笔记】网络设备(华为交换机)基础知识1——命令行入门知识

一、前期准备 提示&#xff1a;下面所有学习内容都是基于以下条件完成的 条件1.已经可以正常访问交换机的命令行接口 连接到命令行接口的方法 &#xff1a; ① &#xff1a;通过Console口本地访问 ② &#xff1a; 通过Telnet访问 ③ &#xff1a; 通过SSH访问 ④ &#xff1…

小阿轩yx-LVS负载均衡群集

小阿轩yx-LVS负载均衡群集 构建群集服务器—通过整合多台服务器使用 LVS 达到服务器的高可用和负载均衡并以同一个 IP 地址对外提供相同的服务 LVS 群集应用基础 群集称呼来自英文单词“Cluster”在服务器领域则表示大量服务器的集合体&#xff0c;区分单个服务器 Cluster …

创新驱动,智享未来:电动车仪表盘之蓝牙芯方案

电动车行业不断发展的浪潮中&#xff0c;我们自豪地推出引领时代的电动车仪表盘蓝牙芯方案&#xff0c;为您的骑行之旅带来前所未有的智能与便捷。 精准掌控&#xff0c;一目了然 我们的蓝牙芯方案搭载了高性能BLE 5.0蓝牙芯片-HS6621CG-C 内核ARM Cortex-M4F, max 64MHZ,SRAM…

Operations Research课程之带约束的非线性规划(凸分析|Lagrange松弛|Lagrange对偶|KKT条件)

目录 1.凸分析 1.1 为什么需要凸分析 1.2 凸分析相关概念 1.3 凸规划定义 1.4 单变量NLP凸分析 1.5 多变量NLP凸分析 2.拉格朗日松弛 2.1 拉格朗日函数 2.2 拉格朗日对偶 2.2.1 弱对偶性 2.2.2 凸性 2.2.3 强对偶性 2.2.4 与LP对偶关系 3.KKT条件 3.1 KKT介绍…

Redis 管道(Pipeline)是什么?有什么用?

目录 1. redis 客户端-服务端模型的不足之处 2. redis 管道是什么&#xff1f;有什么好处&#xff1f; 3. 管道的使用场景 4. 管道使用的注意事项 1. redis 客户端-服务端模型的不足之处 众所周知&#xff0c;redis 是一个客户端-服务端的模型设计&#xff0c;客户端向服务…