C++类基础5——拷贝构造函数,拷贝赋值运算符(复制构造函数,复制赋值运算符)

拷贝控制操作

 当定义一个类时,我们显式地或隐式地指定在此类望的对象拷贝,移动、赋值和销毁时做什么。

一个类通定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数(copy consinuctor)、拷贝赋值运算符(copy-assignment operator)、移动构造函数(movecomstructor)、移动赋值运算符(move-assignment operator)和析构函数(destructor)。

拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么。拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。析构函数定义了当此类型对象销毁时做什么。我们称这些操作为拷贝控制操作(copy control)。

如果一个类没有定义所有这些拷贝控制成员,编译器会自动为它定义缺失的操作。

因此,很多类会忽略这些拷贝控制操作。

但是,对一些类来说,依赖这些操作的默认定义会导致灾难。

通常,实现拷贝控制操作最困难的地方是首先认识到什么时候需要定义这些操作。

在定义任何C++类时,拷贝控制操作都是必要部分。对初学C++的程序员来说,必须定义对象拷贝、移动、赋值或销毁时做什么,这常常令他们感到困惑。

这种困扰很复杂,因为如果我们不显式定义这些操作,编译器也会为我们定义,但编译器定义的版本的行为可能并非我们所想。

拷贝构造函数

我们将以最基本的操作——拷贝构造函数、拷贝赋值运算符和析构函数作为开始。
 

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

class Foo {
public:
Foo(); // 默认构造函数
Foo(const Foo&); //拷贝构造函数
Foo(const Foo&t,int b=0)://拷贝构造函数
};

拷贝构造函数的第一个参数必须是一个引用类型,原因我们稍后解释。

虽然我们可以定一个接受非const引用的拷贝构造函数,但此参数几乎总是一个const的引用。

拷贝检造函数在几种情况下都会被隐式地使用。因此,拷贝构造函数通常不应该是 explicit的

合成拷贝构造函数

如果我们没有为一个类定义拷贝构造函数,编译器会为我们定义一个。

与合成默认构造函数不同,即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。

对某些类来说,合成拷贝构造函数用来阻止我们拷贝该类类型的对象。

而一般情况,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。

编译器从给定对象中依次将每个非与at3c成员拷贝到正在创建的对象中。

每个成员的类型决定了它如何接贝;对类类型的成员,会使用其拷贝构造函数来拷贝:

内置类型的成员则直接拷贝。

虽然我们不能直接拷贝一个数组,但合成拷贝构造函数正会逐元素地拷贝一个数组类型的成员。如果数组元素是类类型,则使用元素的拷贝构造函数来进行拷贝。

作为一个例子,我们的Sales_data类的合成拷贝构造函数等价于;
 

class Sales_data {
pubiic:
 //其他成员和构造函数的定义,如前
// 与合成的拷贝构造函数等价的拷贝构造函数的声明
Sales_data(const Sales_data&);

private:
std::string bookNo;
int units_sold =0;
double revenue = 0.0;
};

//与Sales_data的合成的拷贝构造函数等价
Sales data::Sales_data(const Sales_data &orig):
                                bookNo (orig.bookNo). //使用string的拷贝构造函数
                                 units_sold(orig.units_sold),//拷贝 orig.units_sold
                                revenue(orig.revenue) //拷贝 orig.revenue
{}//空函数体

拷贝初始化

现在,我们可以完全理解直接初始化和拷贝初始化之间的差异了(参见3.2.1节,第76页):

string dots(10,'.'); // 直接初始化
string s(dots); // 直接初始化
string s2 = dots; // 拷贝初始化
string null book="9-999-99999-9"; //.拷贝初始化
string nines = string(100, '9'); //拷贝初始化

当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。

当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。

拷贝初始化通常使用拷贝构造函数来完成。但是,如果一个类有一个移动构造函数,则拷贝初始化有时会使用移动构造函数而非拷贝构造函数来完成。

发生时机

但现在,我们只需了解拷贝初始化何时发生,以及拷贝初始化是依靠拷贝构造函数或移动构造函数来完成的就可以了。

拷贝初始化不仅在我们用=定义变量时会发生,在下列情况下也会发生

将一个对象作为实参传递给一个非引用类型的形参

#include<iostream>
using namespace std;
class A
{
public:
	int a_;
	A(const A& t)
	{
		a_ = t.a_;
		cout << "拷贝函数被调用" << endl;
	}
	A() = default;
};
void A1(A r){}

int main()
{
	A a;
	A1(a);
	
}

 

从一个返回类型为非引用类型的函数返回一个对象

#include<iostream>
using namespace std;
class A
{
public:
	int a_;
	A(const A& t)
	{
		a_ = t.a_;
		cout << "拷贝函数被调用" << endl;
	}
	A() = default;
};
A A2(A& r) { return r; }

int main()
{
	A a;
	
	A b = A2(a);
}

 

用花括号列表初始化一个数组中的元素或一个聚合类中的成员

int a[2]={1,2};//数组里存的是副本


某些类类型还会对它们所分配的对象使用拷贝初始化。

例如,当我们初始化标准库容器或是调用其insert或push成员时,容器会对其元素进行将拷贝初始化。与之相对,用emplace成员创建的元素都进行直接初始化

参数和返回值 

在函数调用过程中,具有非引用类型的参数要进行拷贝初始化。

类似的,当一个函数具有非引用的返回类型时,返回值会被用来初始化调用方的结果。

拷贝构造函数被用来初始化非引用类类型参数,这一特性解释了为什么拷贝构造函新自己的参数必须是引用类型。如果其参数不是引用类型,则调用永远也不会成功——为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环。

拷贝初始化的限制

如前所述,如果我们使用的初始化值要求通过一 explicit的构造函数来进行类型转换,那么使用拷贝初始化还是直接初始化就不是无关紧要的了:

vector<int> v1(10); // 正确:直接初始化
vector<int> v2 = 10;// 错误:接受大小参数的构造函数是explicit的
void f(vector<int>);// f的参数进行拷贝初始化
f(10);//错误:不能用一个explicit的构造函数拷贝一个实参
f(vector<int>(10));// 正确:从一个int直接构造一个临时vector


直接初始化v1是合法的,但看起来与之等价的拷贝初始化v2则是错误的,因为vector的接受单一大小参数的构造函数是 explicit 的

出于同样的原因,当传递一个实参或从函数返回一个值时,我们不能隐式使用一个 explicit 构造函数。

如果我们希望使用个explicit构造函数,就必须显式地使用,像此代码中最后一行那样。

编译器可以绕过拷贝构造函数

在拷贝初始化过程中,编译器可以(但不是必须)跳过拷贝/移动构造函数,直接创建对象。

即,编译器被允许将下面的代码

string null_book ="9-999-99999-9"; // 拷贝初始化

改写为

string null_book("9-999-99999-9");//编译器略过了拷贝构造函数


但是,即使编译器略过了拷贝/移动构造函数,但在这个程序点上,拷贝/移动构造函数必须是存在且可访问的(例如,不能是private的)。

拷贝赋值运算符

与类控制其对象如何初始化一样,类也可以控制其对象如何赋值:

Sales_data trans, accum;
trans = accum; //使用Sales_data的拷贝赋值运算符

与拷贝构造函数一样,如果类未定义自己的拷贝赋值运算符,编译器会为它合成一个。

重载赋值运算符

在介绍合成赋值运算符之前,我们需要了解一点儿有关重载运算符的知识,

重载运算符本质上是函数,其名字由operator关键字后接表示要定义的运算符的符号组成。因此,赋值运算符就是一个名为operator=的函数。类似于任何其他函数,运算符函数也有一个返回类型和一个参数列表。
重载运算符的参数表示运算符的运算对象。某些运算符,包括赋值运算符,必须定义为成员函数。如果一个运算符是一个成员函数,其左侧运算对象就绑定到隐式的this参数(参见7.1.2节,第231页)。对于一个二元运算符,例如赋值运算符,其右侧运算对象作为显式参数传递。
拷贝赋值运算符接受一个与其所在类相同类型的参数;

class Foo 
{
public:
Foo& operator=(conat Foo&);// 赋值运算符
//...
};


为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用。

另外值得注意的是,标准库通常要求保存在容器中的类型要具有赋值运算符,且其返回值是左侧运算对象的引用。

赋值运算符通常应该返回一个指向其左侧运算对象的引用。

合成拷贝赋值运算符

与处理拷贝构造函数一样,如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符。

类似拷贝构造函数,对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值。

如果拷贝赋值运算符并非出于此目的,它会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,这一工作是通过成员类型的拷贝赋值运算符来完成的。

对于数组类型的成员,逐个赋值数组元素。合成拷贝赋值运算符返回一个指向其左侧运算对象的引用。

作为一个例子,下面的代码等价于Sales_data的合成拷贝赋值运算符:

// 等价于合成拷贝赋值运算符
Sales_data&  Sales_data::operator=(const Sales_data &rhs)
{
bookNo = rhs.bookNo; // 调用 string::operator=
units_sold = rhs.units_sold; // 使用内置的int赋值
revenue = rhs.revenue; // 使用内置的double赋值
return *this; // 返回一个此对象的引用
}



 

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

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

相关文章

如何修复开机但不显示任何内容的计算机?这里提供详细步骤

前言​ 计算机“无法开机”的最常见方式是PC实际开机但在显示器上不显示任何内容。你看到电脑机箱上的灯,可能看到里面的风扇在转,甚至可能听到声音,但屏幕上什么也没有显示,请按照我们提供的顺序尝试以下常见修复方法。 测试显示器 在对计算机的其余部分进行更复杂和耗时…

Mac 下安装maven教程

note&#xff1a;网上已经有很多该类型教程了&#xff0c;这边自身保留一份&#xff0c;方便后面使用&#xff1b; 一、安装地址&#xff1a;官网 二、安装步骤 $ tar -xvf apache-maven-3.3.9-bin.tar.gz //mac支持手动点击解压 $ sudo mv -f apache-maven-3.3.9 /usr…

C语言函数递归调用

在C语言中&#xff0c;函数可以直接或间接地调用自身&#xff0c;这种函数调用自身的过程称为递归调用。递归是一种强大的编程技巧&#xff0c;能够简化程序结构、提高代码的可读性和可维护性。本文将介绍C语言函数递归调用的原理、应用场景以及注意事项。 以下是我整理的关于…

HANA中的内存及磁盘使用统计

1. 引言 在实际使用中&#xff0c;通过HANA的admin控制台&#xff0c;确实可以得到很多重要的信息。但有的时候不如人愿&#xff0c;你需要提供相应的SQL语句得到具体的信息。 比如&#xff0c;我要得到所有的行表的内存及磁盘占用信息&#xff1b;我需要得到所有列表的内存及…

[WebGL] 实例化绘制性能测试

实例化绘制&#xff08; Instanced Drawing &#xff09;是 OpenGL / WebGL 等图形 API 中常用的性能优化技术&#xff0c;它适用于同样的模型绘制次数非常多的场景&#xff0c;能够有效的降低显存占用和图形 API 接口调用的次数&#xff0c;达到性能提升的效果。以前我只知道怎…

蓝桥杯 java 承压计算

题目: 思路&#xff1a; 1&#xff1a;其中的数字代表金属块的重量(计量单位较大) 说明每个数字后面不一定有多少个0 2&#xff1a;假设每块原料的重量都十分精确地平均落在下方的两个金属块上&#xff0c;最后&#xff0c;所有的金属块的重量都严格精确地平分落在最底层的电子…

QT 二维坐标系显示坐标点及点与点的连线-通过定时器自动添加随机数据点

QT 二维坐标系显示坐标点及点与点的连线-通过定时器自动添加随机数据点 功能介绍头文件C文件运行过程 功能介绍 上面的代码实现了一个简单的 Qt 应用程序&#xff0c;其功能包括&#xff1a; 创建一个 MainWindow 类&#xff0c;继承自 QMainWindow&#xff0c;作为应用程序的…

物联网学习1、什么是 MQTT?

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、基于发布-订阅模式的消息传输协议&#xff0c;适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎&#xff0c;能够实现传感器、执行器和其它设备之间的高效通…

FLASH的读取与写入

FLASH的写入 结合HAL库所给参数&#xff1a; 查阅具体使用芯片的参考手册。 就不在详细解释&#xff0c;英文自行翻译。具体代码如下&#xff1a; /*FLASH写入程序*/ void WriteFlashTest(uint32_t L, uint32_t addr, uint32_t *Data,int Page) {int i0;/* 1/4解锁FLASH*/HAL…

揭秘五力模型:轻松掌控企业竞争命脉,决策不再迷茫!

五力分析模型又成为波特五力模型是由著名的管理学者迈克尔波特(Michael Porter)在20世纪80年代初提出的一种理论框架&#xff0c;它对企业营销中的战略制定产生了全球性的深远影响。这一模型被广泛应用于企业竞争战略的分析&#xff0c;可以帮助企业有效地分析企业在营销环境中…

python-pytorch获取FashionMNIST实际图片标签数据集

在查看pytorch官方文档的时候&#xff0c;在这里链接中https://pytorch.org/tutorials/beginner/basics/data_tutorial.html的Creating a Custom Dataset for your files章节&#xff0c;有提到要自定义数据集&#xff0c;需要用到实际的图片和标签。 在网上找了半天没找到&a…

蓝海项目!黑科技引爆全网流量小红书拉新,单日暴力收益7000+,小白也能轻松上手【揭秘】

众所周知&#xff0c;自去年以来&#xff0c;图文作品在各大平台如快手和抖音的流量大增&#xff0c;这得益于这些平台与小红书的竞争。目前&#xff0c;小红书也在积极推广&#xff0c;成功推荐新用户下载应用的奖励高达15元&#xff0c;这一数字几乎是其他平台的几倍。此外&a…

【TypeScript】解决字面量类型推断错误的四种方式

解决字面量类型推断错误的四种方式 方式一&#xff1a;对象属性使用类型断言方式二&#xff1a;传参使用类型断言方式三&#xff1a;对象使用类型断言方式四&#xff1a;对象属性使用变量&#xff0c;变量使用字面量类型参考 declare function handleRequest(url: string, meth…

深入理解HDFS工作原理:大数据存储和容错性机制解析

** 引言&#xff1a; ** 在当今数据爆炸的时代&#xff0c;存储和管理大规模数据成为了许多组织面临的重要挑战。为了解决这一挑战&#xff0c;分布式文件系统应运而生。Hadoop分布式文件系统&#xff08;HDFS&#xff09;作为Apache Hadoop生态系统的核心组件之一&#xff…

Java | Leetcode Java题解之第2题两数相加

题目&#xff1a; 题解&#xff1a; class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode head null, tail null;int carry 0;while (l1 ! null || l2 ! null) {int n1 l1 ! null ? l1.val : 0;int n2 l2 ! null ? l2.val : 0;int sum…

罗永浩直播阿里云服务器ECS u1性能如何?值得买吗?

罗永浩直播卖阿里云服务器&#xff0c;企业专享云服务器ECS通用算力型u1实例是什么&#xff1f;性能如何&#xff1f;目前阿里云服务器ECS u1实例&#xff0c;2核4G&#xff0c;5M固定带宽&#xff0c;80G ESSD Entry盘&#xff0c;优惠价格199元一年&#xff0c;罗永浩今晚直播…

算法学习16:数论03(容斥原理、博弈论)

算法学习16&#xff1a;数论03&#xff08;容斥原理、博弈论&#xff09; 文章目录 算法学习16&#xff1a;数论03&#xff08;容斥原理、博弈论&#xff09;前言一、容斥原理&#xff1a;求多个集合的并集二、博弈论1.Nim游戏&#xff1a;2.集合N-im游戏 总结 前言 提示&#…

【AI系列】Python NLTK 库和停用词处理的应用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

04 | Swoole 源码分析之 epoll 多路复用模块

首发原文链接&#xff1a;Swoole 源码分析之 epoll 多路复用模块 大家好&#xff0c;我是码农先森。 引言 在传统的IO模型中&#xff0c;每个IO操作都需要创建一个单独的线程或进程来处理&#xff0c;这样的操作会导致系统资源的大量消耗和管理开销。 而IO多路复用技术通过…

INA350ABSIDDFR 仪表放大器 单路低功耗 TSOT-23-8

NA350ABSIDDFR 是一款高精度、低功耗、单片式精密运算放大器。它具有出色的直流精度和低失调电压&#xff0c;适用于需要高精度信号处理的应用。这款产品广泛应用于各种领域&#xff0c;如工业控制、医疗设备、测试与测量设备以及通信系统等。 制造商: Texas Instruments …