当前位置: 首页 > article >正文

C++ 拷贝构造函数深度解析:从浅拷贝到深拷贝

引言在 C 面向对象编程中拷贝构造函数是一个既基础又容易出错的话题。很多初学者包括曾经的我在遇到指针成员时常常因为默认的浅拷贝而导致程序崩溃或内存错误。我想通过自己的学习笔记和实践经验系统地分享拷贝构造函数的方方面面。第一部分什么是拷贝构造函数一、拷贝构造函数的定义拷贝构造函数是一种特殊的构造函数它使用同一个类的另一个对象来初始化新创建的对象。class Person { private: int pid; char* name; public: // 拷贝构造函数的签名类名(const 类名 other) Person(const Person other) { // 拷贝逻辑 } };二、拷贝构造函数的调用时机拷贝构造函数在以下三种情况下会被调用:class Person { public: Person() { cout 默认构造 endl; } Person(const Person other) { cout 拷贝构造 endl; } ~Person() { cout 析构 endl; } }; // 1. 使用一个对象初始化另一个对象 Person p1; Person p2 p1; // 拷贝构造 Person p3(p1); // 拷贝构造 // 2. 函数参数按值传递 void func(Person p) { } // 形参是实参的副本 func(p1); // 调用拷贝构造 // 3. 函数按值返回 Person createPerson() { Person p; return p; // 可能调用拷贝构造可能被优化 }第二部分浅拷贝的问题一、编译器默认的浅拷贝编译器默认提供的拷贝构造函数执行的是浅拷贝Shallow Copy即逐字节复制成员变量:class Person { private: int pid; char* name; // 指针成员 public: Person(int pid, const char* name) : pid(pid) { this-name new char[32]{0}; memcpy(this-name, name, strlen(name)); cout 构造: name endl; } // 使用编译器默认的拷贝构造函数浅拷贝 // Person(const Person other) default; void hi() const { cout hi, name , pid pid endl; } ~Person() { cout 析构: name endl; delete[] name; } }; int main() { Person p1(1001, HELLO); Person p2 p1; // 浅拷贝p2.name 和 p1.name 指向同一块内存 p1.hi(); // hi, HELLO, pid1001 p2.hi(); // hi, HELLO, pid1001 return 0; // 析构时同一块内存被释放两次 → 程序崩溃 }二、浅拷贝导致的问题内存布局浅拷贝后 p1: [pid1001] [name] ──────┐ │ p2: [pid1001] [name] ──────┘ │ ┌─────────┴─────────┐ │ HELLO 内存块 │ └───────────────────┘ 问题 1. p1 和 p2 共享同一块内存 2. 析构时同一块内存被释放两次 → double free 错误 3. 通过 p2 修改 name 会影响 p1第三部分深拷贝的实现一、深拷贝的原理深拷贝Deep Copy为每个指针成员分配独立的内存空间并将内容复制过去class Person { private: int pid; char* name; public: // 构造函数 Person(int pid, const char* name) : pid(pid) { this-name new char[32]{0}; memcpy(this-name, name, strlen(name)); cout 构造: this-name endl; } // 深拷贝构造函数 Person(const Person other) { cout 深拷贝构造 endl; // 1. 复制普通成员 pid other.pid; // 2. 为指针成员分配新空间 name new char[32]{0}; // 3. 复制内容 memcpy(name, other.name, strlen(other.name)); } void hi() const { cout hi, name , pid pid endl; } void setPid(int pid) { this-pid pid; } void setName(const char* name) { memset(this-name, 0, strlen(this-name)); memcpy(this-name, name, strlen(name)); } ~Person() { cout 析构: name endl; delete[] name; } }; int main() { Person p1(1001, HELLO); Person p2 p1; // 调用深拷贝构造函数 p2.setPid(1002); p2.setName(Lucy); p1.hi(); // hi, HELLO, pid1001 p2.hi(); // hi, Lucy, pid1002 return 0; // 各自释放自己的内存没有问题 }二、深拷贝的内存布局深拷贝后的内存布局 p1: [pid1001] [name] ──────┐ │ ┌───────┴───────┐ │ HELLO │ └───────────────┘ p2: [pid1002] [name] ──────┐ │ ┌───────┴───────┐ │ Lucy │ └───────────────┘ 特点 1. 每个对象拥有独立的内存 2. 修改 p2 不影响 p1 3. 析构时各自释放自己的内存第四部分拷贝构造函数的其他形式一、拷贝构造函数的参数拷贝构造函数的参数必须是引用否则会形成无限递归class Person { public: // 错误会造成无限递归 // Person(Person other) { } // 传值会再次调用拷贝构造 // 正确使用 const 引用 Person(const Person other) { } // 也可以是非 const 引用较少用 Person(Person other) { } };二、深拷贝的完整实现class String { private: char* data; size_t length; public: // 构造函数 String(const char* str ) { length strlen(str); data new char[length 1]; strcpy(data, str); cout 构造: data endl; } // 深拷贝构造函数 String(const String other) { length other.length; data new char[length 1]; strcpy(data, other.data); cout 深拷贝: data endl; } // 拷贝赋值运算符 String operator(const String other) { if (this ! other) { // 防止自赋值 delete[] data; // 释放原有内存 length other.length; data new char[length 1]; strcpy(data, other.data); cout 拷贝赋值: data endl; } return *this; } void print() const { cout data endl; } ~String() { cout 析构: data endl; delete[] data; } }; int main() { String s1(Hello); String s2 s1; // 深拷贝构造 String s3(World); s3 s1; // 拷贝赋值 s1.print(); // Hello s2.print(); // Hello s3.print(); // Hello return 0; }三、禁用拷贝构造函数某些类不应该被拷贝如管理独占资源的类// 方式1C11 使用 delete class NonCopyable { public: NonCopyable() default; NonCopyable(const NonCopyable) delete; NonCopyable operator(const NonCopyable) delete; }; // 方式2传统做法声明为 private 且不实现 class NonCopyableOld { private: NonCopyableOld(const NonCopyableOld); NonCopyableOld operator(const NonCopyableOld); public: NonCopyableOld() {} }; int main() { NonCopyable a; // NonCopyable b a; // 编译错误 return 0; }第五部分浅拷贝与深拷贝对比对比项浅拷贝深拷贝内存分配不分配新内存为指针成员分配新内存内存共享共享同一块内存各自独立修改影响相互影响互不影响析构安全可能导致 double free安全性能快较慢需要分配内存适用场景无指针成员的简单类包含指针成员的类何时需要深拷贝// 需要深拷贝的情况 class NeedDeepCopy { private: int* data; // 指针成员 char* buffer; // 动态分配的内存 vectorint* ptr; // 指针指向容器 }; // 不需要深拷贝的情况 class NoDeepCopy { private: int a, b, c; // 基本类型 string name; // string 自己管理内存 vectorint vec; // 容器自己管理内存 };第六部分规则与最佳实践一、三/五法则三法则C98如果类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个那么通常需要自定义所有三个。五法则C11增加了移动构造函数和移动赋值运算符。class RuleOfFive { private: int* data; size_t size; public: // 构造函数 RuleOfFive(size_t s) : size(s), data(new int[s]) {} // 析构函数 ~RuleOfFive() { delete[] data; } // 拷贝构造函数 RuleOfFive(const RuleOfFive other) : size(other.size), data(new int[other.size]) { std::copy(other.data, other.data size, data); } // 拷贝赋值运算符 RuleOfFive operator(const RuleOfFive other) { if (this ! other) { delete[] data; size other.size; data new int[size]; std::copy(other.data, other.data size, data); } return *this; } // 移动构造函数 RuleOfFive(RuleOfFive other) noexcept : data(other.data), size(other.size) { other.data nullptr; other.size 0; } // 移动赋值运算符 RuleOfFive operator(RuleOfFive other) noexcept { if (this ! other) { delete[] data; data other.data; size other.size; other.data nullptr; other.size 0; } return *this; } };二、最佳实践建议优先使用string和vector它们已经正确实现了深拷贝遵循三/五法则保持资源管理的一致性使用 default当默认行为正确时显式声明使用 delete当不希望有拷贝操作时禁用传参用 const 引用避免不必要的拷贝// 推荐使用标准库类型 class GoodExample { string name; vectorint scores; // 编译器生成的拷贝构造已正确实现深拷贝 }; // 推荐显式声明意图 class ExplicitExample { public: ExplicitExample() default; virtual ~ExplicitExample() default; // 禁用拷贝 ExplicitExample(const ExplicitExample) delete; ExplicitExample operator(const ExplicitExample) delete; // 允许移动 ExplicitExample(ExplicitExample) default; ExplicitExample operator(ExplicitExample) default; };总结一、核心要点概念说明拷贝构造函数用已有对象初始化新对象的特殊构造函数浅拷贝逐字节复制指针成员共享内存深拷贝为指针成员分配独立内存复制内容调用时机初始化、传值、返回值三/五法则资源管理类需自定义拷贝/析构/移动操作二、快速判断是否需要深拷贝class MyClass { // 如果有以下成员通常需要深拷贝 char* ptr; // 原始指针 int* data; // 动态分配的数组 FILE* file; // 资源句柄 // 以下成员不需要深拷贝 int a; // 基本类型 string s; // 标准库类型 vectorint v; // 标准库容器 };三、常见陷阱提醒// 错误浅拷贝导致 double free class Bad { char* p; public: Bad() { p new char[10]; } ~Bad() { delete[] p; } // 缺少拷贝构造函数和拷贝赋值 }; // 正确深拷贝或禁用拷贝 class Good { char* p; public: Good() { p new char[10]; } ~Good() { delete[] p; } Good(const Good other) { p new char[10]; memcpy(p, other.p, 10); } // 或者禁用拷贝 // Good(const Good) delete; };拷贝构造函数是 C 资源管理的核心知识点之一。理解浅拷贝和深拷贝的区别不仅能避免程序崩溃更是理解 C 内存模型的重要一步。在学习过程中我最大的体会是默认的不一定是对的。编译器为我们提供了便利但当类涉及资源管理时我们必须自己动手确保资源的正确复制和释放。掌握拷贝构造函数是迈向专业 C 开发者的必经之路。希望这篇文章能帮助你彻底理解这个重要的概念

相关文章:

C++ 拷贝构造函数深度解析:从浅拷贝到深拷贝

引言在 C 面向对象编程中,拷贝构造函数是一个既基础又容易出错的话题。很多初学者(包括曾经的我)在遇到指针成员时,常常因为默认的浅拷贝而导致程序崩溃或内存错误。我想通过自己的学习笔记和实践经验,系统地分享拷贝构…...

PHP爬虫框架大比拼

PHP 爬虫框架介绍PHP 作为服务器端脚本语言,在爬虫领域有多个成熟的框架,以下是主流框架的对比分析:1. Goutte特点:基于 Symfony 组件,轻量易用,适合基础爬取任务。 核心功能:模拟浏览器行为&am…...

新手福音:用快马AI生成你的第一个简易网页网盘项目

作为一个刚接触编程的新手,想要快速上手一个实际项目确实容易感到无从下手。最近我在学习网页开发时,尝试用InsCode(快马)平台做了一个简易网页网盘,整个过程意外地顺利。这个项目虽然功能简单,但涵盖了前端开发的几个核心概念&am…...

G-Helper技术指南:华硕笔记本显示配置与性能优化全解析

G-Helper技术指南:华硕笔记本显示配置与性能优化全解析 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, Strix, S…...

OpenClaw隐私保护方案:千问3.5-35B-A3B-FP8本地化数据处理实践

OpenClaw隐私保护方案:千问3.5-35B-A3B-FP8本地化数据处理实践 1. 为什么需要全链路隐私保护 去年我帮一位医生朋友整理病历资料时,突然意识到一个问题:当AI助手能读取患者检查报告、化验单甚至影像资料时,如何确保这些敏感信息…...

告别复杂配置!Fish Speech 1.5 开箱即用,3步搭建你的专属语音合成工具

告别复杂配置!Fish Speech 1.5 开箱即用,3步搭建你的专属语音合成工具 1. 为什么选择Fish Speech 1.5? 语音合成技术正在改变我们与数字世界的交互方式,但传统TTS系统往往需要复杂的音素标注和专业配置。Fish Speech 1.5通过创新…...

G-Helper终极指南:解锁华硕笔记本隐藏性能的5个秘密功能

G-Helper终极指南:解锁华硕笔记本隐藏性能的5个秘密功能 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, Strix, …...

如何用ULTIMATE ANIMATION COLLECTION打造3A级游戏动画效果?Unity 2022实战案例解析

如何用ULTIMATE ANIMATION COLLECTION打造3A级游戏动画效果?Unity 2022实战案例解析 在游戏开发领域,动画质量往往是区分平庸作品与精品的关键分水岭。当玩家控制角色挥剑时剑刃的轨迹是否流畅自然,角色与环境互动时是否呈现真实的物理反馈&a…...

如何用Sunshine打造个人专属的游戏云服务:从零开始搭建高性能串流服务器

如何用Sunshine打造个人专属的游戏云服务:从零开始搭建高性能串流服务器 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 厌倦了被高性能游戏主机束缚在固定位置&#x…...

保健及护理用家具市场:548.6亿元规模下的多维洞察

据恒州诚思调研统计,2025年全球保健及护理用家具收入规模约达466.7亿元,预计到2032年,这一数字将接近548.6亿元,2026 - 2032年的复合年增长率(CAGR)为2.5%。在医疗行业不断发展、人口结构持续变化的背景下&…...

AGI通用人工智能:离我们还有多远

AGI通用人工智能:离我们还有多远📝 本章学习目标:通过本章学习,你将全面掌握"AGI通用人工智能:离我们还有多远"这一核心主题,建立系统性认知。一、引言:为什么这个话题如此重要 在人工…...

小功率风冷电堆市场:68.65MW产能下的氢燃料电池产业新局

氢燃料电池产业在发展进程中,经历了显著的变革与细分。最初,该产业主要聚焦于高功率水冷电堆,此类液冷电堆凭借高额定功率与复杂的热管理系统,成为乘用车和商用车辆大规模部署的坚实基础。然而,随着市场需求的不断演变…...

佣金自动算、订单自动记,这才叫好系统

做推客、做分销、做私域小店,最磨人的从来不是拉新和卖货,而是没完没了的记账、对账、算佣金。人工统计订单、Excel 算佣金、靠截图核对业绩,不仅慢、容易错,还特别消耗信任。真正能让商家省心、让推客放心的好系统,标…...

从PC到移动端:百度地图电子围栏的绘制实践与坐标检测全解析

1. 电子围栏技术概述与应用场景 电子围栏作为地理围栏(Geo-Fencing)技术的具体实现形式,本质上是通过虚拟边界对物理空间进行数字化划分。想象一下,就像小朋友用粉笔在地上画出一个游戏区域,只不过我们把这种能力搬到了…...

【初学者说—C语言】

大家好!我是一名计算机网络技术专业的学生,刚刚开始接触C语言,感到无比有趣。当然我并非是一时兴起来学C语言的,我学习C语言是为了跟好拿到offer, 为自己在这AI迭代更新迅速的时代谋求生路。学习代码是一个长久的过程,…...

若依RuoYi-Vue集成wangEditor:从零到一构建富文本内容管理模块

1. 为什么选择wangEditor与若依框架组合 在前后端分离的开发模式中,富文本编辑器是内容管理系统的核心组件。我实测过市面上主流的编辑器,wangEditor以其轻量级、易扩展的特性脱颖而出。特别是对于使用若依(RuoYi-Vue)框架的开发者来说,这个组…...

如何让Flash内容重获新生?CefFlashBrowser全方位应用指南

如何让Flash内容重获新生?CefFlashBrowser全方位应用指南 【免费下载链接】CefFlashBrowser Flash浏览器 / Flash Browser 项目地址: https://gitcode.com/gh_mirrors/ce/CefFlashBrowser 随着Adobe Flash Player的正式退役,大量依赖Flash技术的网…...

YOLO-v8.3部署优化指南:显存管理+参数调整,解决卡顿难题

YOLO-v8.3部署优化指南:显存管理参数调整,解决卡顿难题 1. 问题诊断:为什么YOLO-v8.3会卡顿? 当你兴奋地部署了最新的YOLO-v8.3模型,准备开始物体检测任务时,突然遇到程序卡顿甚至崩溃的情况,…...

个人开发者如何用隧道代理实现“代理自由”?

那个被反爬逼疯的周末去年有个周末,我窝在家里写一个比价脚本。想爬几个主流电商平台的价格数据,做个小工具自己用。代码写得挺顺,Requests库套上代理,循环跑起来。前50次请求一切正常,第51次——啪,403。换…...

5分钟为Windows 11 24H2 LTSC恢复微软应用商店:小白也能懂的完整教程

5分钟为Windows 11 24H2 LTSC恢复微软应用商店:小白也能懂的完整教程 【免费下载链接】LTSC-Add-MicrosoftStore Add Windows Store to Windows 11 24H2 LTSC 项目地址: https://gitcode.com/gh_mirrors/ltscad/LTSC-Add-MicrosoftStore 还在为Windows 11 24…...

工厂升级不换设备?揭秘全志T113-i边缘网关的“万能翻译”魔法

在当今智能制造和工业物联网的浪潮下,工厂车间正经历着一场深刻的“神经”系统升级。以PROFINET、EtherNet/IP、Modbus TCP为代表的工业以太网协议,凭借其高速、实时、开放的特性,已成为现代自动化系统的“中枢神经”。然而,走进许…...

MKVToolNix Batch Tool:高效处理视频字幕的批量解决方案

MKVToolNix Batch Tool:高效处理视频字幕的批量解决方案 【免费下载链接】mkvtoolnix-batch-tool Batch video and subtitle processing program with the ability to add, remove, or extract subtitles from all video files in a directory and its sub-director…...

基于51单片机的智能鱼缸设计:STC12C5A60S2为核心的多功能控制系统

基于51单片机的智能鱼缸设计。 有原理图,程序,原文 才用STC12C5A60S2,最新款国产51单片机。 本系统设计的主要是基于单片机为核心,设计一款集温度检测、恒温控制、步进电机控制、继电器控制、矩阵键盘设计于一身的智能鱼缸控制系统…...

网络基础回顾:DNS、IP封锁与HTTP/S协议关键点

网络基础回顾:DNS、IP封锁与HTTP/S协议关键点 昨天有个读者在后台问我:“为什么改了Hosts文件还是打不开ZLibrary?明明Ping得通啊。” 这个问题让我想起刚入行时踩过的坑——你以为网络通了,其实只是你以为。今天我们就从这个问题…...

穿透式监管是什么?终于有人把穿透式监管落地讲明白了!

最近,各位老板有没有发现各种审计、检查多起来了?国资委、集团总部的发文一个接一个,问题也越来越细致。最近大家都被穿透式监管这个词弄得有点紧张,害怕自己的企业那天也被点名。其实,穿透式监管对企业来说&#xff0…...

RobotFramework自动化测试实战:从关键字设计到复杂循环处理

RobotFramework自动化测试实战:从关键字设计到复杂循环处理 在软件测试领域,自动化测试已经成为提升效率、保证质量的必备手段。而RobotFramework作为一款基于Python的开源自动化测试框架,凭借其关键字驱动的设计理念和高度可扩展性&#xf…...

AMD ROCm 图形加速库优化指南:释放gfx1103架构性能潜力

AMD ROCm 图形加速库优化指南:释放gfx1103架构性能潜力 【免费下载链接】ROCmLibs-for-gfx1103-AMD780M-APU ROCm Library Files for gfx1103 and update with others arches based on AMD GPUs for use in Windows. 项目地址: https://gitcode.com/gh_mirrors/r…...

LN2407 PWM/PFM 控制 DC-DC 降压稳压器

■ 产品概述 LN2407 是一款由基准电压源、振荡电路、比较器、PWM/PFM 控制电路等构成的 CMOS 降压 DC/DC 调整器。利用 PWM/PFM 自动切换控制电路达到可调占空比,具有全输入电压范围(2.0-6V)内的低纹波、高效率和大输出电流等特点…...

LN2406 PWM/PFM 控制 DC-DC 降压稳压器

■ 产品概述 LN2406 是一款由基准电压源、振荡电路、比较器、PWM/PFM 控制电路等构成的 CMOS 降压 DC/DC 调整器。利用 PWM/PFM 自动切换控制电路达到可调占空比,具有全输入电压范围(2.0-6V)内的低纹波、高效率和大输出电流等特点…...

OpenAI Operator深度解析:自主浏览器智能体如何改变人机交互

OpenAI Operator 深度解析:自主浏览器智能体如何改变人机交互 摘要:OpenAI Operator 是一款革命性的自主浏览器智能体,能够独立执行复杂的网页任务。本文深入解析其技术原理、应用场景及未来发展趋势。 一、什么是 OpenAI Operator? OpenAI Operator 是 OpenAI 于 2025 年…...