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

news2024/11/27 14:33:57

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}构造complex
    complex(double r) : re{r} {}					// 从一个标量{r,0}构造complex
    complex() {}									// 默认值为{0,0}的complex
    // ...
};

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

6.2 拷贝和移动

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

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

6.2.1 拷贝容器

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

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

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

void bad_copy(Vector v1)
{
    Vector v2 = v1;	// 将v1的表层拷贝到v2
    v1[0] = 2;		// v2[0]也变成了2
    v2[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.coloradmin.cn/o/1192802.html

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

相关文章

广州华锐互动:VR互动实训内容编辑器助力教育创新升级

随着科技的飞速发展,教育领域也正在经历一场深刻的变革。其中,虚拟现实(VR)技术为教学活动提供了前所未有的便利和可能性。在诸多的VR应用中,VR互动实训内容编辑器无疑是最具潜力和创新性的一种。广州华锐互动开发的这款编辑器以其独特的功能…

HelloGitHub 社区动态,开启新的篇章!

今天这篇文章是 HelloGitHub 社区动态的第一篇文章,所以我想多说两句,聊聊为啥开启这个系列。 我是 2016 年创建的 HelloGitHub,它从最初的一份分享开源项目的月刊,现如今已经成长为 7w Star 的开源项目、1w 用户的开源社区、全网…

Xshell安装+使用教程

简介 Xshell 是一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议。Xshell 通过互联网到远程主机的安全连接以及它创新性的设计和特色帮助用户在复杂的网络环境中享受他们的工作。 Xshell可以在Windows界面下用来访问远端不…

linux中使用arthas进行jvm内存分析

1. 安装下载 首先在官方github地址选择合适的版本,下载后上传到对于服务器。 使用unzip arthas-bin.zip 解压文件。进入目录中,执行./install-local.sh进行安装。执行完成后提示succeed,即可使用。 2. 启动 进入目录,执行java…

Python大语言模型实战-利用ChatDev框架自动开发一个游戏软件(附完整教程)

实现功能 ChatDev一个由多智能体协作框架,是一个虚拟软件公司,在人类 “用户” 指定一个具体的任务需求后,不同角色的智能体将进行交互式协同,以生产一个完整软件(包括源代码、环境依赖说明书、用户手册等&#xff09…

智汇云舟入选IDC《中国智慧园区解决方案2023年厂商评估》报告

近日,全球领先的市场研究和咨询公司IDC发布报告《中国智慧园区解决方案2023年厂商评估》。报告内,IDC对中国市场具有代表性、且符合评估入围门槛要求的智慧园区解决方案厂商进行了综合评估。智汇云舟凭借在产品、技术等方面的综合优势,与大华…

从事人力资源相关工作,必须要有人力资源证书吗?

人力资源证书不是HR必备,但高含金量的HR证书确实是个加分项,有时候门槛有时候就是一证之隔。 作为人力资源从业者或者打算从事人力行业的同学,如果有意向考证的不妨看看,有哪些证书可以让你真正学以致用?哪些证书可以…

伦敦金冬令时开市时间怎样调整

在刚刚过去的一周,欧美的金融市场已经正式进入了冬令时,这对伦敦金市场的交易时间也产生了影响。由于美国于今年11月5日(星期日)开始正式实施冬令时间,所以香港的伦敦金平台的交易时间也随之而有所调整。 从今年11月6日开始&#…

makefile的基础使用

1、建一个目录: mkdir Makefile/makefile(两个任意一个就可以) 2、用vim打开 3、在makefile里面的写法: 目标文件 : 依赖文件 >小例子: test:test.c [tab]依赖关系 gcc -o test test.c 4、…

win10 下 ros + Qt 工程CMakeLists.txt

win10 下 ros Qt 工程CMakeLists.txt 系统:win10 ros: melodic Qt: 5.12.12 源码目录: D:\workspace\catkin_qt 示例代码 https://github.com/ncnynl/ros-qt.git 由于示例代码是Qt4 ,目前我是用QT5,所以CMakeLists.txt 修改如下 CMakeLists.txt #####…

区块链多链数字钱包开发

随着区块链技术的不断发展,多链数字钱包的开发逐渐成为热门领域。多链数字钱包是一种可以支持多种区块链网络的数字钱包,用户可以使用它来存储、管理和转移不同的数字资产。本文将探讨多链数字钱包的开发背景、市场需求、技术实现和未来趋势等方面。 一、…

AI编程工具:一站式编程解决方案,引领AI编程新时代

在人工智能的巨浪之下,编程领域正在经历一场深刻的转型。这场转型的核心,就是AI智能编程工具的出现。它们为开发者提供了一种全新的编程方式,极大地提高了编程效率。在这场变革中,DevChat无疑是引领者之一。 一、DevChat&#xf…

2011年03月31日 Go生态洞察:Godoc —— Go代码的文档化

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

98 多数元素

多数元素 题解1 哈希表题解2 Boyer-Moore 投票算法 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n / 2 ⌋ ⌊ n/2 ⌋ ⌊n/2⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例…

外部访问K8S集群内部的kafka集群服务

不许转载 kafka 部署 把 kafka 部署到 k8s 后,我们肯定是通过 service 从 k8s 外部访问 kafaka。这里的 service 要么是 NodePort, 要么是 LoadBalancer 类型。我们使用的方式是 LoadBalancer。 我们先看下面这张图,这是 kafka 在集群中的网…

案例精选|聚铭综合日志分析系统为中电飞华业务数据安全保驾护航

当下,云和网正从过去的独立走向融合,各行各业从“上网”纷纷演进到“上云”。“上云,才能更好地拥抱数字时代”。云网融合高质量发展对信息基础设施能力提出了新要求,同时运营商在产业数字化领域的业务探索也需要强大的云网能力支…

如何构建并提高自己的核心竞争力?

上一篇文章聊到了软件工程师的核心竞争力主要分为三个方面:快速学习能力、解决问题能力和个人影响力,且核心竞争力的培养和提高需要长时间实践和积累,并不是短时间就可以达到的。这篇文章, 来聊聊如何培养和提高自己的核心竞争力。…

代码随想录算法训练营第四十七天丨 动态规划part10

121. 买卖股票的最佳时机 思路 动态规划 动规五部曲分析如下: 确定dp数组(dp table)以及下标的含义 dp[i][0] 表示第i天持有股票所得最多现金 ,这里可能有疑惑,本题中只能买卖一次,持有股票之后哪还有…

什么是 CASB,在网络安全中的作用

数字化转型正在稳步攀升,组织现在越来越关注在线生产力系统和协作平台,各行各业的企业都采用了不同的云基础设施服务模式。云基础架构提供按需服务,可提高易用性、访问控制、内容协作和减少内部存储资源,以及许多其他好处。迁移到…

多彩的树 -----题解(状压dp + 容斥原理)

目录 多彩的树 题目描述 输入描述: 输出描述: 输入 输出 思路解析: 代码实现: 多彩的树 时间限制:C/C 5秒,其他语言10秒 空间限制:C/C 262144K,其他语言524288K 64bit IO Format: %lld 题目描述 …