C++类和对象进阶:拷贝构造函数深度详解

news2025/2/19 4:36:43

拷贝构造函数

  • 拷贝构造函数
    • 前言
    • 引入
    • 拷贝构造函数
      • 特征
        • 拷贝构造函数建议参数加上const
      • 拷贝构造函数参数传值会引发无穷递归的解释
        • 内置类型传参拷贝
        • 自定义类型传参拷贝
        • 详细解释
      • 编译器生成的默认拷贝构造函数
        • 默认构造函数做了什么?
        • 深拷贝与浅拷贝
          • 简单实现一个深拷贝。
      • 深浅拷贝总结
    • 拷贝构造函数典型使用场景
      • 使用已存在的对象创建新对象。
      • 函数参数类型为类类型对象。
      • 函数返回值类型为类类型对象。
    • 总结需要实现拷贝构造的场景

拷贝构造函数

在这里插入图片描述

前言

上文中详解了构造函数析构函数,本文详解类的六个默认成员函数中的第三个拷贝构造函数,并辨析区分深拷贝与浅拷贝

引入

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
在这里插入图片描述
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

答案当然是有的, 拷贝构造函数就是来完成这一工作的。

拷贝构造函数

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

特征

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
//拷贝构造函数实例
class Date {
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year = 2024, int month = 10, int day = 28) {
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数实例
	Date(const Date& date) {	
		_year = date._year;
		_month = date._month;
		_day = date._day;
	}
};

int main(){
	Date d1(2025, 2, 8);
	Date d2(d1);	//调用拷贝构造函数,用已有对象初始化新的对象
}
拷贝构造函数建议参数加上const
Date(const Date& date) {	
	//const 是为了防止别人写错,防止别人写成以下代码
	date._year = _year;		//不仔细看真看不出来,赔了夫人又折兵,会出现随机值
	date._month = _month;
	date._day = _day;
}

在这里插入图片描述

  • 不加const可能会出现随机值。

拷贝构造函数参数传值会引发无穷递归的解释

解释前,我们需要明晰该概念: 函数调用前,需要先传参。
**C++**中传参时有如下要求:

  1. 内置类型,无要求,直接拷贝
  2. 自定义类型,值传参,必须调用其拷贝构造函数
    在这里插入图片描述
内置类型传参拷贝

C++内置类型拷贝

我们可以看到,

自定义类型传参拷贝

C++自定义类型拷贝

我们可以看到:

  • 内置类型作为参数时,调用函数会直接进入函数内。。
  • 自定义类型作为参数时,调用函数,会先调用其拷贝构造函数来拷贝。

此时再来看,若拷贝构造函数为值传递:

Date(const Date date) {		//下面解释会无穷递归调用
	_year = date._year;
	_month = date._month;
	_day = date._day;
}
详细解释

栈帧基本概念

  • 栈帧(Stack Frame): 当一个函数被调用时,程序会为这个函数分配一个栈帧,其中存放了函数的参数、局部变量、返回地址等信息
  • 函数调用和返回: 每当一个函数调用完成后,其对应的栈帧会被释放。如果函数不断调用自身(递归),每次调用都会在栈上分配一个新的栈帧。如果没有合适的终止条件,栈帧会不断累积,最终导致栈空间耗尽(栈溢出)。

在这里插入图片描述
执行语句Date d2(d1)时,流程如下:

  1. Date d2(d1),执行该语句,参数列表匹配,会调用其拷贝构造函数,构造函数会创建一个栈帧,栈帧内空间存放有形参date
  2. 形参date实参d1的一份拷贝,拷贝时会调用Date的拷贝构造函数,而拷贝构造函数Date(const Date date)参数为自定义类型的值传递,自定义类型的值传递,同样会创建栈帧并调用其拷贝构造函数
  3. 拷贝构造函数继续传值引发对象的拷贝,之后会层层传值引发对象的拷贝的递归调用

总结调用 Date d2(d1)时,新分配一个拷贝构造函数的栈帧,同时栈帧内创建参数的副本创建参数的副本时又不断分配新的栈帧,层层嵌套,无法返回,直至栈空间耗尽。

编译器生成的默认拷贝构造函数

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

默认构造函数做了什么?
  • 内置类型成员完成,,值拷贝/浅拷贝
  • 自定义类型,调用各自的拷贝构造函数,如果自定义类型没有拷贝构造函数,则值拷贝
//默认拷贝构造函数
class Date_1 {
private:
	int _year;
	int _month;
	int _day;
public:
	Date_1(int year = 2024, int month = 10, int day = 28) {
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	/*Date_1(const Date_1& date) {
		this->_year = date._year;
		this->_month = date._month;
		this->_day = date._day;
	}*/
};
int main() {
	Date_1 d1(2025, 6, 6);
	Date_1 d2(d1);
	return 0;
}

在这里插入图片描述
可以看到,编译器自动生成的默认拷贝构造函数,可以完成Date_1(类内均为自定义类型)的拷贝。

深拷贝与浅拷贝

再看如下代码:

class Stack {
private:
	int* _base = nullptr;	//C++11支持的成员变量缺省值
	int _top = 0;
	int _capacity = 0;
public:
	Stack(int defaultCapacity = 4) {
		this->_base = (int*)malloc(sizeof(int) * defaultCapacity);
		if (this->_base == nullptr) {
			perror("malloc failed\n");
			return;
		}
		this->_capacity = defaultCapacity;
		this->_top = 0;
	}
	~Stack() {
		cout << " ~Stack" << endl;
		free(this->_base);
		this->_base = nullptr;
		this->_capacity = 0;
		this->_top = 0;
	}
};
int main(){
	Stack st1;
	Stack st2(st1);
	return 0;	
}

运行结果如下:
在这里插入图片描述
这是为什么呢?原因就是浅拷贝的危害

在这里插入图片描述

  • 值拷贝(浅拷贝)存在问题,值拷贝时,由于只是简单的值拷贝,导致两个栈类对象中,两个指针存下了同一块空间的地址
    在这里插入图片描述

  • 析构函数完成的是对象中资源空间的清理和释放。可以看到,当两个栈对象的生命周期结束时,析构函数会调用了两次,那也就是说,会对同一块空间析构(释放)两次,这是这便是引发错误的原因。

  • 隐患,由于两块空间的地址相同,我们对对象st1进行push操作时,也会改变对象st2中的值,这并不是我们所希望的。因此我们应该在此类场景中避免浅拷贝。

此类对象的拷贝构造函数需要实现深拷贝!

简单实现一个深拷贝。
//在拷贝构造函数中简单实现一个深拷贝  st2(st1)
Stack(const Stack& stack) {
	this->_base = (int*)malloc(sizeof(int) * stack._capacity);
	if (this->_base == nullptr) {
		perror("malloc failed\n");
		return;
	}
	memcpy(this->_base, stack._base, sizeof(int) * stack._top);
	this->_top = stack._top;
	this->_capacity = stack._capacity;
}

在这里插入图片描述
可以看到,实现深拷贝后,程序正常返回。

深浅拷贝总结

  • 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以主要是要防止浅拷贝后,两个对象内的成员指针指向同一块空间
  • 一旦涉及到资源申请时,则拷贝构造函数是一定要写的,且要实现深拷贝,否则就是浅拷贝。

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

拷贝构造函数典型调用场景:

使用已存在的对象创建新对象。

Date d1(2025, 2, 11);
Date d2(d1);	//调用拷贝构造函数
  • 其作用是通过深拷贝或浅拷贝初始化一个新对象为已有对象的副本。。

函数参数类型为类类型对象。

Date Test(Date d);	//自定义类型传参时,必须调用其拷贝构造
  • 以值方式传递参数时,会调用一次拷贝构造函数创建形参d,存放在函数栈帧中

函数返回值类型为类类型对象。

Date Test(Date d) {
	Date temp(d);	//使用已存在对象创建新对象
	return temp;	//返回式
}
  • 函数返回temp时,若未启用返回值优化(RVO),会调用拷贝构造函数生成临时对象

总结需要实现拷贝构造的场景

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

  • 类中如果没有涉及资源申请时(如堆区的内存),拷贝构造函数是否写都可以
  • 一旦涉及到资源申请时(开辟了堆区的空间),则拷贝构造函数是一定要写的,否则就是浅拷贝

文章到此结束啦,以上便是要介绍的关于拷贝构造函数的所有内容。欢迎各位大佬在评论区讨论交流,如果觉得文章写的不错,还请留下免费的赞和收藏

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

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

相关文章

像取快递一样取文件?

看到一个很有意思的项目&#xff0c;像我们做软件分享的感觉会有用&#xff0c;就是现在服务器费用太贵了&#xff0c;如果自建的话感觉不是很值得。 FileCodeBox FileCodeBox 是一个轻量级的文件分享系统&#xff0c;它基于匿名口令分享文本和文件&#xff0c;无需注册登录&…

DeepSeek 助力 Vue 开发:打造丝滑的返回顶部按钮(Back to Top)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

【前端开发学习笔记15】Vue_8

手动添加Pinia到Vue项目&#xff1a; 在实际开发中&#xff0c;Pinia配置可在项目创建时自动添加。初次学习从零开始&#xff1a; 1. 用Vite创建空的Vue3项目&#xff0c;命令为npm create vuelatest。 2. 按官方文档将pinia安装到项目中。 import { createApp } from vue im…

通过docker启用rabbitmq插件

创建文件&#xff0c;docker-compose.yml services:rabbitmq:image: rabbitmq:4.0-managementports:- "5672:5672"- "15672:15672"volumes:- ./data/rabbitmq/data:/var/lib/rabbitmq # 持久化数据- ./data/rabbitmq/plugins/rabbitmq_delayed_message_ex…

对比 LVS 负载均衡群集的 NAT 模式和 DR 模式,比较其各自的优势 与基于 openEuler 构建 LVS-DR 群集

一、 对比 LVS 负载均衡群集的 NAT 模式和 DR 模式&#xff0c;比较其各自的优势 NAT 模式 部署简单&#xff1a;NAT 模式下&#xff0c;所有的服务器节点只需要连接到同一个局域网内&#xff0c;通过负载均衡器进行网络地址转换&#xff0c;就可以实现负载均衡功能。不需要对…

C++17 中 std::lcm:从入门到精通

文章目录 一、引言二、std::lcm 的基本概念三、入门示例四、计算多个整数的最小公倍数五、std::lcm 的实现原理六、在实际项目中的应用七、注意事项八、总结 一、引言 在 C 编程中&#xff0c;处理数学运算时&#xff0c;计算最小公倍数&#xff08;Least Common Multiple&…

html 点击弹出视频弹窗

一、效果: 点击视频按钮后,弹出弹窗 播放视频 二、代码 <div class="index_change_video" data-video-src="</

代码随想录算法【Day44】

Day44 1143.最长公共子序列 class Solution { public:int longestCommonSubsequence(string text1, string text2) {vector<vector<int>> dp(text1.size() 1, vector<int>(text2.size() 1, 0));for (int i 1; i < text1.size(); i) {for (int j 1; …

项目总结:java agent的使用

测试团队会做java agent的事&#xff0c;实现测试模拟&#xff0c;各种数据采集等等工作&#xff0c;而这些不需要开发改代码来做到&#xff0c;只需要挂载下agent。 目录 javaagent认识和例子代码例子&#xff1a;java.lang.instrument自定义实现一个javaagentagent jar测试 回…

如何借助NoETL指标平台实现数据分析、决策的提效?

通常&#xff0c;企业通过明确分析目标、定位所需分析的数据&#xff0c;再通过多渠道汇集销售数据、客户反馈、市场调研等信息&#xff0c;经过数据清洗、缺失值处理及格式标准化等手段&#xff0c;运用描述性统计、回归分析、聚类分析及关联规则挖掘等多样分析方法&#xff0…

大模型语言简介

大模型语言能做什么 信息提取 将长段文字中的信息抽取出来并且以结构化的方式输出。相比起传统NLP的方式&#xff0c;大模型在泛化能力上有非常大的提升&#xff0c;并且开发成本要低2个数量级。应用场景包括&#xff1a;论文论点论据提取、用户画像提取、舆情分析、病例结构…

手动配置IP

手动配置IP&#xff0c;需要考虑四个配置项&#xff1a; 四个配置项 IP地址、子网掩码、默认网关、DNS服务器 IP地址&#xff1a;格式表现为点分十进制&#xff0c;如192.168.254.1 子网掩码&#xff1a;用于区分网络位和主机位 【子网掩码的二进制表达式一定是连续的&#…

Golang 进阶训练营

一、Golang 的 slice、map、channel 1.1 slice vs array a : make([]int, 100) //切片 b : [100]int{} //数组array需指明长度&#xff0c;长度为常量且不可改变 array长度为其类型中的组成部分&#xff08;给参数为长度100的数组的方法传长度为101的会报错&#xff09; array在…

2-使用wifidog实现portal

wifidog是openwrt上面实现portal认证的一个开源工具&#xff0c;从网关端到服务器都帮你搭建好&#xff0c;通过学习wifidog的原理&#xff0c;后面就可以改造成自己需要的逻辑。 1. openwrt安装wifidog 添加源 vim 14.07/feeds.conf.defaultsrc-git wifidog https://github.c…

AI时代前端开发的创造力:解放还是束缚?

在人工智能&#xff08;AI&#xff09;快速发展的时代&#xff0c;AI技术的影响已经渗透到各个领域&#xff0c;从医疗保健到金融服务&#xff0c;再到创意产业。AI工具的出现&#xff0c;为前端开发带来了前所未有的效率提升&#xff0c;但也引发了人们对创造力的担忧&#xf…

有哪些免费的SEO软件优化工具

随着2025年互联网的不断发展&#xff0c;越来越多的企业意识到在数字营销中&#xff0c;网站的曝光度和排名至关重要。无论是想要提高品牌知名度&#xff0c;还是想要通过在线销售增加收益&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;都是一项不可忽视的关键策略。而要…

FastExcel + Java:打造高效灵活的Excel数据导入导出解决方案

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 基于AOP的数据字典实现…

node.js + html调用ChatGPTApi实现Ai网站demo(带源码)

文章目录 前言一、demo演示二、node.js 使用步骤1.引入库2.引入包 前端HTML调用接口和UI所有文件总结 前言 关注博主&#xff0c;学习每天一个小demo 今天是Ai对话网站 又到了每天一个小demo的时候咯&#xff0c;前面我写了多人实时对话demo、和视频转换demo&#xff0c;今天…

STM32+Proteus+DS18B20数码管仿真实验

1. 实验准备 硬件方面&#xff1a; 了解 STM32 单片机的基本原理和使用方法&#xff0c;本实验可选用常见的 STM32F103 系列。熟悉 DS18B20 温度传感器的工作原理和通信协议&#xff08;单总线协议&#xff09;。数码管可选用共阴极或共阳极数码管&#xff0c;用于显示温度值。…

Vulhub靶机 ActiveMQ 反序列化漏洞(CVE-2015-5254)(渗透测试详解)

一、开启vulhub环境 docker-compose up -d 启动 docker ps 查看开放的端口 漏洞版本&#xff1a;Apache ActiveMQ 5.x ~ Apache ActiveMQ 5.13.0 二、访问靶机IP 8161端口 默认账户密码 admin/admin&#xff0c;登录 此时qucues事件为空 1、使用jmet-0.1.0-all.jar工具将…