C++类与对象(二)——拷贝构造函数

news2025/1/16 6:01:50

文章目录

  • 拷贝构造函数
    • 1.拷贝构造函数的概念
    • 2.拷贝构造函数的特性

前言

本章继续学习类的6个默认成员函数——拷贝构造函数

拷贝构造函数

1.拷贝构造函数的概念

拷贝构造函数:使用已经存在的一个对象初始化创建另一个对象。

举例:

class Date
{
public:
	// 构造函数
	Date()
	{
		cout << "Date()" << endl;
	}
	// 拷贝构造函数
	Date(const Date& d)
	{
		cout << "Date(&)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 0;
	int _month = 0;
	int _day = 0;
};

void TestDate()
{
	Date d1;
	Date d2(d1);
}

int main()
{
	TestDate();
}

运行结果:

在这里插入图片描述

2.拷贝构造函数的特性

  1. 拷贝构造函数是构造函数的重载
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用。若使用传值的方式,则编译器会报错,因为理论上这会引发无穷递归

错误示例:

class Date
{
public:
	// 错误示例:
	// 这样写,编译器就会报错,会引发无穷递归
	Date() {};
	Date(const Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year = 0;
	int _month = 0;
	int _day = 0;
};

void TestDate()
{
	Date d1;
	Date d2(d1);
}
  • 当拷贝构造函数的参数采用传值的方式时,创建对象d2,会调用它的拷贝构造函数d1会作为实参传递给形参d。但是,实参传递给形参本身又是一个拷贝,会再次调用形参的拷贝构造函数……,因此会引发无穷递归。
    在这里插入图片描述

3.若显示未定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数按对象内存存储字节序完成拷贝,这种拷贝叫做浅拷贝值拷贝

示例:

class Date
{
public:
	// 错误示例:
	// 这样写,编译器就会报错,会引发无穷递归
	Date(int year=0,int month=0,int day=0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 未显示定义的拷贝构造函数
	//Date(const Date& d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year = 0;
	int _month = 0;
	int _day = 0;
};

void TestDate()
{
	Date d1(2023,5,3);
	Date d2(d1);
	d2.print();
}

int main()
{
	TestDate();
	return 0;
}

运行结果:

在这里插入图片描述

注意:

  • 在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
    义类型是调用其拷贝构造函数
    完成拷贝的。

4.类中如果没有涉及资源申请时,拷贝构造函数写不写都可以,一旦涉及资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

错误示例:

class stack
{
public:
	stack(int defaultCapacity = 10)
	{
		_a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		_top = 0;
		_capacity = defaultCapacity;
	}
	~stack()
	{
		cout << "~stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
	void push(int n)
	{
		_a[_top++] = n;
	}
	void print()
	{
		for (int i = 0; i < _top; i++)
		{
			cout << _a[i] << " ";
		}
		cout << endl;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

void TestStack()
{
	stack s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	s1.push(4);
	s1.print();

	stack s2(s1);
	s2.print();
	s2.push(5);
	s2.push(6);
	s2.push(7);
	s2.push(8);
	s2.print();

}

int main()
{
	TestStack();
	return 0;
}

运行结果:

在这里插入图片描述

如图所示:程序崩溃了,经过排查,我们发现是在第二次析构的时候出现了错误:在第二次析构的时候对野指针进行了free

那为什么会出现对野指针进行free呢?

  • 对象s1与对象s2中的成员_a,指向的是同一块内存空间。 在s2析构完成后,这块空间已经被释放,此时的s1._a就是野指针。这也就是浅拷贝导致的后果。

  • 多个对象进行析构的顺序如同一样,先创建的对象后析构,后创建的对象先析构

浅拷贝?? 🤔

编译器默认生成的拷贝构造函数是按字节序拷贝的,在创建对象s2时,仅仅是把s1._a的值赋给s2._a并没有重新开辟一块与s1._a所指向的空间大小相同且内容相同的空间。我们把前者的拷贝方式称为浅拷贝后者称为深拷贝

在这里插入图片描述
当打开监视窗口来观察整个过程时,我们发现s1._as2._a指向的是同一块空间,内容是相同的:
在这里插入图片描述
正确的做法应该是:

class stack
{
public:
	stack(int defaultCapacity = 10)
	{
		_a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		_top = 0;
		_capacity = defaultCapacity;
	}
	stack(const stack& s)
	{
		_a = (int*)malloc(sizeof(int) * s._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, s._a, sizeof(int) * s._capacity);
		_top = s._top;
		_capacity = s._capacity;
	}
	~stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
	void push(int n)
	{
		_a[_top++] = n;
	}
	void print()
	{
		for (int i = 0; i < _top; i++)
		{
			cout << _a[i] << " ";
		}
		cout << endl;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

5.拷贝构造函数典型的使用场景

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用以用尽量使用引用


本文到此结束,码文不易,还请多多支持哦!!

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

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

相关文章

Golang每日一练(leetDay0056) 单个编辑距离、寻找峰值

目录 161. 单个编辑距离 One Edit Distance &#x1f31f;&#x1f31f; 162. 寻找峰值 Find Peak Element &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 …

vmware 安装Kylin-Desktop-V10-SP1-General-Release-2203-X86_64.iso

下载 官网&#xff1a;国产操作系统、银河麒麟、中标麒麟、开放麒麟、星光麒麟——麒麟软件官方网站 (kylinos.cn) 点击桌面操作系统 选择No1 点击申请试用 填写相关信息&#xff0c;点击立即提交&#xff0c;就会获取到下载连接&#xff0c; 点击下载按钮等待下载完成即可 安…

Java中顺序表详解

前言 在Java编程中&#xff0c;顺序表是一种基础且重要的数据结构。它通常用来表示线性结构数据&#xff0c;如数组等。通过使用顺序表&#xff0c;我们可以轻松管理和操作大量的数据&#xff0c;并实现各种算法和功能。本篇博客将详细介绍Java中顺序表相关的原理、方法和实例&…

BRDF

文章目录 0. 写在前面1. 简单理解2. Cook-Torrance BRDF模型2.1 BRDFD 法线分布函数F 菲涅尔G 几何函数1.1.3 L i ( p , ω i ) L_i(p,\omega_i) Li​(p,ωi​)1.1.1 积分框架1.1.2 f r ( p , ω i , ω o ) f_r(p,\omega_i,\omega_o) fr​(p,ωi​,ωo​): 个人疑惑及解答1.…

ESL设计概述

‍‍ ‍‍前言 随着芯片面临着应用场景丰富多变、集成功能模块越来越多、片内通信及模块间接口越来越复杂、设计规模越来越大以及PPA要求越来越高的需求&#xff0c;芯片设计方法面临越来越大的挑战。架构的合理性、完备性和一致性很大程度上决定了芯片设计的成败。基于同样的I…

《基于光电容积图法的两种可穿戴设备在不同身体活动情况下监测心率的一致性:一种新的分析方法》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课…

电容笔有必要买最好的吗?推荐的ipad手写笔

随着科技的进步&#xff0c;各种类型的电容笔的生产厂家越来越多。一支好的电容笔&#xff0c;不仅能大大提高我们的工作效率&#xff0c;而且能大大提高我们的学习效果。平替电容笔无论从技术水平&#xff0c;还是从产品品质来看&#xff0c;都具有十分广阔的市场前景。以下是…

redis基本数据类型及常见命令

数据库操作 select <库号>: 切换库 默认共有15个 dbsize: 查看当前库的key数量 flushdb: 清空当前库 flushall: 清空所有库 Key的操作 keys *&#xff1a; 查看当前库的所有key exists <key>: 判断该key是否存在 type <key>: 查看该key的类型 de <…

【原创】使用PowerShell配置新安装的ESXI主机

安装PowerCLI 模块 在线安装 优点&#xff1a;简单 缺点&#xff1a;太慢 启动PowerShell命令行&#xff0c;执行行如下命令 Install-Module -Name VMware.PowerCLI离线安装 先到VMware官网下载离线包&#xff0c;然后分几个步骤安装 https://developer.vmware.com/powercl…

redis5新增数据类型

Bitmaps 概念 &#xff08;1&#xff09; Bitmaps本身不是一种数据类型&#xff0c; 实际上它就是字符串&#xff08;key-value&#xff09; &#xff0c; 但是它可以对字符串的位进行操作。 &#xff08;2&#xff09; 可以把Bitmaps想象成一个以位为单位的数组&#xff0c; 数…

HIEE300024R4 UAA326A04什么是反馈和前馈控制系统?

​ HIEE300024R4 UAA326A04什么是反馈和前馈控制系统&#xff1f; 反馈控制系统&#xff1a; 反馈系统测量过程中的值并对测量值的变化做出反应。 在传感器的帮助下测量过程的输出&#xff0c;并将传感器值提供给控制器以采取适当的控制措施。控制器将此传感器信号与设定点进行…

人物专辑丨技术服务展计讯风采,助力客户显计讯担当

正所谓&#xff1a;平凡铸就伟大。一切令人赞叹的不凡&#xff0c;都来自于平凡点滴的坚守&#xff1b;一切砥砺前行的坚持&#xff0c;都来自于责任的担当。 在计讯物联高质量发展的进程中&#xff0c;不乏敢于担当、踏实勤恳、爱岗敬业的计讯人。他们扎根岗位&#xff0c;坚…

grafana-report在grafana7中遇到的问题

一、点击之后报错pdf报错&#xff1a;NO image renderer available/installed 查看grafana日志后&#xff0c;有以下报错&#xff1a; Could not render image, no image renderer found/installed. For image rendering support please install the grafana-image-renderer …

ThingsBoard使用jar包单机部署

1、概述 前面一节我讲了如何初始化数据库表结构以及默认的数据。这一节我将讲解如何使用jar包部署。 2、部署 2.1、修改thingsboard.yml配置 上一节我已经讲解了thingsboard.yml中的基础配置,基础的组件配置如何redis、kafka、Cassandra、pg等大家都知道,关键的地方是在于…

Neo4j图数据库的介绍_图数据库结构_节点_关系_属性_数据---Neo4j图数据库工作笔记0001

以前就知道这个了,也见别人用过,在大数据领域有可能会用到所以就看了一下. 其实就是用来,指定数据之间的关系,但是他这个更适合处理,数据之间的大规模的关系 可以看到图数据可以用到上面的这些领域 因为图数据库,更适合处理关系,基于数学中的图论 可以看到,因为如果关系太庞大…

pandas使用教程:pandas resample函数处理时间序列数据

文章目录 时间序列(TimeSeries)执行多个聚合 上采样和填充值通过apply传递自定义功能 DataFrame对象 时间序列(TimeSeries) #创建时间序列数据 rng pd.date_range(1/1/2012, periods300, freqS)ts pd.Series(np.random.randint(0, 500, len(rng)), indexrng) ts2012-01-01 0…

十二、MyBatis分页插件

文章目录 十二、分页插件12.1 分页插件使用步骤12.2 分页插件的使用12.3 测试案例 本人其他相关文章链接 十二、分页插件 12.1 分页插件使用步骤 1. 添加依赖 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</art…

Java基本数据类型以及包装类型的常量池技术

Java 中的基本数据类型 Java 中有 8 种基本数据类型&#xff0c;分别为&#xff1a; 6 种数字类型&#xff1a; 4 种整数型&#xff1a;byte、short、int、long2 种浮点型&#xff1a;float、double 1 种字符类型&#xff1a;char1 种布尔型&#xff1a;boolean。 这 8 种基本…

Socks5 代理协议:网络安全中的利器

随着网络的普及&#xff0c;网络安全问题已成为各行各业所面临的共同难题。为了保护自己的网络安全&#xff0c;不少人选择使用代理IP&#xff0c;其中 Socks5 代理协议因其安全性、灵活性等优势备受青睐。本文将介绍 Socks5 代理协议及其在网络安全中的作用。 一、什么是 Soc…

安卓手机搭建智能语音客服/通话播音/聊天播音乐技术实现

声明&#xff0c;此项技术需要root支持&#xff0c;如果因为刷机导致手机变砖或其他不可预料的后果请自行解决。 场景 我有一个朋友他是做业务的&#xff0c;主要还是做电销&#xff0c;其实电销相对于以前纪念没那么好做了&#xff08;我自己觉得主要是互联网冲击&#xff0c…