C++11新特性【下】

一、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/diannao/39083.shtml

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

相关文章

化身李时珍弟子,演绎中医药故事,李良济花神戏,创新传承中医药文化

6月29日&#xff0c;李良济与花神戏联袂举办的儿童剧本&#xff0c;在李良济嵩山店强势开启。 20余名小朋友&#xff0c;一起在这次中医药儿童剧本活动中&#xff0c;化身李时珍弟子&#xff0c;学中医&#xff0c;识草药&#xff0c;传承中医智慧&#xff0c;沉浸式学习传统文…

关于一维,二维正态分布的绘制

绘制一维正态分布代码 % 给定的均值和标准差 mu 0; % 例如&#xff0c;你可以改变这个值 sigma 1; % 例如&#xff0c;你可以改变这个值 % 定义x的范围&#xff08;例如&#xff0c;从mu-3*sigma到mu3*sigma&#xff0c;步长为0.1&#xff09; x mu - 3*sigma:0.1:m…

STM32 中断编程入门

目录 一、中断系统 1、中断的原理 2、中断类型 外部中断 定时器中断 DMA中断 3、中断处理函数 中断标志位清除 中断服务程序退出 二、实际应用 中断控制LED 任务要求 代码示例 中断控制串口通信 任务要求1 代码示例 任务要求2 代码示例 总结 学习目标&…

ROS学习笔记(17):建图与定位(1)

目录 0.前言 1.定位和建图 1.里程计&#xff08;Odometry&#xff09; 2.扫描匹配&#xff08;Scan Matching&#xff09; 3.结尾 0.前言 好久不见各位&#xff0c;前段时间忙着考试&#xff08;6级和一些专业课&#xff09;和摆烂断更了近30天&#xff0c;现在哥们回来更…

计算机毕业设计Python+Spark股票基金推荐与预测系统 股票基金可视化 股票基金推荐系统 股票基金可视化系统 股票基金数据分析 股票基金爬虫大数据

目 录 摘 要 Abstract 第1章 前 言 1.1 项目的背景和意义 1.2 研究现状 1.3 项目的目标和范围 1.4 论文结构简介 第2章 技术与原理 2.1 开发原理 2.2 开发工具 2.3 关键技术 第3章 需求建模 3.1 系统可行性分析 3.2 功能需求分析 3.3 非功能性…

高职人工智能专业实训课之“生成对抗网络(GAN)”

一、前言 生成对抗网络&#xff08;GAN&#xff09;作为人工智能领域的一项重要技术&#xff0c;已经在图像生成、风格迁移、数据增强等多个领域展现出巨大的潜力和应用价值。为了满足高职院校对GAN专业实训课程的需求&#xff0c;唯众人工智能教学实训凭借其前沿的教育技术平…

mst[讲课留档]

最小生成树(Minimum Spanning Tree) (1)概念 我们知道&#xff0c;树是有 n n n个结点&#xff0c; n − 1 n-1 n−1条边的无向无环的连通图。 一个连通图的生成树是一个极小的连通子图&#xff0c;它包含图中全部的 n n n个顶点&#xff0c;但只有构成一棵树的 n − 1 n-1 …

内容营销专家刘鑫炜:越是赚不到钱,越要加大推广力度

这两天&#xff0c;一位跟我们有长期合作关系的小微企业主老苏问我。 “现在钱这么不好赚&#xff0c;品牌推广应该怎么做&#xff1f;” 我说&#xff1a;“这是好机会&#xff0c;加大投放力度&#xff01;” 老苏很是不解&#xff0c;这时候不开源节流&#xff0c;还要加…

使用Git从Github上克隆仓库,修改并提交修改

前言 本次任务主要是进行github提交修改的操作练习实践&#xff0c;本文章是对实践过程以及遇到的问题进行的一个记录。 在此之前&#xff0c;我已经简单使用过github&#xff0c;Git之前已经下好了&#xff0c;所以就省略一些步骤。 步骤记录 注册github账号&#xff0c;gi…

【C++】C++指针在线程中调用与受保护内存空间读取方法

引言 在C的多线程编程中&#xff0c;正确地管理内存和同步访问是确保程序稳定性和安全性的关键。特别是当涉及到指针在线程中的调用时&#xff0c;对受保护内存空间的访问必须谨慎处理&#xff0c;以防止数据竞争、死锁和内存损坏等问题。本文将详细探讨C指针在线程中调用时如何…

易校网校园综合跑腿小程序源码修复运营版

简介&#xff1a; 易校网校园综合跑腿小程序源码修复运营版&#xff0c;带服务端客户端前端文档说明。 源码安装方法&#xff1a; 需要准备小程序服务号 服务器 备案域名 校园网跑腿小程序源码需要准备 1.小程序 2.服务器&#xff08;推荐配置2h4g3m&#xff09; 3.域名…

使用JMeter+Grafana+Influxdb搭建可视化性能测试监控平台

【背景说明】 使用jmeter进行性能测试时&#xff0c;工具自带的查看结果方式往往不够直观和明了&#xff0c;所以我们需要搭建一个可视化监控平台来完成结果监控&#xff0c;这里我们采用三种JMeterGrafanaInfluxdb的方法来完成平台搭建 【实现原理】 通过influxdb数据库存储…

开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(五)

一、前言 使用 FastAPI 可以帮助我们更简单高效地部署 AI 交互业务。FastAPI 提供了快速构建 API 的能力,开发者可以轻松地定义模型需要的输入和输出格式,并编写好相应的业务逻辑。 FastAPI 的异步高性能架构,可以有效支持大量并发的预测请求,为用户提供流畅的交互体验。此外,F…

SpringBoot中整合ONLYOFFICE在线编辑

SpringBoot整合OnlyOffice SpringBoot整合OnlyOffice实现在线编辑1. 搭建私有的OnlyOffice的服务2. SpringBoot进行交互2.1 环境2.2 我们的流程2.3 接口规划2.3.1 获取编辑器配置的接口2.3.2 文件下载地址2.3.3 文件下载地址 3. 总结4. 注意4.1 你的项目的地址一定一定要和only…

三层交换基础

一、什么是三层交换 三层交换是一种在OSI模型第三层&#xff0c;即网络层上工作的网络设备技术&#xff0c;它整合了二层交换机的功能和路由器的部分功能&#xff0c;以实现更高效的网络数据转发和路由选择。三层交换技术的核心在于结合了二层交换技术和三层转发技术&#xff…

【RabbitMQ实战】Springboot 整合RabbitMQ组件,多种编码示例,带你实践 看完这一篇就够了

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、对RabbitMQ管理界面深入了解1、在这个界面里面我们可以做些什么&#xff1f; 二、编码练习&#xff08;1&#xff09;使用direct exchange(直连型交换机)&a…

2024 年的 13 个 AI 趋势

2024 年的 13 个 AI 趋势 人工智能对环境的影响和平人工智能人工智能支持的问题解决和决策针对人工智能公司的诉讼2024 年美国总统大选与人工智能威胁人工智能、网络犯罪和社会工程威胁人工智能治疗孤独与对人工智能的情感依赖人工智能影响者中国争夺人工智能霸主地位人工智能…

【Lua小知识】Vscode中Emmylua插件大量报错的解决方法

起因 Vscode写Lua用的好好的&#xff0c;最近突然出现了大量报错。 看报错是有未定义的全局变量&#xff0c;这里查日志才发现是由于0.7.5版本新增诊断启用配置&#xff0c;所以导致了原先好的代码&#xff0c;现在出现了大量的报错。 解决方案一 最直接的方法当然是在配置中直…

用摄像头实现识别道路中的车道线、行人与车辆检测(级联分类器、HOG+SVM、行人检测)

基于树莓派的智能小车&#xff0c;用摄像头实现识别道路中的车道线识别、行人检测与车辆检测。 本项目旨在开发一套基于摄像头的智能道路环境感知系统&#xff0c;该系统能够实时识别道路中的车道线、行人与车辆&#xff0c;为自动驾驶汽车、智能交通管理以及辅助驾驶系统提供关…

LeetCode热题100刷题3:3. 无重复字符的最长子串、438. 找到字符串中所有字母异位词、560. 和为 K 的子数组

3. 无重复字符的最长子串 滑动窗口、双指针 class Solution { public:int lengthOfLongestSubstring(string s) {//滑动窗口试一下//英文字母、数字、符号、空格,ascii 一共包含128个字符vector<int> pos(128,-1);int ans 0;for(int i0,j0 ; i<s.size();i) {//s[i]…