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

news2024/12/25 9:05:33

拷贝控制操作

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

一个类通定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数(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; // 返回一个此对象的引用
}



 

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

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

相关文章

某国投集团知识竞赛活动方案

一、抽签分组办法 1.抽签&#xff1a;参赛队伍赛前进行抽签分组。 2.分组&#xff1a;全部报名参赛队伍按照抽签顺序分为4组&#xff0c;每组7支队伍进行预赛&#xff0c;9月16日上午1、2组进行初赛&#xff0c;9月16日下午3、4组进行初赛。每组决出的前三名进入决赛。 二、初…

【Frida】【Android】工具篇:ZenTracer

&#x1f6eb; 系列文章导航 【Frida】【Android】01_手把手教你环境搭建 https://blog.csdn.net/kinghzking/article/details/136986950【Frida】【Android】02_JAVA层HOOK https://blog.csdn.net/kinghzking/article/details/137008446【Frida】【Android】03_RPC https://bl…

Leetcode刷题记录面试基础题day1(备战秋招)

hello&#xff0c;你好鸭&#xff0c;我是康康&#xff0c;很高兴你能来阅读&#xff0c;昵称是希望自己能不断精进&#xff0c;向着优秀程序员前行!&#x1f4aa;&#x1f4aa;&#x1f4aa; 目前博客主要更新Java系列、数据库、项目案例、计算机基础等知识点。感谢你的阅读和…

P2802 回家

P2802 回家 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 虽然是普及-难度的题&#xff0c;但是感觉细节有很多。 细节&#xff1a; bfs第一次到 ( i , j ) (i, j) (i,j)&#xff0c;但是距离不一定是最小的 鼠标是一次性物品 血量到达 ( x x , y y ) (xx, yy) (xx,yy)为…

【二分图】【二分图最大匹配】LCP 04. 覆盖

作者推荐 视频算法专题 本文涉及知识点 二分图 二分图最大匹配 LeetCode LCP 04. 覆盖 你有一块棋盘&#xff0c;棋盘上有一些格子已经坏掉了。你还有无穷块大小为1 * 2的多米诺骨牌&#xff0c;你想把这些骨牌不重叠地覆盖在完好的格子上&#xff0c;请找出你最多能在棋盘…

探索C语言中的联合体和枚举:让处理数据更加得心应手

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;http://t.csdnimg.cn/Oytke 小新的主页&#xff1a;编程版小新-CSDN博客 C语言中有内置类型&#xff0c; 比如&…

JavaScript:快速入门

1. 数据类型 /** * 数据类型: number(包含整数、小数) * string&#xff08;字符串类型&#xff09; * boolean&#xff08;布尔类型&#xff09; * object&#xff08;对象类型&#xff09; * function&#xff08;函数类型&#xff09; …

Roxlabs代理服务:智能化数据采集的加速器

TOC 一、引言 在这个数据驱动的时代&#xff0c;无论是企业还是个人&#xff0c;对于准确、及时的信息获取都有着前所未有的需求。网络数据采集已成为洞察市场趋势、分析竞争对手动态、优化营销策略的关键手段。然而&#xff0c;面对全球范围内的网站和服务&#xff0c;如何高…

【美团笔试题汇总】2024-03-30-美团春招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新美团近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

使用python将pdf插入到docx中

from pdf2image import convert_from_path from docx import Document from docx.shared import Inches,Cm# 将PDF转换为图片 pages convert_from_path(4.pdf, 200) # 200是DPI&#xff0c;可以根据需要调整doc Document()# 计算图片在docx中应该显示的宽度 img_width Cm(2…

【文献分享】 机器学习 + 分子动力学 + 第一性原理计算 + 热力学性质(熔化温度 热导率 热膨胀系数)

分享一篇关于机器学习 分子动力学 第一性原理 熔化温度&#xff08;熔化温度 & 热导率 & 热膨胀系数&#xff09;的文章。 感谢论文的原作者&#xff01; 关键词&#xff1a; 1. Al−Li alloy 2. Neural network potential 3. Molecular dynamics 4. Thermal pr…

二维码门楼牌管理应用平台建设:三维白模数据建设的意义

文章目录 前言一、三维白模数据建设的意义二、二维码门楼牌管理系统的构建三、二维码门楼牌管理系统的优势四、面临的挑战与未来展望 前言 随着城市管理的精细化和智能化需求日益增强&#xff0c;二维码门楼牌管理应用平台的建设成为推动城市管理现代化的重要手段。本文将探讨…

【漏洞复现】WordPress Plugin LearnDash LMS 敏感信息暴漏

漏洞描述 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 WordPress Plugin LearnDash LMS 4.10.2及之前版本存在安全漏洞&#x…

labelme的安装与使用以及如何将labelme标注的json格式关键点标签转为yolo格式的标签

有任何问题我们一起交流&#xff0c;让我们共同学习 标注的json格式以及转换后的yolo格式示例希望得到您的指导背景及代码可用范围一、yolo关键点检测数据集格式二、labelme的安装和使用&#xff08;一&#xff09;labelme的安装&#xff08;二&#xff09;labelme的使用 三、j…

Unity-C#进阶——3.27更新中

文章目录 数据结构类ArrayListStackQueueHashtable 泛型泛型类、泛型方法、泛型接口ListDictionaryLinkedList泛型栈&#xff0c;泛型队列 委托和事件委托事件匿名函数Lambad 表达式**闭包** List 排序逆变协变多线程进程线程多线程方法&#xff1a;线程之间共享数据&#xff1…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《强沙尘暴下新能源基地的弹性评估及其提升方法 》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

mysql 条件/系统/加密/其它函数

学习了日期时间函数&#xff0c;接着学习条件、系统、加密和其它函数。 3&#xff0c;条件判断函数 条件判断函数也称为控制流程函数&#xff0c;根据满足的条件的不同&#xff0c;执行相应的流程。MySQL中进行条件判断的函数有IF、IFNULL和 CASE。 函数 说明 IF(expr,v1,v2…

单例设计模式(3)

单例模式&#xff08;3&#xff09; 实现集群环境下的分布式单例类 如何理解单例模式中的唯一性&#xff1f; 单例模式创建的对象是进程唯一的。以springboot应用程序为例&#xff0c;他是一个进程&#xff0c;可能包含多个线程&#xff0c;单例代表在这个进程的某个类是唯一…

跨境电商IP防关联是什么?有什么作用?

做跨境电商的朋友应该都知道IP防关联这个词,那么为何IP需要防关联呢&#xff1f;今天为大家来解答这个问题。 跨境电商IP防关联是指在跨境电商运营中&#xff0c;通过采取一系列技术手段&#xff0c;确保每个跨境电商账号使用独立的IP地址&#xff0c;以避免账号之间因为IP地址…

【Linux实践室】Linux用户管理实战指南:用户权限切换操作详解

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 &#x1f514;图形化界面登录2.2 &#x1f514;使用login…