C++之旅(学习笔记)第6章 基本操作

C++之旅(学习笔记)第6章 基本操作

6.1 基本操作

class X{
public:X(Sometype);			// "普通的构造函数": 创建一个对象X();					// 默认构造函数X(const X&);			// 拷贝构造函数X(X&&);					// 移动构造函数X& operator=(const X&);	// 拷贝赋值操作符:清空目标对象并拷贝X& operator=(X&&);		// 移动赋值操作符:清空目标对象并移动~X();					// 析构函数:清理资源//...
};

在下面5种情况下,对象会被移动或拷贝:

  • 赋值给其他对象。
  • 作为对象初始化。
  • 作为函数的实参。
  • 作为函数的返回值。
  • 作为异常。

如果希望显示地使用函数的默认实现:在函数后面加上 =default

class Y{
public:Y(Sometype);Y(const Y&) = default;	// 我确实需要默认的拷贝构造函数Y(Y&&) = default;		// 也确实需要默认的移动构造函数
};
  • 一旦显示地指定了某些函数地默认形式,编译器就不会再为函数生成其他默认定义了。
  • 当类中含有指针成员时,最好显示地指定拷贝操作和移动操作。如果不这样做,当编译器生成地默认函数试图delete指针对象时,系统将发生错误。

如果不想生成目标操作函数:在函数后面加上=delete

class Shape {
public:Shape(const Shape&) =delete;		// 禁止拷贝Shape& operator=(const Shape&) =delete;//...
};
void copy(Shape& s1, const Shape& s2)
{s1 = s2;							// 错误:Shape禁止拷贝
}
  • 试图使用=delete的函数会在编译时报错;=delete可以用于禁用任意函数,并非仅仅用于禁用基础成员函数。

6.1.2 转换

接受单个参数的构造函数同时定义了从参数类型到类类型的转换。

例如:complex提供了一个接受double类型的参数的构造函数:

class complex {double re,im;		// 成员变量:两个双精度浮点数
public:complex(double r,double i):re{r},im{i}{}	// 用两个标量构建该复数complex(double r):re{r},im{0}{}				// 用一个标量构建该复数complex():re{0},im{0}{}						// 默认的复数是:{0,0}//...
};
complex z1 = 3.14;	// z1变成 {3.14,0.0}
complex z2 = z1*2;	// z2变成 z1*{2.0,0} == {6.28,0.0}
  • 这种转换有时似乎合情理,有时则不然。

例如:Vector提供了一个接受int类型的参数构造函数:

class Vector{
private:double* elem;int sz;
public:Vector(int s):elem{new double[s]},sz{s}{for(int i = 0; i != s; ++i)elem[i] = 0;}~Vector(){delete[] elem;}// ...
};
Vector v1 = 7;		// 可行:v1有7个元素
  • 通常情况下,该语句的执行结果并非如我们的预期,标准库vector禁止这种int到vector的转换。

解决该问题的办法是只允许显示进行类型转换:

class Vector{
public:explicit Vector(int s);	// 不能隐式地将int转化为Vector// ...
};
Vector v1(7);		// 可行:v1有7个元素
Vector v2 = 7;		// 错误:不能隐式地将int转化为Vector
  • 关于类型转换地问题,complex只是一小部分,大多数类型地情况与Vector类似。

所以除非你有充分地理由,否则最好把接受单个参数的构造函数声明成explicit的。

6.1.3 成员初始值设定项

定义类的数据成员时,可以提供默认的初始值,称其为默认成员初始值设定项。

如下:修订版本的complex

class complex {double re = 0;double im = 0;	// 表示两个默认值为0.0的double类型的成员
public: complex(double r, double i) : re{r}, im{i} {}	// 从两个标量{r,i}构造complexcomplex(double r) : re{r} {}					// 从一个标量{r,0}构造complexcomplex() {}									// 默认值为{0,0}的complex// ...
};

对于所有构造函数没有提供初始值的成员,默认初始值都会起作用。

6.2 拷贝和移动

默认情况下,我们可以拷贝对象,不论是用户自定义类型的对象还是内置类型的对象。

拷贝的默认含义是逐成员地复制,即依次复制每个成员。

6.2.1 拷贝容器

当一个类被作为资源句柄时,换句话说,当这个类负责通过指针访问一个对象时,采用默认的逐成员复制方式通常意味着会产生灾难性的错误。

逐成员复制的方式会违反资源句柄的约束条件。

例如:默认拷贝将产生Vector的一份拷贝,而这个拷贝所指向的元素与原来的元素是同一个:

void bad_copy(Vector v1)
{Vector v2 = v1;	// 将v1的表层拷贝到v2v1[0] = 2;		// v2[0]也变成了2v2[1] = 3;		// v1[1]也变成了3
}

假设v1包含4个元素,则结果如下图所示:

在这里插入图片描述

类对象的拷贝操作可以通过两个成员来定义:拷贝函数与拷贝赋值操作符:

class Vector {
public:Vector(int s);~Vector() { delete[] elem; }Vector(const Vector& a);				// 拷贝构造函数Vector& operator=(const Vector& a);		// 拷贝赋值操作符double& operator[](int i);const double& operator[](int i) const;int size() const;
private:double* elem;int sz;
};

对Vector来说,拷贝构造函数的正确定义应该首先为指定数量的元素分配空间,然后把元素复制到空间中。这样复制完成后,每个Vector就拥有自己的元素拷贝了:

Vector::Vector(const Vector& a) : elem {new double[a.sz]}, sz{a.sz}	//拷贝构造函数,分配元素所需要的空间
{for(int i = 0; i != sz; ++i)elem[i] = a.elem[i];
}

在这个示例中,v2=v1的结果现在可以表示成:

在这里插入图片描述

除了拷贝构造函数,我们还需要一个拷贝赋值操作符:

Vector& Vector::operator=(const Vector& a) 	// 拷贝赋值操作
{double* p = new double[a.sz];for(int i = 0; i != a.sz; ++i)p[i] = a.elem[i];delete[] elem;							// 删除旧元素elem = p;sz = a.sz;return *this;
}

其中,名字this被预定义在成员函数中,它指向调用该成员函数的那个对象。

元素拷贝发生在旧元素被删除之前,所以如果在拷贝的过程中抛出异常,Vector的旧值可得以保留。

6.2.2 移动容器

对于大容量的容器,拷贝过程有可能消耗巨大。

当给函数传递对象时,可通过使用引用类型来减少拷贝对象的代价,但是无法返回局部对象的引用(函数的调用者都没机会和返回结果碰面,局部对象就被销毁了)。

我们相比于拷贝一个Vector对象,更希望移动它。

class Vector {// ...Vector(const Vector& a);			// 拷贝构造函数(复制构造)Vector& operator=(const Vector& a);	// 拷贝赋值操作符(复制赋值)Vector(Vector&& a);					// 移动构造函数Vector& operator=(Vector&& a);		// 移动赋值操作符
};

基于上述定义,编译器将选择移动构造函数来执行从函数中移出返回值的任务。

定义Vector移动构造函数的过程非常简单:

Vector::Vector(Vector&& a) : elem {a.elem}, sz{a.sz}
{a.elem = nullptr;	//现在a中没有任何元素a.sz = 0;
}

符号&&的意思是”右值引用“,右值的含义与左值正好相反。

左值的大致含义是 ”能出现在赋值操作符左侧的内容“,因此右值大致上就是无法为其赋值的值,比如函数调用返回的一个整数就是右值。进一步地,右值引用地含义就是引用了一个别人无法赋值地内容,所以我们可以安全地”窃取“它的值。

移动构造函数不接受const实参:毕竟移动构造函数最终要删除它实参中的值。

6.3 资源管理

通过定义构造函数、拷贝操作、移动操作和析构函数,程序员就能对受控资源(比如容器中元素)的生命周期进行完全控制。

内存也不是唯一的一种资源。资源是指任何在使用前需要获取与(显示或隐式)释放的东西,除了内存,还有锁、套接字、文件句柄和线程句柄等非内存资源。

在C++标准库中,RAII无处不在:例如,内存(string、vector、map、unordered_map等)、文件(ifstream、ofstream等)、线程(thread)、锁(lock_guard、unique_lock等)和通用对象(通过unique_ptr和shared_ptr访问)。

6.4 建议

  1. 尽量让对象的构造、拷贝(复制)、移动和销毁、在掌控之中;

  2. 同时定义所有的基本操作,或者什么都不定义;

  3. 如果默认的构造函数、赋值操作符和析构函数符合要求、那么让编译器负责生成它们;

  4. 如果类含有指针成员,考虑这个类是否需要用户自定义或者删除析构函数、拷贝函数及移动函数;

  5. 默认情况下,把单参数的构造函数声明成explicit的;

  6. 如果默认拷贝函数不适合当前类型,则重新定义或禁止拷贝函数;

  7. 用传值的方式返回容器(依赖拷贝消除和移动以提高效率);

  8. 避免显示使用std::copy();

  9. 对于容量较大的操作数,使用const引用作为参数类型;

  10. 使用RAII管理所有资源——内存和非内存资源;

  11. 如果默认的构造函数、赋值操作符和析构函数符合要求、那么让编译器负责生成它们;

  12. 如果类含有指针成员,考虑这个类是否需要用户自定义或者删除析构函数、拷贝函数及移动函数;

  13. 默认情况下,把单参数的构造函数声明成explicit的;

  14. 如果默认拷贝函数不适合当前类型,则重新定义或禁止拷贝函数;

  15. 用传值的方式返回容器(依赖拷贝消除和移动以提高效率);

  16. 避免显示使用std::copy();

  17. 对于容量较大的操作数,使用const引用作为参数类型;

  18. 使用RAII管理所有资源——内存和非内存资源;

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

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

相关文章

微信小程序将后端返回的图片文件流解析显示到页面

说明 由于请求接口后端返回的图片格式不是一个完整的url,也不是其他直接能显示的图片格式,是一张图片 后端根据模板与二维码生成图片,返回二进制数据 返回为文件流的格式,用wx.request请求的时候,就自动解码成为了下面这样的数据数据格式,这样的数据没…

Go-服务注册和发现,负载均衡,配置中心

文章目录 什么是服务注册和发现技术选型 Consul 的安装和配置1. 安装2. 访问3. 访问dns Consul 的api接口go操作consulgrpc下的健康检查grpc的健康检查规范动态获取可用端口号 负载均衡策略1. 什么是负载均衡2. 负载均衡策略1. 集中式load balance2. 进程内load balance3. 独立…

SOLIDWORKS软件提供了哪些特征造型方法?硕迪科技

SOLIDWORKS作为一款三维设计软件,为用户提供了多种特征造型方法,以下是其中几种常用的: 实体建模特征:SOLIDWORKS使用实体建模技术来创建和编辑三维几何体。通过使用基本几何体(如立方体、圆柱体、圆锥体等&#xff09…

计算机视觉驾驶行为识别应用简述

一、什么是计算机视觉识别? 计算机视觉识别是一种基于图像处理和机器学习的人工智能应用技术,可以用于多个场景。常见应用场景包括人脸识别、场景识别、OCR识别以及商品识别等。今天以咱们国产系统豌豆云为例,为大家梳理一下在车辆驾驶行为中…

docker部署redis6

前言:在离线服务器上(无联网),部署redis的方式,采用docker是比较方便的。下面将描述如何使用docker部署单机版redis 环境:centos 7 redis:6.2.14 docker:20.10.9 1.下载 redis 镜像…

工业相机基本知识理解:工业相机IO接口,功耗和供电方式

I-input 相机接收外部信号,可用于触发相机(硬触发),也可用于定制不同的 功能,例如使用不同信号宽度来改变相机的曝光时间。主要用于现场设 备控制相机使用,常常配合各种传感器使用 O-output 相机输出信号&a…

单链表(增删改查)【超详细】

目录 单链表 1.单链表的存储定义 2.结点的创建 3.链表尾插入结点 4.单链表尾删结点 5.单链表头插入结点 6.单链表头删结点 7.查找元素,返回结点 8.在pos结点前插入一个结点 ​编辑 9.在pos结点后插入一个结点 10.删除结点 11.删除pos后面的结点 12.修改…

一文读懂RestCloud AppLink

RestCloud AppLink是什么? RestCloud AppLink 是一种应用程序集成解决方案,它提供了一套工具和技术,用于实现不同应用程序之间的无缝集成和交互。平台旨在解决企业中应用程序之间数据孤岛、信息孤立和业务流程不畅的问题,提高企业…

每次重启完IDEA,application.properties文件里的中文变成?

出现这种情况,在IDEA打开Settings-->Editor-->File Encodings 然后,你需要将问号改为你需要的汉字。 重启IDEA,再次查看你的.properties文件就会发现再没有变成问号了

XOR Construction

思路: 通过题目可以得出结论 b1^b2a1 b2^b3a2 ....... bn-1^bnan-1 所以就可以得出 (b1^b2)^(b2^b3)a1^a2 b1^b3a1^a2 有因为当确定一个数的时候就可以通过异或得到其他所有的数,且题目所求的是一个n-1的全排列 那么求出a的前缀异或和arr之后…

网页分析和xml.etree库

源代码: Lib/xml/etree/ElementTree.py 该xml.etree.ElementTree模块实现了一个简单高效的 API,用于解析和创建 XML 数据。 一、说明 这是一个简短的使用教程xml.etree.ElementTree(ET简而言之)。目标是演示该模块的一些构建块和基…

送水服务预约小程序内容该如何做

无论小区还是办公楼等场景,送水服务往往有较高需求,同时该服务属于长期稳定性的,因此对品牌来说,如何打造品牌获取更多用户及转化非常重要,然而在实际订水过程中,又会面临着一些难题: 1、品牌传…

代码随想录算法训练营第四十六天|139. 单词拆分、多重背包问题、总结

第九章 动态规划part08 139. 单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 关于字符串类型的题目还是…

ElementUI之el-progress动态修改进度条里面文本颜色与进度条色块统一

1.效果&#xff1a; 2.实现方式 通过行内style样式动态给整个progress赋颜色 再在样式里给进度条文字单独设置颜色为默认继承父级颜色就ok啦 <el-progress class"custom-progress" stroke-linecap"square" :style"{color:item.color}" :colo…

使用电阻检测仪是否能满足生产车间防静电要求

在现代工业生产中&#xff0c;静电对产品质量和人员安全造成的影响越来越受到重视。特别是在电子、半导体、化工等领域&#xff0c;静电问题可能导致产品损坏、人员触电等严重后果。因此&#xff0c;生产车间的防静电工作显得尤为重要。而电阻检测仪作为一种常用的防静电工具&a…

Java后端开发——JDBC入门实验

JDBC&#xff08;Java Database Connectivity&#xff09;是Java编程语言中用于与数据库建立连接并进行数据库操作的API&#xff08;应用程序编程接口&#xff09;。JDBC允许开发人员连接到数据库&#xff0c;执行各种操作&#xff08;如插入、更新、删除和查询数据&#xff09…

OpenAI开发者大会大模型圈开卷AI Agent? 实在智能布局前瞻已下“先手棋”

“平地起惊雷&#xff0c;至今有余音。” 去年的11月&#xff0c;OpenAI发布ChatGPT给科技圈劈下了一道惊雷&#xff0c;引爆了全世界的AI大模型热潮&#xff0c;全球科技巨头公司争先恐后地推出通用大模型&#xff0c;探索产业应用的可能。 短短一年后&#xff0c;北京时间1…

汇编-DUP操作符

DUP操作符使用整数表达式作为计数器&#xff0c; 为多个数据项分配存储空间。 在为字符串或数组分配存储空间时&#xff0c;这个操作符尤其有用&#xff0c;并且可以使用初始化或非初始化数据&#xff1a; .data BYTE 20 DUP(0) ;20个字节&#xff0c;都等于0 BYTE 20 …

电商项目之Java8函数式接口落地实践

文章目录 1 问题背景2 前言3 多处重复的重试机制代码4 优化后的代码5 进一步优化 1 问题背景 在电商场景中&#xff0c;会调用很多第三方的云服务&#xff0c;比如发送邮件、发起支付、发送验证码等等。由于网络存在抖动&#xff0c;有时候发起调用后会拿到500的状态码&#xf…

Linux学习第38天:Linux I2C 驱动实验(二):哥俩好

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 本节笔记主要学习I2C设备驱动编写及硬件原理图分析。 先把整个本节的思维导图贴出来&#xff1a; 二、I.MX6U的I2C适配器驱动分析 适配器驱动一般都是由SOC厂商提…