【C++类和对象】类和对象(中):拷贝构造函数 {拷贝构造函数的概念及特征,拷贝构造函数不能使用传值传参,编译器自动生成的拷贝构造函数}

news2024/10/6 4:12:36

四、拷贝构造函数

4.1 概念

在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用


4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 函数名和类名相同,没有返回值。
  3. 可以使用函数法或赋值法调用拷贝构造函数。
  4. 拷贝构造函数的参数只有一个且必须是同类类型对象的引用,而且一般用const修饰以限制引用权限,防止误操作修改拷贝源的属性。
  5. 如果使用传值传参的方式编译器直接报错,因为会引发无穷递归调用。

实现日期类的拷贝构造函数

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// Date(Date d) // 错误写法:编译报错,会引发无穷递归
	Date(const Date& d) // 正确写法
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	//写法一:函数法
	Date d2(d1);//使用d1拷贝构造d2
	//写法二:赋值法
	Date d2 = d1;
	return 0;
}

4.3 拷贝构造函数不能使用传值传参

传值传参的底层是在栈中开辟空间拷贝参数的值。如果拷贝构造函数的参数是同类类型对象的值,那么实例化形参就又要调用拷贝构造。这样就形成了死递归。

注意:类对象传值传参传值返回都会调用拷贝构造函数构造临时对象(出作用域还要析构)。由此可以看出,传引用比传值更高效尤其对于自定义类型,传值不仅要开空间(尤其对于深拷贝)还要调用拷贝构造函数和析构函数(空间时间消耗)代价更大。

在这里插入图片描述

提示:还可以传同类型对象的指针实现拷贝的功能,但要注意的是使用指针实现的函数不是拷贝构造函数(不符合语法);同时指针实现的拷贝函数在使用起来效果也不如引用。如:Date d2(&d1); Date d2 = &d1;


4.4 编译器自动生成的拷贝构造函数

若未显式定义,编译器会生成默认的拷贝构造函数。 默认生成的拷贝构造函数对于内置类型是逐字节拷贝的(aka 浅拷贝 or 值拷贝),而自定义类型是调用其拷贝构造函数完成拷贝的。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	// 对于内置类型(_hour,_minute等)是按照字节方式直接拷贝的,而自定义类型(Time _t)是调用其拷贝构造函数完成拷贝的。
	Date d2(d1);
	return 0;
}

编译器自动生成的拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然,因为默认生成的拷贝构造函数不能解决深拷贝的问题

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType *_array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);//调用的是默认生成的拷贝构造函数,进行值拷贝
	return 0;
}

编译运行上面的代码发现程序崩掉了,程序为什么会崩溃掉呢?
在这里插入图片描述

  • 注意:类中如果没有涉及资源申请时,拷贝构造函数写不写都可以,像Date类;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,像Stack类。

  • 此处默认的浅拷贝出现的问题:

    1. 由于两个对象中的指针指向同一块空间,一个对象修改会影响另外一个对象。
    2. 函数返回,调用析构函数时,对同一块内存空间free两次造成程序崩溃。

解决方法:自己显示实现深拷贝

再看下面这个例子:

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	Stack(const Stack& st){
	//此处实现栈结构的深拷贝
	//........
	}
	//其他方法的实现....

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType *_array;
	size_t _size;
	size_t _capacity;
};

class MyQueue{
//对于内置类型进行值拷贝
int sz = 0;
//对于自定义类型调用它们的拷贝构造
Stack output;
Stack input;
};

int main()
{
	MyQueue mq1;
	MyQueue mq2 = mq1;
	// 像MyQueue类型这种未直接涉及资源申请的类可以不写拷贝构造,
	// 但前提是其自定义类型成员中涉及资源申请的类实现了深拷贝。
	return 0;
}

像MyQueue类型这种未直接涉及资源申请的类可以不写拷贝构造,但前提是其自定义类型成员中涉及资源申请的类实现了深拷贝

总结:

  • 涉及资源申请的类需要显示的写拷贝构造,以实现类的深拷贝。比如:Stack,Queue
  • 未涉及资源申请的类不需要写拷贝构造,默认生成的就会完成类的值拷贝/浅拷贝。比如:Date
  • 未直接涉及资源申请的类也不需要写拷贝构造,默认生成的就会调用其自定义类型成员的拷贝构造函数。比如:Myqueue

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

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

相关文章

MySQL高级(二)

一、SQL优化 &#xff08;一&#xff09;插入数据 批量插入 多次插入每一次insert都要与数据库建立连接。 INSERT INTO 表名 VALUES (),(),(); 一次插入数据不宜过多&#xff0c;不要超过1000条。 手动提交事务 START TRANSACTION; INSERT INTO 表名 VALUES (),(),(); I…

车载以太网 - SomeIP - 协议用例 - Format_01

目录 1、验证Client ID字段静态设置为0x0000 2、验证Session ID字段静态设置为0x0001 3、验证Protocol Version字段静态设置为0x01

SpringCloud:ElasticSearch之自动补全

当用户在搜索框输入字符时&#xff0c;我们应该提示出与该字符有关的搜索项&#xff0c;如图&#xff1a; 这种根据用户输入的字母&#xff0c;提示完整词条的功能&#xff0c;就是自动补全了。 因为需要根据拼音字母来推断&#xff0c;因此要用到拼音分词功能。 1.拼音分词器…

【移动端网页布局】移动端网页布局基础概念 ④ ( 物理像素 | 物理像素比 | 代码示例 - 100 像素在 PC浏览器 / 移动端浏览器 显示效果 )

文章目录 一、物理像素 / 物理像素比二、代码示例 - 100 像素在 PC浏览器 / 移动端浏览器 显示效果 一、物理像素 / 物理像素比 移动端 网页开发 与 PC 端开发有很多不同之处 , 在图片处理方向需要采用 二倍图 / 三倍图 / 多倍图 方式进行图片处理 ; 图片处理的方式与如下的 物…

项目支付接入支付宝【沙箱环境】

前言 订单支付接入支付宝&#xff0c;使用支付宝提供的沙箱机制模拟为订单付款。我这里主要记录一下沙箱环境如何接入到系统中&#xff0c;具体细节的实现。按照官方文档来就可以了。 1、使用步骤 这里有几个重要数据要拿到&#xff0c;一个是支付宝的公钥和私钥&#xff0c…

ClickHouse监控系统Prometheus+Grafana

目录 1 PrometheusGrafana概述2 安装Prometheus Grafana3 配置ClickHouse4 配置Grafana 1 PrometheusGrafana概述 ClickHouse 运行时会将一些个自身的运行状态记录到众多系统表中( system.*)。所以我们对于 CH 自身的一些运行指标的监控数据&#xff0c;也主要来自这些系统表。…

docoker笔记

0.安装Docker Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道…

RabbitMQ【#1】是什么,有什么用

RabbiMQ是什么&#xff1f; RabbitMQ是一种开源的消息队列软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;并支持多种编程语言。它可以用于将消息从一个应用程序传递到另一个应用程序或进程&#xff0c;并支持分布式系统中的异步消息通信。RabbitMQ的主…

【Linux】System V 共享内存、消息队列、信号量

&#x1f34e;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;Linux系统编程 system V共享内存介绍 System V 共享内存是一种进程间通信的机制&#xff0c;它允许多个进程共享一块物理内存区域&#xff08;称为“段”&#xff09;。System V 共享内存的优点是效率高&…

AD21 PCB----过滤、捕获、板子边框绘制、精准移动

目录 过滤器和捕获 板子边框绘制 精准移动 过滤器和捕获 板子边框绘制 两种方式均在Mechanical 1 方式一&#xff1a; 第一步&#xff1a;利用PCB的基础图形进行绘制边框 第二步&#xff1a;选中绘制的图形 第三步&#xff1a; 方式二&#xff1a;外部导入 第一步&#x…

SpringCloud 微服务系列——【服务间的通信方式、OpenFeign、Hystrix组件使用】

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

【UE 粒子系统】电火花粒子效果

效果 步骤 1. 新建一个粒子系统&#xff0c;命名为“SparkParticles” 再新建一个材质&#xff0c;命名为“SparkParticleMaterial” 2. 打开“SparkParticleMaterial”&#xff0c;将混合模式改为半透明&#xff0c;着色模型为无光照 然后添加如下节点 3. 打开“SparkParticl…

输入输出练习

文章目录 1. AB(1)2. AB(2)3. AB(3)4. AB(4)计算一系列数的和5. AB(5) 计算一系列数的和6. AB(6)7. AB(7)8. 字符串排序(1)9. 字符串排序(2)10 字符串排序(2)11. 注意数据范围 1. AB(1) import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public …

差分运算放大电路原理解析

差分运算放大电路&#xff0c;对共模信号得到有效抑制&#xff0c;而只对差分信号进行放大&#xff0c;因而得到广泛的用。 注&#xff1a; &#xff08;1&#xff09;共模信号   共模信号&#xff08;common mode signal&#xff09;是指同时作用于多个电路或电子设备上的信…

1.10和1.11和1.12、Makefile

1.10和1.11和1.12、Makefile 1.10、Makefile(1)1.10.1、什么是Makefile1.10.2、Makefile的文件命名和规则实际操作 1.11、Makefile(2)1. 工作原理&#xff08;1.10.3&#xff09;实际操作 1.12、Makefile(3)1. 变量2. 模式匹配3. 函数实际操作①实现变量和模式匹配②实现函数操…

Vue(简介、前期配置、Vue展示、模板语法)

一、简介 1. 什么是Vue&#xff1f; 2. Vue特点 采用组件化模式&#xff0c;提高代码复用率、且让代码更好维护 组件化&#xff1a;每一部分直接就是大盒子组件&#xff08;创建一个单独的Vue文件&#xff09;&#xff0c;直接可以修改单独封装的组件部分代码 Vue使用声明式…

为什么需要内网穿透技术?

随着互联网技术的快速发展&#xff0c;企业和个人越来越依赖于网络资源&#xff0c;而内网穿透技术正是解决远程访问内网资源的关键。本文将详细介绍内网穿透的概念及其重要性&#xff0c;以帮助您了解为什么我们需要使用内网穿透技术。 目录 一、内网穿透技术简介 二、为什…

Java中List排序的3种方法

在某些特殊的场景下&#xff0c;我们需要在 Java 程序中对 List 集合进行排序操作。比如从第三方接口中获取所有用户的列表&#xff0c;但列表默认是以用户编号从小到大进行排序的&#xff0c;而我们的系统需要按照用户的年龄从大到小进行排序&#xff0c;这个时候&#xff0c;…

2 变量运算符-基本数据类型讲解【Go语言教程】

2 变量运算符-基本数据类型讲解【Go语言教程】 2.1 变量 2.1.1 声明变量方式 指定变量类型&#xff0c;声明后若不赋值&#xff0c;使用默认值 类型推导 通过: 多变量声明 全局变量定义 在函数外部定义的就是全局变量 变量变量名值数据类型 注意&#xff1a;如果go程序报错&…

UE4/5多人游戏详解(七、自定义委托,实现寻找会话和加入会话的函数,通过Steam进行两台电脑的联机)

目录 可能出现问题&#xff08;在六部分的测试可能无法连接的问题【在末尾加上了&#xff0c;怕有人没看见在这里写一下】&#xff09; 自定义委托 调整位置 创建更多的委托和回调函数给菜单&#xff1a; 多播和动态多播 代码&#xff1a; 委托变量 代码&#xff1a; 回…