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

前言
上文中详解了构造函数和析构函数,本文详解类的六个默认成员函数中的第三个,拷贝构造函数,并辨析区分深拷贝与浅拷贝。
引入
在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
答案当然是有的, 拷贝构造函数就是来完成这一工作的。
拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
//拷贝构造函数实例
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++**中传参时有如下要求:
- 内置类型,无要求,直接拷贝;
- 自定义类型,值传参,必须调用其拷贝构造函数

内置类型传参拷贝
C++内置类型拷贝
我们可以看到,
自定义类型传参拷贝
C++自定义类型拷贝
我们可以看到:
- 内置类型作为参数时,调用函数会直接进入函数内。。
- 自定义类型作为参数时,调用函数,会先调用其拷贝构造函数来拷贝。
此时再来看,若拷贝构造函数为值传递:
Date(const Date date) { //下面解释会无穷递归调用_year = date._year;_month = date._month;_day = date._day;
}
详细解释
栈帧基本概念
- 栈帧(Stack Frame): 当一个函数被调用时,程序会为这个函数分配一个栈帧,其中存放了函数的参数、局部变量、返回地址等信息。
- 函数调用和返回: 每当一个函数调用完成后,其对应的栈帧会被释放。如果函数不断调用自身(递归),每次调用都会在栈上分配一个新的栈帧。如果没有合适的终止条件,栈帧会不断累积,最终导致栈空间耗尽(栈溢出)。

执行语句Date d2(d1)时,流程如下:
Date d2(d1),执行该语句,参数列表匹配,会调用其拷贝构造函数,构造函数会创建一个栈帧,栈帧内空间存放有形参date。形参date是实参d1的一份拷贝,拷贝时会调用Date的拷贝构造函数,而拷贝构造函数Date(const Date date),参数为自定义类型的值传递,自定义类型的值传递,同样会创建栈帧并调用其拷贝构造函数。- 拷贝构造函数继续传值引发对象的拷贝,之后会层层传值引发对象的拷贝的递归调用。
总结:调用 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),会调用拷贝构造函数生成临时对象
总结需要实现拷贝构造的场景
声明:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
因此:
- 类中如果没有涉及资源申请时(如堆区的内存),拷贝构造函数是否写都可以;
- 一旦涉及到资源申请时(开辟了堆区的空间),则拷贝构造函数是一定要写的,否则就是浅拷贝。
文章到此结束啦,以上便是要介绍的关于拷贝构造函数的所有内容。欢迎各位大佬在评论区讨论交流,如果觉得文章写的不错,还请留下免费的赞和收藏!
相关文章:
C++类和对象进阶:拷贝构造函数深度详解
拷贝构造函数 拷贝构造函数前言引入拷贝构造函数特征拷贝构造函数建议参数加上const 拷贝构造函数参数传值会引发无穷递归的解释内置类型传参拷贝自定义类型传参拷贝详细解释 编译器生成的默认拷贝构造函数默认构造函数做了什么?深拷贝与浅拷贝简单实现一个深拷贝。…...
docker搭建redis-cluster
两台服务器,并且想要搭建 Redis 集群。根据你的命令,Redis 集群将会运行在 Docker 容器中,而你使用的镜像是 redis-cluster:4.0,并且设定了 CLUSTER_ANNOUNCE_IP 环境变量来指定 Redis 实例的 IP 地址。 为了在两台服务器上搭建 …...
深入了解 Oracle 正则表达式
目录 深入了解 Oracle 正则表达式一、正则表达式基础概念二、Oracle 正则表达式语法(一)字符类(二)重复限定符(三)边界匹配符(四)分组和捕获 三、Oracle 正则表达式函数(…...
傅里叶公式推导(三)
文章目录 周期 2L周期T 周期 2L 周期 T 2 L T2L T2L 的傅里叶变换 即 f ( t ) f ( t 2 L ) f(t) f(t2L) f(t)f(t2L) xt2 π \pi π 2 L 2L 2L 原公式 f ( x ) a 0 2 ∑ n 1 ∞ [ a n cos n x b n sin n x ] a 0 1 π ∫ − π π f ( x ) d x a n 1 π ∫…...
像取快递一样取文件?
看到一个很有意思的项目,像我们做软件分享的感觉会有用,就是现在服务器费用太贵了,如果自建的话感觉不是很值得。 FileCodeBox FileCodeBox 是一个轻量级的文件分享系统,它基于匿名口令分享文本和文件,无需注册登录&…...
【CXX】1 CXX主要概念概览
本文描述了CXX(一个用于在Rust和C之间进行桥接的库)中的关键概念,特别是FFI(外部函数接口)边界所涉及的三种主要类型:共享结构体、不透明类型和函数。 一、示例代码 #[cxx::bridge] mod ffi {// 任何共享…...
PyQT项目如何在Linux中自启显示界面
可以通过systemd服务启动PyQt程序 1. 创建服务文件: 在 /etc/systemd/system/ 目录下创建一个新的服务文件。例如,如果您的程序名为 my_program.py,可以创建一个名为 my_program.service 的文件: sudo nano /etc/systemd/system…...
【信息系统项目管理师-案例真题】2019下半年案例分析答案和详解
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 试题一【问题 1】(6 分)【问题 2 】(8 分)【问题 3 】(11 分)试题二【问题 1】(5分)【问题 2】 (14 分)【问题 3 】(6 分)试题三【问题 1】(8 分)【问题 2 】(6 分)【问题 3】 (8 分)【问题 4 …...
DeepSeek 助力 Vue 开发:打造丝滑的返回顶部按钮(Back to Top)
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
【前端开发学习笔记15】Vue_8
手动添加Pinia到Vue项目: 在实际开发中,Pinia配置可在项目创建时自动添加。初次学习从零开始: 1. 用Vite创建空的Vue3项目,命令为npm create vuelatest。 2. 按官方文档将pinia安装到项目中。 import { createApp } from vue im…...
deepin linux UOS AI 使用 deepseek-r1 30B
我们用 ollama 下载 deepseek-r1 3B 执行命令: $ ollama pull models/unsloth/DeepSeek-R1-Distill-Qwen-32B-GGUF 下载完成后 我们就要重新更改目录和文件了 deepseek-r1/gguf (这是目录结构) 然后我把 gguf文件 更名成 DeepSeek-R1.gguf (就是目录下最大的那个文件) …...
通过docker启用rabbitmq插件
创建文件,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 模式,比较其各自的优势 NAT 模式 部署简单:NAT 模式下,所有的服务器节点只需要连接到同一个局域网内,通过负载均衡器进行网络地址转换,就可以实现负载均衡功能。不需要对…...
C++17 中 std::lcm:从入门到精通
文章目录 一、引言二、std::lcm 的基本概念三、入门示例四、计算多个整数的最小公倍数五、std::lcm 的实现原理六、在实际项目中的应用七、注意事项八、总结 一、引言 在 C 编程中,处理数学运算时,计算最小公倍数(Least Common Multiple&…...
html 点击弹出视频弹窗
一、效果: 点击视频按钮后,弹出弹窗 播放视频 二、代码 <div class="index_change_video" data-video-src="</...
docker安装mongo,导入、导出数据
1、docker安装mongo docker pull mongo docker run -d -p 27017:27017 --name mongodb mongodocker update mongodb --restartalways ## 开机自启动-d:表示以后台模式运行容器。 -p 27017:27017:将容器内部的 MongoDB 默认端口 27017 映射到宿主机的 27…...
代码随想录算法【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的事,实现测试模拟,各种数据采集等等工作,而这些不需要开发改代码来做到,只需要挂载下agent。 目录 javaagent认识和例子代码例子:java.lang.instrument自定义实现一个javaagentagent jar测试 回…...
使用 LangChain 对接硅基流动(SiliconFlow)API:构建一个智能对话系统
文章目录 什么是硅基流动(SiliconFlow)?LangChain 简介在 LangChain 中对接硅基流动步骤 1:安装必要的库步骤 2:设置 API 密钥步骤 3:编写代码代码解析步骤 4:运行代码如何扩展和改进总结 在现代…...
如何借助NoETL指标平台实现数据分析、决策的提效?
通常,企业通过明确分析目标、定位所需分析的数据,再通过多渠道汇集销售数据、客户反馈、市场调研等信息,经过数据清洗、缺失值处理及格式标准化等手段,运用描述性统计、回归分析、聚类分析及关联规则挖掘等多样分析方法࿰…...
Java--IO流详解 (上)--字符流
目录 IO流的概念 字符流 输入流 Reader核心方法 1.close() 2.mark(int readAheadLimit) 3.markSupported() 4.read() 5.read(char[] cbuf) 6.read(char[] cbuf, int off, int len) 7.read(CharBuffer target) 8.ready() 9.reset() 10.skip(long n) Reader 的常用…...
大模型语言简介
大模型语言能做什么 信息提取 将长段文字中的信息抽取出来并且以结构化的方式输出。相比起传统NLP的方式,大模型在泛化能力上有非常大的提升,并且开发成本要低2个数量级。应用场景包括:论文论点论据提取、用户画像提取、舆情分析、病例结构…...
手动配置IP
手动配置IP,需要考虑四个配置项: 四个配置项 IP地址、子网掩码、默认网关、DNS服务器 IP地址:格式表现为点分十进制,如192.168.254.1 子网掩码:用于区分网络位和主机位 【子网掩码的二进制表达式一定是连续的&#…...
Golang 进阶训练营
一、Golang 的 slice、map、channel 1.1 slice vs array a : make([]int, 100) //切片 b : [100]int{} //数组array需指明长度,长度为常量且不可改变 array长度为其类型中的组成部分(给参数为长度100的数组的方法传长度为101的会报错) array在…...
2-使用wifidog实现portal
wifidog是openwrt上面实现portal认证的一个开源工具,从网关端到服务器都帮你搭建好,通过学习wifidog的原理,后面就可以改造成自己需要的逻辑。 1. openwrt安装wifidog 添加源 vim 14.07/feeds.conf.defaultsrc-git wifidog https://github.c…...
Spring Boot + ShardingSphere 踩坑记
最近在准备秋招,偷了个轮子项目之后想改个分表,于是有了这篇文章。 省流:请使用shardingsphere-jdbc 5.5.2,并根据官方5.5.2版本文档进行配置,不要使用starter。此外,如果希望使用INTERVAL分片算法&#x…...
AI时代前端开发的创造力:解放还是束缚?
在人工智能(AI)快速发展的时代,AI技术的影响已经渗透到各个领域,从医疗保健到金融服务,再到创意产业。AI工具的出现,为前端开发带来了前所未有的效率提升,但也引发了人们对创造力的担忧…...
有哪些免费的SEO软件优化工具
随着2025年互联网的不断发展,越来越多的企业意识到在数字营销中,网站的曝光度和排名至关重要。无论是想要提高品牌知名度,还是想要通过在线销售增加收益,SEO(搜索引擎优化)都是一项不可忽视的关键策略。而要…...
FastExcel + Java:打造高效灵活的Excel数据导入导出解决方案
作者:后端小肥肠 🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案 🍊 有疑问可私信或评论区联系我。 🥑 创作不易未经允许严禁转载。 姊妹篇: 基于AOP的数据字典实现…...
在Vue中,JavaScript数组常用方法,添加,插入,查找,删除等整理
在Vue中,JavaScript数组常用,添加,插入,查找,删除等整理 1.splice()方法可以直接修改原数组,通过指定要删除元素的索引来删除它。 例: let index // 要删除的元素的索引; this.array.splice(i…...
