【C++心愿便利店】No.6---C++之拷贝构造函数

news2024/11/24 19:50:36

文章目录

  • 一、拷贝构造函数的引入
  • 二、拷贝构造函数


在这里插入图片描述

👧个人主页:@小沈YO.
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:C++ 心愿便利店
🔑本章内容:拷贝构造函数
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


一、拷贝构造函数的引入

对于上章节的学习我们认识并了解了两大默认成员函数:构造函数和析构函数。构造函数主要用来进行对象的成员变量初始化操作,而析构函数主要用来对战斗后的战场做清理工作。当我们不写这些函数时,编译器会自动生成默认的构造与析构函数,但有时候,编译器生成的并不能满足我们对代码的需求,这就需要我们自己去写了(比如Stack类),所以要根据情况的不同而去选择性的写。
此外就引入一个问题,假设我们需要创建一个对象和已经存在的对象一模一样那应该怎么办呢?显然易见的答案就是拷贝,但真的只是简简单单的拷贝吗?通过对比如下两种类的拷贝:

1. 以日期类为例:进行的值拷贝是不会发生错误的
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream> 
#include<assert.h>
using namespace std;
class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Printf()
	{
		cout << _year<<"/" << _month << "/" << _day << endl;
	}
private:
	int _year = 1;
	int _month;
	int _day;
};

void func1(Date d)
{
	d.Printf();
}


int main()
{
	Date d1(2023, 9, 12);
	func1(d1);
	return 0;
}

请添加图片描述

2. 以栈类为例:进行的值拷贝会发现发生错误
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream> 
#include<assert.h>
using namespace std;

class Stack
{
public:
	Stack(size_t n = 4)
	{
		cout << "Stack(size_t n=4)" << endl;
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if (a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			top = 0;
			capacity = n;
		}
	}
	void Init()
	{
		a = nullptr;
		top = capacity = 0;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp == a)
			{
				cout << capacity << "原地扩容" << endl;
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
	int Top()
	{
		return a[top - 1];
	}
	void Pop()
	{
		assert(top > 0);
		--top;
	}
	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
	bool Empty()
	{
		return top == 0;
	}
private:
	int* a;
	int top;
	int capacity;
};


void func2(Stack s)
{
	
}

int main()
{
	Stack s1;
	func2(s1);
	return 0;
}

请添加图片描述

报错原因:

🌟同样的拷贝方式为什么对于日期类不会报错,而对于栈类就会报错呢?
请添加图片描述

解决方式:
  • 采用引用
void func2(Stack& s)
{
	引用没有值拷贝的问题,s就是s1别名,没有两个对象指向同一块空间的这种说法(这是一个对象)
}

请添加图片描述
🌟这里有一个误解:采用引用它不是也会析构吗? —> 同一个对象不会析构两次,s是s1的别名,s不析构,不调用析构函数

但是采用引用,s的修改也会影响s1,那如何让s改变且不影响s1?这就需要引入拷贝构造函数

二、拷贝构造函数

1. 拷贝构造函数的概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎
请添加图片描述

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

2. 拷贝构造函数的特征

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

  • 🌏拷贝构造函数是构造函数的一个重载形式。
  • 🌏拷贝构造函数的参数只有一个必须是同类型对象的引用使用传值方式编译器直接报错,因为会引发无穷递归调用
#include<iostream> 
using namespace std;
class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		cout << "Date(Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Printf()
	{
		cout << _year<<"/" << _month << "/" << _day << endl;
	}
private:
	int _year = 1;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	以下两种写法是等价的
	Date d2(d1);  调用拷贝构造
	Date d3 = d1;   调用拷贝构造
	定义了一个日期类对象d1,然后想再创建一个和d1一模一样的日期类对象d2,也就是用d1去拷贝d2
	return 0;
}

😽 注意—>拷贝构造的错误写法:引发无穷递归

对于下述代码按常规理解就是创建d2对象的时候,把d1传过去,然后用形参d接收,再把d的值赋值给this指针(this指针指向的是d2,也就是赋值给了d2)并不会发现有任何错误

//Date d2(d1);
Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

其实编译器是会报错因为底层发生了错误

在这里插入图片描述
😽根据下述图解来探索一下引发无穷递归的原因
在这里插入图片描述
如上图所示,执行date d2(d1);调用拷贝构造函数, d1传参给拷贝构造的形参d, 形参d在接收实参d1的时候,又要去调用拷贝构造来创建d,所以会出现 date d(d1),而拷贝的过程中又会调用自身的拷贝构造函数,会无休止的递归下去。
😽 对于上述的错误可以采用下述方法进行规避:
采用引用的方法:Date d2(d1);调用拷贝构造,d1传给了d,且d是d1的别名,this指针就是d2,这样d1就拷贝给了d2,此时就不会再去无穷无尽的调用拷贝构造

//Date d2(d1);
Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
拷贝构造函数针对自定义类型,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数
  • 🌏若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
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类生成一个默认的拷贝构造函数
	
	Date d2(d1);
	return 0;
}

在这里插入图片描述
😽注意:建议写拷贝构造函数时加上const

Date(const Date& d)//d是d1的别名,权限缩小
	{
		cout << "Date(Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

表明拷贝构造函数中没有对传递进来的对象做任何修改,也是防止拷贝构造函数对对象进行修改,实际上不加const也是可以照常运行的。不过还是建议:不需要改变对象时,传引用时加上const

  • 🌏编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类(浅拷贝)这样的类是没必要的。但是对于栈类(深拷贝)的对象,是必须要显示写的,不然会出现析构两次的问题
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;
}

在这里插入图片描述

  • s1对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间然后里面存了4个元素12 3 4
  • s2对象使用s1拷贝构造,而Stack类没有显式定义拷贝构造函数,则编译器会给Stack类生成一份默认的拷贝构造函数,默认拷贝构造函数是按照值拷贝的,即将s1中内容原封不动的拷贝到s2中。因此s1和s2指向了同一块内存空间。
  • 当程序退出时,s2和s1要销毁。s2先销毁,s2销毁时调用析构函数,已经将0x11223344的空间释放了,但是s1并不知道,到s1销毁时,会将0x11223344的空间再释放一次,一块内存空间多次释放,肯定会造成程序崩溃。

😽注意:
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
😽综上所述栈类是要自己写拷贝构造的具体如下:
深拷贝就是去堆上重新申请一块空间,把s1中_array指向的空间中的内容,拷贝到新申请的空间,再让s2中的_array指向该空间

	Stack(const Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
		//深拷贝
		_array = (DataType*)malloc(sizeof(DataType) * s._capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_array, s._array, sizeof(DataType)*s._size );
		_size = s._size;
		_capacity = s._capacity;
	}

😽总结:
我们不写,编译默认生成的拷贝构造,跟之前的构造函数特性不一样:

  • 内置类型,值拷贝(浅拷贝)
  • 自定义的类型,调用他的拷贝
  • Date不需要我们实现拷贝构造,默认生成就可以用
  • Stack需要我们自己实现深拷贝的拷贝构造,默认生成会出问题(析构两次)
4. 拷贝构造函数的典型调用场景
  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
class Date
{
public:
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}
	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
Date Test(Date d)
{
	Date temp(d);
	return temp;
}
int main()
{
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

在这里插入图片描述
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。


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

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

相关文章

python安全工具开发笔记(四)——python网络编程

一、C/S架构 什么是C/S架构 C : Client S : Server。客户机和服务器结构。 Server 唯一的目的就是等待Client 的请求&#xff0c;Client 连上 Server 发送必要的数据&#xff0c;然后等待Server端完成请求的反馈。 C/S网络编程 Server端进行设置&#xff0c;首先创建一个通信…

怎么选择AI伪原创工具-AI伪原创工具有哪些

在数字时代&#xff0c;创作和发布内容已经成为了一种不可或缺的活动。不论您是个人博主、企业家还是网站管理员&#xff0c;都会面临一个共同的挑战&#xff1a;如何在互联网上脱颖而出&#xff0c;吸引更多的读者和访客。而正是在这个背景下&#xff0c;AI伪原创工具逐渐崭露…

ReadPaper论文阅读工具

之前看文献一直用的EndNote嘛&#xff0c;但是突然发现了它的一个弊端&#xff0c;就是说每次没看完退出去之后&#xff0c;下次再接着看的时候它不能保留我上一次的位置信息&#xff0c;又要重头开始翻阅&#xff0c;这让我感到很烦躁哈哈。&#xff08;当然也不知道是不是我哪…

6条优势,anzo capital昂首资本相信MT5替代MT4的原因

投资者都知道MT5是在MT4基础上升级换代的多资产平台&#xff0c;MT5于2010年6月首次发布。anzo capital昂首资本认为MT5将完全取代MT4&#xff0c;就像MT4取代之前版本一样&#xff0c;因为有以下6条优势&#xff1a; 一.市场深度(DOM)数据。在MT4中&#xff0c;DOM几乎没有用…

GoAccess实时分析Nginx日志

GoAccess 是一个基于终端的实时 Web 日志分析仪。用 C 语言编写&#xff0c;它是快速&#xff0c;互动的&#xff0c;并以优雅而直观的方式显示日志。它提供了各种 Web 日志文件的支持&#xff0c;包括 Apache&#xff0c;Nginx&#xff0c;Caddy&#xff0c;Amazon S3 和 Clou…

c++STL概述

目录 STL基本概念 STL六大组件 STL的优点 STL三大组件 容器 算法 迭代器 普通的迭代器访问vector容器元素 算法for_each实现循环 迭代器指向的元素类型是自定义数据类型 迭代器指向容器 常用容器 string容器 string的基本概念 string容器的操作 string的构造函…

Python函数绘图与高等代数互融实例(六): 条形图|直方图|饼状图

Python函数绘图与高等代数互融实例(一):正弦函数与余弦函数 Python函数绘图与高等代数互融实例(二):闪点函数 Python函数绘图与高等代数互融实例(三):设置X|Y轴|网格线 Python函数绘图与高等代数互融实例(四):设置X|Y轴参考线|参考区域 Python函数绘图与高等代数互融实例(五…

代码随想录刷题 Day 16

104.二叉树的最大深度 class Solution { public:int get_max(TreeNode* root) {if(root NULL) return 0;int left_depth get_max(root->left);int right_depth get_max(root->right);int depth max(left_depth, right_depth) 1;return depth;}int maxDepth(TreeNod…

Kafka的消息传递保证和一致性

前言 通过前面的文章&#xff0c;相信大家对Kafka有了一定的了解了&#xff0c;那接下来问题就来了&#xff0c;Kafka既然作为一个分布式的消息队列系统&#xff0c;那它会不会出现消息丢失或者重复消费的情况呢&#xff1f;今天咱们就来一探。 实现机制 Kafka采用了一系列机…

Node2Vec实战---《悲惨世界》人物图嵌入

1. pip各个包后导入 import networkx as nx # 图数据挖掘 import numpy as np # 数据分析 import random # 随机数# 数据可视化 import matplotlib.pyplot as plt %matplotlib inline plt.rcParams[font.sans-serif][SimHei] # 用来正常显示中文标签 plt.rcParams[axes.uni…

ArtifactResolveException

bug描述 Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all files for configuration :app:debugCompileClasspath. 产生原因 一般可能是更换了新AndroidStudio导致的。依赖库未能成功…

关于Safari浏览器报错:Failed to load resource: 发生SSL错误,无法建立到该服务器的安全连接

报错信息&#xff1a; Failed to load resource: 发生SSL错误&#xff0c;无法建立到该服务器的安全连接 XMLHttpRequest cannot load https://xxxxxxx due to access control checks. 具体如图下&#xff1a; 原因是&#xff1a;页面上的http请求变了https请求 解决办法…

【校招VIP】产品思维创意之活动推广

考点介绍&#xff1a; 对于活动新手来说&#xff0c;策划出一个活动创意不是难事。但是如何把做活动考虑周详&#xff0c;理清运营活动的思路和流程&#xff0c;避免重复工作却是需要沉淀的。运营活动有一个很重要的思路方法&#xff0c;就是倒推。九维这次来和你们讲述倒推的思…

day31多线程01

1.实现多线程 1.1简单了解多线程【理解】 是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程&#xff0c;提升性能。 1.2并发和并行【理解】 并行&#xff1a;在同一时刻&#xff0c;有多个指令在多个CPU上…

大模型时代,探人工智能发展的新动向

导语 | 今年以来大模型的热度居高不下&#xff0c;人工智能成为国内外各大厂商争相布局的新赛道。那么近期 AI 领域有哪些值得关注的新趋势&#xff0c;它又将为软件开发带来哪些影响呢&#xff1f;今天&#xff0c;我们特邀了微智云科技 CEO、腾讯云 TVP 张虎老师&#xff0c;…

HR人才测评,什么是领导力?如何测评人的领导能力?

什么是领导力&#xff1f; 领导力指的是带领和组织团队&#xff0c;充分利用各种条件和资源&#xff0c;为团队目标而努力。具有卓越领导力的人&#xff0c;即使是在困难的条件下&#xff0c;也能充分利用条件&#xff0c;激励成员&#xff0c;提高团队的效率&#xff0c;朝着…

数据备份文件生成--根据表名生成对应的sql语句文件

最近客户有个需求&#xff0c;希望在后台增加手动备份功能&#xff0c;将数据导出下载保存。 当然&#xff0c;此方法不适用于海量数据的备份&#xff0c;这只适用于少量数据的sql备份。 这是我生成的sql文件&#xff0c;以及sql文件里的insert语句&#xff0c;已亲测&#x…

Software Grand Exposure: SGX Cache Attacks Are Practical【WOOT‘17】

目录 摘要引言我们的目标和贡献贡献新颖的SGX缓存攻击技术非加密应用程序泄漏对策分析 背景Intel SGX缓存结构性能监视计数器 系统和威胁模型对手的能力进攻目标 攻击设计PrimeProbePrimeProbe for SGX挑战 作者&#xff1a;Ferdinand Brasser, Urs M ̈uller, Alexandra Dmitr…

2023 第十二届中国智能产业高峰论坛 - 文档大模型的未来展望

目录 前言文档图像分析识别与理解中的技术挑战 文档图像分析识别与理解的研究主题文档图像分析与预处理文档解析与识别版面分析与还原文档信息抽取与理解AI安全知识化&存储检索和管理 多模态大模型在文档图像处理中的应用多模态的GPT-4在文档图像上的表现多模态的Google Ba…

Spring Security :二【原理解析、会话管理、RBAC中集成认证和授权、JWT】

文章目录 三、原理解析3.1 结构分析3.1 登录认证流程分析3.1.1 **UserDetailsService**3.1.2 自定义UserDetailsService3.1.3 **PasswordEncoder** 3.2 授权流程分析3.2.1 配置方式的原理解析3.2.2 注解方式原理解析 四、会话管理4.1 获取用户身份4.2 会话控制 五、 RBAC中集成…