C++11——新的类功能与可变参数模板

系列文章目录


文章目录

  • 系列文章目录
  • 一、新的类功能
      • 默认成员函数
      • 类成员变量初始化
      • 强制生成默认函数的关键字default
      • 禁止生成默认函数的关键字delete
      • 继承和多态中的final与override关键字
  • 二、可变参数模板
      • 递归函数方式展开参数包
      • 逗号表达式展开参数包
      • STL容器中的empalce_back与push_back的区别


一、新的类功能

默认成员函数

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

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

最后重要的是前4个,后两个用处不大,默认成员函数就是我们不写编译器会生成一个默认的

C++11 新增了两个:移动构造函数和移动赋值运算符重载

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

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

我们用之前自实现的string类来观察上述规律

namespace Tlzns
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
		{
			swap(s);
			cout << "string(string&& s) -- 移动构造" << endl;
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			swap(s);
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}
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& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}

	~Person()
	{}
private:
	Tlzns::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

在Person类没有实现移动构造函数,且实现析构函数 、拷贝构造、拷贝赋值重载的情况下
注:自实现的string类中已经支持移动构造和移动赋值重载
在这里插入图片描述
我们将Person类中实现的析构函数 、拷贝构造、拷贝赋值重载都注释掉后

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& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}

	~Person()
	{}*/
private:
	Tlzns::string _name;
	int _age;
};

在这里插入图片描述
可以观察到编译器默认生成了移动构造和移动赋值重载

类成员变量初始化

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

class A
{
public:
	~A() {}
private:
	int _a = 1;// 缺省值
	Tlzns::string _s = "aaa";// 缺省值
};

这里的缺省值会在构造/拷贝构造的初始化列表使用:
如果在构造/拷贝构造有这两个的初始化就不会走缺省值,谁不在初始化列表初始化就走谁的缺省值,相当于是一层额外的保险,如果初始化列表没有,就会走这里获得缺省值

强制生成默认函数的关键字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& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}

	~Person()
	{}

	Person(Person&& p) = default;
	Person& operator= (Person&& p) = default;


private:
	Tlzns::string _name;
	int _age;
};

将移动拷贝和移动赋值重载函数加上default,这样即使实现了析构函数 、拷贝构造、拷贝赋值重载,编译器也会默认生成
在这里插入图片描述

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

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

继承和多态中的final与override关键字

继承和多态中的内容
在C++中,final关键字可以用于类和成员函数。当用于类时,它表示该类不能被继承(Inheritance)。当用于成员函数时,它表示该成员函数不能在子类中被覆盖(Overriding)

如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译

二、可变参数模板

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

逗号表达式展开参数包

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

这种展开参数包的方式,不需要通过递归终止函数,是直接在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数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包

STL容器中的empalce_back与push_back的区别

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

我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用

而有这样的设计的empalce_back与push_back的区别在哪呢

  1. 构造函数的使用:
    emplace_back 可以直接将构造函数所需的参数传递过去,然后构建一个新的对象并将其填充到容器的尾部,它不会立即拷贝或移动新对象的副本,而是直接在容器尾部创建该对象,只调用了一次构造函数。push_back 首先会利用传入的参数调用构造函数构造一个临时的对象,然后将这个临时对象拷贝或移动到容器中,这个过程涉及到拷贝构造函数的使用,可能会导致额外的性能开销
  2. 效率问题:
    使用 emplace_back 可以更好地避免内存的拷贝和移动,从而提升容器插入元素的性能,如果容器的大小接近于某个特定的数值(如1, 2, 4, 8等),每次扩容都需要从头开始复制所有元素,这可能导致效率降低。在这种情况下,emplace_back 由于是在容器末尾直接创建新对象,因此不会有这样的问题
  3. 支持的功能:
    emplace_back 可以接受多个构造参数,并且支持原地构造,push_back 支持右值引用,但不支持传入多个构造参数,且总是会进行拷贝构造

综上所述,emplace_back 在效率和某些功能上优于 push_back,尤其是在处理大型容器时,因为它避免了不必要的拷贝和移动操作


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

写点东西《JWT 与会话身份验证》

写点东西《JWT 与会话身份验证》 身份验证与授权 JWT 与session身份验证 - 基本差异 什么是 JWT&#xff1f; JWT 结构&#xff1a; JWT 工作流程&#xff1a;优势: 安全问题&#xff1a; 处理令牌过期&#xff1a; 基于session的身份验证&#xff08;通常称为基于 cookie 的身…

工程对接大模型流式和非流式对话底层原理解析

文章目录 前言一、非流式输出设计二、stream流式输出设计三、手撸一个流式输出项目总结 前言 之前对接过OpenAi大模型的官方API&#xff0c;可以看到它有一个Stream参数&#xff0c;设置成true的时候就是流式的对话输出&#xff0c;现象就是一段一段的往外崩。 官方手册的地址…

蓝桥杯训练|基础语言Day1 - STL pair vector list stack queue set map容器

学习目标&#xff1a; 博主介绍: 27dCnc 专题 : 算法题入门 &#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d; ☆*: .&#xff61;. o(≧▽≦)o .&#xff61…

Python爬虫案例展示:实现花猫壁纸数据采集

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python 3.10 Pycharm 模块使用: import requests >>> pip install requests win R 输入cmd 输入安装命令 pip install requests 安装即…

Springboot各种请求参数详解

文章目录 请求Postman**为什么需要Postman****什么是Postman****Postman使用教程** 请求参数简单参数实体参数数组参数集合参数![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/eba0ca80e3724412ae4c79af72b859c3.png#pic_center)日期参数json参数路径参数总结 请求…

STM32CubeMX教程31 USB_DEVICE - HID外设_模拟键盘或鼠标

目录 1、准备材料 2、实验目标 3、模拟鼠标实验流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.0、工程基本配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.0、配置Project Manager页面 3.2.1、设初始化调用流程 3.2.2、外设中…

ESP8266 控制之 : 使用 RingBuffer USART1 和 USART3互传

简介 使用Buffer来避免数据的丢失, 或许你自己在使用串口进行收发时会丢失数据, 现在我们就来简单使用一下RingBuffer创建Rx、Tx的Buffer来避免发送接收丢包或数据丢失问题。 扩展知识 RingBuffer的介绍, 看完大概也就知道了&#xff0c;实在不知道就看看下面的代码 线路连接…

详解操作系统各章大题汇总(死锁资源分配+银行家+进程的PV操作+实时调度+逻辑地址->物理地址+页面置换算法+磁盘调度算法)

文章目录 第三章&#xff1a;死锁资源分配图例一例二 第三章&#xff1a;银行家算法第四章&#xff1a;进程的同步与互斥做题步骤PV操作的代码小心容易和读者写者混 1.交通问题&#xff08;类似读者写者&#xff09;分析代码 2.缓冲区问题&#xff08;第二个缓冲区是复制缓冲区…

实现元素进入界面的平滑效果

先看效果&#xff1a; 实现思路&#xff1a;获取页面中需要加载动画的节点&#xff0c;用元素的animate()方法创建一个动画对象&#xff0c;并传入两个关键帧&#xff0c;接着使用IntersectionObserverAPI创建观察对象&#xff0c;用于观察元素进入页面。当元素进入界面时&…

【数据分享】1929-2023年全球站点的逐年平均气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01;本次我们为大家带来的就是具体到气象监…

CSS--Emmet 语法

Emmet语法的前身是Zen coding,它使用缩写,来提高html/css的编写速度, Vscode内部已经集成该语法. 目录 1. 快速生成HTML结构语法 1.1 快速生成HTML结构语法 2. 快速生成CSS样式语法 2.1 快速生成CSS样式语法 1. 快速生成HTML结构语法 1.1 快速生成HTML结构语法 1. 生成标…

2.【Vue3】Vue 基本使用——局部使用Vue

文章目录 1. 快速入门2. 常用指令2.1 v-for2.2 v-bind2.3 v-if 与 v-show2.4 v-on2.5 v-model 3. 生命周期4. Ajax 函数库 Axios4.1 Axios 基本使用4.2 Axios 请求方式别名 1. 快速入门 现在需要将 “hello vue3” 这样一个字符串渲染到页面上进行展示。 这个需求并不陌生&…

JVM系列——对象管理

JVM对象分布 对象头 第一类是用于存储对象自身的运行时数据&#xff0c;如哈希码&#xff08;HashCode&#xff09;、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等 另外一部分是类型指针&#xff0c;即对象指向它的类型元数据的指针&#xff0c;Java 虚…

【ArcGIS微课1000例】0096:dem三维块状表达(层次地形模型)

文章目录 一、DEM表达方式二、层次模型表达三、注意事项一、DEM表达方式 DEM数字高程模型的表达方式通常有以下4种: 1. 规则格网 2. 不规则三角网 3. 等高线 4. 层次地形模型 作为栅格地理数据,DEM 数据具有2.5维的特征,能够以三维表面的形式进行三维空间表达。但受其数…

Web 开发 6:Redis 缓存(Flask项目使用Redis并同时部署到Docker详细流程 附项目源码)

大家好&#xff01;欢迎来到第六篇 Web 开发教程&#xff0c;今天我们将探讨一个非常重要的话题&#xff1a;Redis 缓存。作为一个互联网开发者&#xff0c;你一定知道在处理大量请求时&#xff0c;性能优化是至关重要的。而 Redis 缓存正是帮助我们提升系统性能的利器。Redis …

爬虫基础-计算机网络协议

一个数据的传输 这些设备的数据转发是通过协议来完成的&#xff0c;整个互联网可以说是完全由网络协议来维持的 不同的协议分工不同&#xff0c;比如ip协议确保了ip寻址&#xff0c;tcp协议确保了数据完整性 IP地址和URL ip地址 整个网络传输可以比作快递&#xff0c;数据就…

2023年度总结——忙忙碌碌,终有归章

思来想去&#xff0c;还是决定写一篇年终总结&#xff0c;一来算是对23年的一年的回顾&#xff0c;二来是对24年的展望。记得22年也写过一篇年度总结&#xff0c;题目是《2022年度总结——一切都在慢慢变好》。今年&#xff0c;我想起的题目是《2023年度总结——忙忙碌碌&#…

在Temu跨境电商平台上,如何快速出单?

随着越来越多的商家选择入驻Temu跨境电商平台&#xff0c;一旦入驻申请通过&#xff0c;商家就可以开始上架商品并等待订单的产生。然而&#xff0c;很多新手跨境电商卖家都面临一个共同的问题&#xff0c;那就是&#xff1a;Temu出单快吗&#xff1f;Temu上架多久能出单&#…

STM32CubeMX教程27 SDIO - 读写SD卡

目录 1、准备材料 2、实验目标 3、轮询方式读取SD卡流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.0、工程基本配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.0、配置Project Manager页面 3.2.1、外设初始化调用流程 3.2.2、外设中断调用流…

激光雷达,角力「降本增效」

高工智能汽车研究院最新发布的数据显示&#xff0c;2023年1-11月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用车前装标配激光雷达搭载量为46.48万颗&#xff0c;同比增长372.35%&#xff0c;继续保持高增长态势。 随着激光雷达在中国市场完成规模化上量的节点&…