C++ 11(3) | 新的类功能、可变参数模板、lambda表达式

news2025/4/15 22:52:23

前文中我们讲述了C++11中新增的右值引用,在本文中我们将继续讲解C++11中的新的类功能、可变参数模板、lambda表达式。

新的类功能

默认成员函数

之前我们学习过在C++中有六个默认成员函数构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、const 取地址重载。默认成员函数当我们在类中不写的时候编译器就会自动生成。在C++11中新增了两个,移动构造函数和移动赋值运算符重载。

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

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

类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。

强制生成默认函数的关键字default

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

禁止生成默认函数的关键字delete:

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

可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比
C++98,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。

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

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

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

递归函数展开方式展开参数包

// 递归终止函数
template <class T>
void ShowList()
{
    cout << 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;
}

逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在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;
}

// 编译器编译推演生成的代码
//void ShowList(int a1, char a2, std::string a3)
//{
//	int arr[] = { PrintArg(a1), PrintArg(a2), PrintArg(a3) };
//	cout << endl;
//}


int main()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', std::string("sort"));
    return 0;
}

lambda表达式

在C++98中当我们想要对一个数据集合中的元素进行排序,可以使用std::sort的方法,使用过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());
}

但是这样每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。在C++11中引入了lambda表达式更够简化仿函数的编写。下面我们下来介绍一下lambda的具体写法,然后我们在对上述的例子进行修改。

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函数不能做任何事情。

下面就来展示一下lambda表达式的使用:

int main() 
{
	auto add = [](int x, int y)->int {return x + y; };// 代表lambda对象

	cout << [](int x, int y)->int {return x + y; }(1, 2) << endl;
	cout << add(1, 2) << endl;
    
    // 省略返回值的类型
	auto add2 = [](int x, int y)
	{
		return x + y; 
	};

	// 最简单的lambda表达式,省略参数、返回值、函数体
    [] {};

	return 0;
}

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

捕捉列表说明

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

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

    auto func1 = [&x, y]() // 混合捕捉
    {; };
    auto func2 = [&]() // 全部引用捕捉
    {; };
    auto func3 = [=]() // 全部传值捕捉
    {; };
    auto func4 = [&, x]() // 全部传值捕捉, x传值捕捉
    {; };


	//auto swap2 = [x, y]() mutable //使传值捕捉可以进行捕获
	auto swap2 = [&x, &y]() // 捕捉列表可以捕捉外面作用域的变量 // 引用捕捉
	{
		int tmp = x;
		x = y;
		y = tmp;
	};

	swap2();
	cout << "x:" << x << " y:" << y << endl;
}

lambda函数总是一个const函数,按照上述的例子在捕获之后是不能够进行修改的,但是使用mutable就可以取消其常性,能够进行修改。

注意:

  1. 父作用域指包含lambda函数的语句块
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:
    [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
    [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  4. 在块作用域以外的lambda函数捕捉列表必须空。
  5. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  6. lambda表达式之间不能相互赋值,即使看起来类型相同

知道了上面的lambda的写法,我们就可以按照上述的规则尝试进行最开始的排序的例子的编写:

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表达式

那么这时可能就会有一个问题,一个lambda表达式究竟有多大,我们可以使用sizeof()函数对其求一下大小,对于这个结果我们可能会以为大小为是四个字节,因为这个lambda的使用与函数指针也是非常相似的。但是实际上它的大小是1字节。这个值就让我们想到了在我们学习类和对象的时候当类的内部没有类型只有函数的时候,它的大小就是1一字节。下面来进行一个实验:有两个相同功能的,一个是类的仿函数,还有一个是lambda表达式。

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);
	// lamber
	auto r2 = [=](double monty, int year)->double {return monty * rate * year;
	};
	r2(10000, 2);// lambda+uuid 汇编 每次生成的类的名称都不相同
	return 0;
}

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。这也就是为什么相同功能的lambda表达式不能够互相赋值,因为在实际上这两者是两个不同的类,这个类的类名使用的是lambda+uuid的方式,这样确保每次生成的类的名称都是不相同的。

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

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

相关文章

【59天|503.下一个更大元素II ● 42. 接雨水】

503.下一个更大元素II class Solution { public:vector<int> nextGreaterElements(vector<int>& nums) {stack<int> st;int n nums.size();vector<int> res (n, -1);for(int i0; i<2*n;i){while(!st.empty()&&nums[i%n]>nums[st.t…

第13章_事务基础知识

第13章_事务基础知识 1.数据库事务概述 事务是数据库区别于文件系统的重要特性之一&#xff0c;当我们有了事务就会让数据库始终保持一致性&#xff0c;同时我们还能通过事务的机制恢复到某个时间点&#xff0c;这样可以保证已提交到数据库的修改不会因为系统崩溃而丢失。 1…

Moonbeam路由流动性

Moonbeam路由流动性&#xff08;Moonbeam Routed Liquidity, MRL&#xff09;使加密资产流动性能够从其他生态系统&#xff08;如以太坊、Solana、Polygon或Avalanche&#xff09;进入波卡生态系统。借助MRL&#xff0c;用户可以通过简洁的用户体验向/从波卡转移他们的流动性。…

横竖屏切换导致页面频繁重启?详细解读screenLayout

目录 前言configChangesscreenLayout平板问题总结 前言 前几天多名用户反馈同一个问题&#xff0c;在小新平板上无法上网课&#xff0c;点击上课按钮后就退回到首页了。了解了一下发现小新平板现在销量特别好&#xff0c;于是赶紧申请了一台测试机打算看看到底是什么问题。 最…

牛客网语法篇刷题(C语言) — 分支控制

作者主页&#xff1a;paper jie的博客_CSDN博客-C语言,算法详解领域博主 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《C语言-语法篇》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白…

附录6-todolist案例

效果是这样的 可以添加新任务&#xff0c;改变任务状态&#xff0c;与筛选任务列表 使用vant创建的项目 使用到了bootstrap&#xff0c;首先 npm install bootstrap进行安装&#xff0c;安装后导入css与js 上中下是三个组件&#xff0c;依次是 todo_input&#xff0c;todo_li…

Python控制LitePoint IQxel-MW 无线网络测试仪

前言 由于项目需要进行WIFI和BT&#xff08;蓝牙&#xff09;的射频测试&#xff0c;所以需要开发一款支持WIFI/BT射频测试的工具。开发射频测试工具的话那肯定离不开仪表的控制。我们项目用的就Litepoint的IQxel-MW无线网络测试仪。这篇文章主要就是介绍一下如何控制仪表以及…

初探core组件:opencv - core组件进阶

core组件进阶 1.访问图像中的像素1.1 图像在内存之中的存储方式1.2 颜色空间缩减1.3 LUT函数&#xff1a;Look up table1.4 计时函数 2. ROI区域图像叠加&图像混合2.1 感兴趣区域&#xff1a;ROI2.2 ROI案例2.2 线性混合操作2.3 计算数组加权和&#xff1a;addWeighted()函…

python-segno:二维码制作

目录 二维码版本 微二维码、数据掩码、数据流、模式 微二维码 数据掩码 数据流 二维码模式 二维码背景 二维码参数 helpers方法 其他库制作及二维码读取&#xff1a;python生成和读取二维码_觅远的博客-CSDN博客 安装&#xff1a;pip install segno import segnoqr …

Qt+QtWebApp开发笔记(六):http服务器html实现静态相对路径调用第三方js文件

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131244269 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

经典30个嵌入式面试问题

经典30个嵌入式面试问题 嵌入式系统的面试经典问题有很多&#xff0c;以下是其中的30个常见问题&#xff1a; 1. 什么是嵌入式系统&#xff1f; 2. 嵌入式系统和普通计算机系统有什么区别&#xff1f; 3. 嵌入式系统的主要应用领域有哪些&#xff1f; 4. 嵌入式系统的设计…

接口测试工具之postman

概念 接口测试是什么&#xff1f; 百度百科给出的解释是&#xff1a; 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间…

构造函数初始化列表的问题

构造函数初始化列表的问题 无法按照表达式中的算符来修改值原因基本原则由此引申的问题使用初始化列表对类成员初始化在构造函数中赋值对类成员初始化 针对构造函数传参,使用引用的情况使用初始化列表对类成员初始化在构造函数中赋值对类成员初始化 将属性也使用引用总结 无法按…

STM32开发——看门狗

目录 1.独立看门狗 1.1需求 1.2CubeMX设置 1.3函数代码 2.窗口看门狗 2.1需求 ​2.2WWDG配置&#xff1a; 2.3函数代码 3.独立看门狗和窗口看门狗的异同点 1.独立看门狗 监测单片机程序运行状态的模块或者芯片&#xff0c;俗称“看门狗”(watchdog) 。 独立看门狗本质 本…

6、DuiLib控件消息响应处理

文章目录 1、DuiLib控件消息响应处理2、基本的消息响应处理 Notify3、仿 MFC 形式消息响应 DUI_DECLARE_MESSAGE_MAP4、事件委托 MakeDelegate5、消息捕获&#xff08;拦截&#xff09;原生消息 HandleMessage 1、DuiLib控件消息响应处理 <?xml version"1.0" en…

软件测试基础教程学习3

文章目录 软件质量与测试3.1 软件质量问题的原因3.2 对软件质量特性的理解3.3 基于软件质量特性的测试3.4 软件能力成熟度模型&#xff08;CMM&#xff09; 软件质量与测试 3.1 软件质量问题的原因 软件质量问题的原因有以下几种&#xff1a; 软件本身的特点和目前普遍采用的…

AWTK实现汽车仪表Cluster/DashBoard嵌入式GUI开发(五):芯片型号

前言 随着汽车四化的推进,可以说汽车仪表也变成越来越智能化的一个ECU部件了。市面上的车型很多,油车发展了很多年,都有仪表,电车也发展起来了,车型也非常丰富,也都有仪表。仪表现在是作为多屏中一个屏存在的,现在车上最多的就是屏了,最大的要算中控屏了,有的还有空调…

【基础】MQTT -- MQTT 协议详解

【基础】MQTT -- MQTT协议详解 与 Broker 建立连接CONNECT 数据包CONNACK 数据包 断开连接DISCONNECT 数据包 订阅与发布PUBLISH 数据包SUBSCRIBE 数据包SUBACK 数据包UNSUBSCRIBE 数据包UNSUBACK 数据包 本文内容针对 MQTT 3.1.1 版本&#xff0c;从连接、发布与订阅等方面对协…

OpenCV做个熊猫表情

有的时候很想把一些有意思的图中的人脸做成熊猫表情&#xff0c;但是由于不太会ps&#xff0c;只能无奈放弃&#xff0c;so sad... 正好最近想了解下opencv的使用&#xff0c;那就先试试做个简单的熊猫表情生成器把~~ 思路就是&#xff0c;工具给两个参数&#xff0c;一个是人…

最小系统板STM32F103C8T6烧录程序指南

STM32F103C8T6烧录程序 【购买链接】&#xff1a;STM32F103C8T6最小系统板 方法一&#xff1a;使用SWD模式烧录 此时BOOT0 0&#xff0c;BOOT1 X&#xff08;任意&#xff09;&#xff0c;跳线帽接法如下图所示 接好后&#xff0c;若手边有STLINK的话&#xff0c;可以使用…