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

【c++面向对象编程】第4篇:类与对象(三):拷贝构造函数与深浅拷贝问题

目录一、一个崩溃的程序二、拷贝构造函数是什么调用时机三个场景三、浅拷贝 vs 深拷贝浅拷贝默认行为深拷贝正确的做法四、什么时候必须自己写拷贝构造函数一个反面例子vector的浅拷贝问题五、完整的例子安全的动态数组六、三个常见的坑1. 拷贝构造函数参数不用引用 → 无限递归2. 忘了const导致无法拷贝const对象3. 浅拷贝发生在你没想到的地方七、这一篇的收获一、一个崩溃的程序先看这段代码你觉得它会崩溃吗cppclass StringWrapper { private: char* data; public: StringWrapper(const char* str) { data new char[strlen(str) 1]; strcpy(data, str); } ~StringWrapper() { delete[] data; } void print() { cout data endl; } }; int main() { StringWrapper s1(Hello); StringWrapper s2 s1; // 用s1初始化s2 s1.print(); s2.print(); return 0; } // 程序在这里崩溃运行结果可能正常输出也可能输出乱码最后大概率崩溃。原因很简单s1和s2里面的data指针指向了同一块内存。当程序结束s2先析构delete[]了那块内存然后s1析构再次delete[]同一块内存——重复释放程序崩溃。这就是浅拷贝带来的灾难。二、拷贝构造函数是什么拷贝构造函数是一种特殊的构造函数参数是本类对象的const引用用已有的对象去创建新的对象时自动调用如果你不写编译器会生成一个默认的逐成员复制语法长这样cppclass MyClass { public: // 拷贝构造函数 MyClass(const MyClass other) { // 拷贝逻辑 } };调用时机三个场景cppclass Demo { public: Demo() { cout 普通构造 endl; } Demo(const Demo other) { cout 拷贝构造 endl; } ~Demo() { cout 析构 endl; } }; Demo makeDemo() { Demo d; return d; // 场景3返回值 } int main() { Demo a; // 普通构造 Demo b a; // 场景1用a初始化b → 拷贝构造 Demo c(a); // 场景2直接传参 → 拷贝构造 Demo d makeDemo(); // 场景3返回值可能被优化掉不一定调用 }关键点“”在这里不是赋值是初始化。赋值是后面讲的重载operator。三、浅拷贝 vs 深拷贝浅拷贝默认行为编译器生成的默认拷贝构造函数做的事很简单把每个成员变量的值原样复制。cpp// 编译器生成的默认版本概念上 StringWrapper(const StringWrapper other) : data(other.data) // 只复制指针的值不复制指针指向的内容 {}对于int、double这种值类型浅拷贝没问题。但对于指针复制的是地址不是地址里的内容。浅拷贝的问题两个对象指向同一块内存一个修改另一个也跟着变可能不是你想要的效果一个释放另一个变成悬空指针重复释放导致崩溃深拷贝正确的做法深拷贝的做法不复制指针的值而是复制指针指向的内容。cppclass StringWrapper { private: char* data; public: // 普通构造函数 StringWrapper(const char* str) { data new char[strlen(str) 1]; strcpy(data, str); } // 拷贝构造函数深拷贝 StringWrapper(const StringWrapper other) { // 1. 分配新内存 data new char[strlen(other.data) 1]; // 2. 复制内容 strcpy(data, other.data); cout 深拷贝 data endl; } // 析构函数 ~StringWrapper() { delete[] data; cout 释放 data endl; } void print() { cout data endl; } // 后面会讲赋值运算符重载 };现在运行之前会崩溃的例子cppint main() { StringWrapper s1(Hello); StringWrapper s2 s1; // 深拷贝s2有自己独立的内存 s1.print(); // Hello s2.print(); // Hello return 0; // 分别释放两块内存不冲突 }内存布局对比text浅拷贝 s1.data ──→ [H][e][l][l][o][\0] s2.data ──→ ↑ (指向同一块) 深拷贝 s1.data ──→ [H][e][l][l][o][\0] s2.data ──→ [H][e][l][l][o][\0] (另一块内存)四、什么时候必须自己写拷贝构造函数三法则Rule of Three如果类需要自定义析构函数那么它几乎一定也需要自定义拷贝构造函数和拷贝赋值运算符。具体来说以下情况必须写拷贝构造函数类里有指针成员并且构造函数里用new分配了内存类里有文件句柄、数据库连接等需要“独占”的资源类里有互斥锁mutex两个对象拥有同一个锁会导致死锁一句话默认的逐成员复制对你的资源管理方式不适用时。一个反面例子vector的浅拷贝问题cppclass IntVector { private: int* arr; int size; public: IntVector(int n) : size(n) { arr new int[n]; for(int i0; in; i) arr[i] i; } ~IntVector() { delete[] arr; } // 没有写拷贝构造函数 → 浅拷贝 void set(int idx, int val) { arr[idx] val; } int get(int idx) { return arr[idx]; } }; int main() { IntVector v1(5); IntVector v2 v1; // 浅拷贝v2.arr指向v1.arr同一块内存 v2.set(0, 999); // 修改v2 cout v1.get(0); // 输出999v1被意外修改了 // 程序结束两次delete[]同一块内存 → 崩溃 }这就是所谓的“意外的共享状态”。五、完整的例子安全的动态数组cpp#include iostream #include cstring using namespace std; class SafeArray { private: int* data; int size; public: // 普通构造函数 SafeArray(int n) : size(n) { data new int[n]; for (int i 0; i n; i) { data[i] 0; } cout 构造分配了 n 个int endl; } // 拷贝构造函数深拷贝 SafeArray(const SafeArray other) : size(other.size) { data new int[size]; for (int i 0; i size; i) { data[i] other.data[i]; } cout 拷贝构造深拷贝了 size 个int endl; } // 析构函数 ~SafeArray() { delete[] data; cout 析构释放了 size 个int endl; } void set(int idx, int val) { if (idx 0 idx size) data[idx] val; } int get(int idx) const { if (idx 0 idx size) return data[idx]; return -1; } void print() const { cout [; for (int i 0; i size; i) { cout data[i] (i size-1 ? , : ); } cout ] endl; } }; int main() { SafeArray a(5); for (int i 0; i 5; i) a.set(i, i * 10); a.print(); // [0, 10, 20, 30, 40] SafeArray b a; // 拷贝构造 b.set(0, 999); cout a: ; a.print(); // [0, 10, 20, 30, 40] ← 没被影响 cout b: ; b.print(); // [999, 10, 20, 30, 40] ← 独立修改 return 0; }输出text构造分配了5个int [0, 10, 20, 30, 40] 拷贝构造深拷贝了5个int a: [0, 10, 20, 30, 40] b: [999, 10, 20, 30, 40] 析构释放了5个int 析构释放了5个int完美两个对象互不干扰各释放各的内存。六、三个常见的坑1. 拷贝构造函数参数不用引用 → 无限递归cppclass Bad { public: Bad(Bad other) { // ❌ 传值会再次调用拷贝构造无限递归 // ... } };参数必须用引用通常是const引用cppBad(const Bad other) { } // ✅2. 忘了const导致无法拷贝const对象cppclass Demo { public: Demo(Demo other) { } // 参数不是const }; const Demo d1; Demo d2 d1; // ❌ 错误不能将const转为非const引用3. 浅拷贝发生在你没想到的地方函数传参也会调用拷贝构造函数cppvoid func(SafeArray arr) { // 传值会调用拷贝构造 // ... } SafeArray a(10); func(a); // 这里发生了一次深拷贝如果数组很大深拷贝的开销不小。想避免拷贝用引用传参cppvoid func(const SafeArray arr) { // 不拷贝 // ... }七、这一篇的收获你现在应该明白拷贝构造函数用已有对象创建新对象时调用浅拷贝默认行为只复制指针的值导致两个对象共享内存深拷贝自己实现分配新内存并复制内容对象各自独立三法则需要析构函数 → 就需要拷贝构造和拷贝赋值 小作业修改上面的SafeArray故意去掉拷贝构造函数观察程序会出什么问题。然后加上拷贝构造函数验证深拷贝解决了问题。下一篇预告第5篇《类与对象四赋值运算符重载》——不只是初始化还有赋值。拷贝构造和赋值运算符有什么区别什么时候调用哪个为什么赋值要返回引用下篇揭晓。

相关文章:

【c++面向对象编程】第4篇:类与对象(三):拷贝构造函数与深浅拷贝问题

目录 一、一个崩溃的程序 二、拷贝构造函数是什么? 调用时机(三个场景) 三、浅拷贝 vs 深拷贝 浅拷贝(默认行为) 深拷贝(正确的做法) 四、什么时候必须自己写拷贝构造函数? 一…...

智能体网格(Agent Mesh)架构解析:构建大规模异构智能体协同网络

1. 项目概述与核心价值最近在开源社区里,一个名为sampleXbro/agentsmesh的项目引起了我的注意。乍一看这个标题,你可能会觉得它有些神秘,甚至有点“缝合怪”的味道——sampleX、bro、agents、mesh,这些词组合在一起,到…...

【c++面向对象编程】第3篇:类与对象(二):构造函数与析构函数

目录 一、一个让人头疼的问题 二、构造函数:对象出生时的“第一声啼哭” 1. 最基本的构造函数 2. 带参数的构造函数(重载) 3. 初始化列表:更高效的初始化方式 三、默认构造函数:那个“看不见”的函数 四、析构函…...

Letta框架:全栈AI应用开发,从模型集成到部署上线的完整解决方案

1. 项目概述:一个开箱即用的AI应用开发框架最近在折腾AI应用开发的朋友,估计都绕不开一个核心痛点:想法很美好,落地很骨感。从模型调用、提示词工程,到前后端集成、状态管理,再到部署上线,每个环…...

【c++面向对象编程】第2篇:类与对象(一):定义第一个类——成员变量与成员函数

目录 一、从一个日常需求开始 二、定义你的第一个类 三、访问修饰符:public、private、protected 举个例子,看看区别: 四、成员变量怎么声明? 五、成员函数:两种实现方式 方式一:类内实现&#xff08…...

AI编程智能体评估平台CodingAgentExplorer:从原理到实践的系统评测指南

1. 项目概述:一个探索智能体编码能力的开源工具最近在GitHub上闲逛,发现了一个挺有意思的项目:tndata/CodingAgentExplorer。光看名字,你可能会觉得这又是一个“AI写代码”的工具,市面上这类工具已经多如牛毛了。但当我…...

iPhone 5c中国遇冷复盘:产品定价、市场预期与战略博弈的深度解析

1. 项目概述:一次关于市场预期的“误判”复盘2013年秋天,苹果公司发布了被外界普遍视为“专为新兴市场打造”的iPhone 5c。这款拥有多彩聚碳酸酯外壳的手机,在发布前就被贴上了“廉价iPhone”的标签,尤其是针对像中国这样庞大且正…...

《Java面试85题图解版(二)》进阶深化中篇:Spring核心 + 数据库进阶

📘 《Java面试85题图解版(二)》进阶深化中篇:Spring核心 数据库进阶 阅读提示:这是“图解比喻一句话总结”面试题库第二篇的进阶深化中篇,覆盖Spring核心与Spring Boot(9题)和数据库…...

物联网标准演进与云平台破局:从M2M到IoT的实战路径

1. 从M2M到IoT:一场迟来的标准革命十多年前,当我第一次接触“机器对机器”这个概念时,感觉它就像个被锁在工厂车间里的幽灵——功能强大,但离普通人的生活无比遥远。那时的M2M,谈论的是专用网络、私有协议和封闭的垂直…...

EDA工程师成长与验证技术演进:从算法到芯片的实践闭环

1. 从算法到芯片:一位EDA工程师的成长路径解析在半导体这个行当里待久了,你会发现,那些真正能把工具做“透”、把流程理“顺”的人,往往自己就亲手“焊”过板子、调过RTL、追过时序违例。Prakash Narain的故事,就是一个…...

ClawMorph:为OpenClaw AI智能体实现安全可逆的“一键换装”

1. 项目概述:一个为AI智能体“一键换装”的开发者工具如果你正在使用OpenClaw这类AI智能体框架,并且厌倦了每次想让智能体扮演不同角色(比如从产品经理切换到设计师)时,都需要手动去修改一堆配置文件、提示词文件&…...

番茄小说下载器:打造个人专属离线小说图书馆的完整指南

番茄小说下载器:打造个人专属离线小说图书馆的完整指南 【免费下载链接】fanqienovel-downloader 下载番茄小说 项目地址: https://gitcode.com/gh_mirrors/fa/fanqienovel-downloader 你是否曾在通勤路上突然想读小说,却因为网络信号不佳而无法加…...

从CEO到营销技术专家:创业者退休后的身份重构与价值延续

1. 从创业者到“退休者”:身份的骤然转变卖掉自己一手创办并经营了近四十年的公司,这种感觉,远非“退休”二字可以概括。它不是一次计划已久的悠闲旅行,更像是一场毫无预兆的急刹车。前一天,你还在会议室里为下一代产品…...

DevSquad:基于Docker Compose的一站式开发环境解决方案

1. 项目概述:一个为开发者量身定制的“特种作战小队”如果你是一名开发者,无论是独立作战还是身处团队,一定都经历过这样的场景:为了搭建一个项目,你需要反复安装和配置各种开发工具、运行环境、依赖包。从代码编辑器、…...

AI心智理论评估:VLM意图理解接近人类,但视角采样能力存在瓶颈

1. 项目概述:当AI“读懂”人心时,它在想什么?在人工智能领域,有一个听起来颇具哲学意味的挑战:如何让机器理解“心智”?这不仅仅是让AI识别图像中的物体或生成流畅的文本,而是让它能够像人类一样…...

5分钟快速上手:Blender 3MF插件让你轻松实现3D打印模型转换

5分钟快速上手:Blender 3MF插件让你轻松实现3D打印模型转换 【免费下载链接】Blender3mfFormat Blender add-on to import/export 3MF files 项目地址: https://gitcode.com/gh_mirrors/bl/Blender3mfFormat 你是否曾经在Blender中精心设计了色彩丰富的3D模型…...

2012年Accellera标准演进:SystemC、UCIS与AMS如何重塑EDA设计流程

1. 回顾2012:Accellera在电子设计自动化标准演进中的关键一年对于从事半导体设计,特别是系统级设计、验证和IP集成的工程师来说,2012年是一个值得标记的年份。那一年,行业正从2008年金融危机后的缓慢复苏中走出,移动计…...

联发科2012年崛起:从功能机到智能机的转型与挑战

1. 从功能机到智能机的惊险一跃:联发科的2012年2012年,对于全球移动芯片行业来说,是几家欢喜几家愁的一年。诺基亚和黑莓的持续衰落,直接拖垮了像ST-Ericsson这样深度绑定的芯片供应商;即便是巨头如高通,也…...

西安石油大学仪光实践协会4月活动机械蝴蝶台灯

项目简介该项目使用stm32芯片设计了一个灯光,300减速,可灯光颜色变化,和电机转向控制。制作了一个简单有趣的动态可控台灯。使用电源控制ic芯片,可与连接电池,对电池进行充电,并且显示电池剩余电量。实现制…...

AMD Ryzen终极性能调优秘籍:5个高效调试技巧让你完全掌控处理器性能

AMD Ryzen终极性能调优秘籍:5个高效调试技巧让你完全掌控处理器性能 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址…...

从零部署私有化AI对话框架:igogpt架构解析与实战指南

1. 项目概述与核心价值最近在折腾AI应用部署的朋友,可能都听说过一个词叫“套壳ChatGPT”。这类项目通常是把OpenAI的API接口包装一下,做个Web界面,让用户能更方便地使用。但今天要聊的这个项目——igolaizola/igogpt,它给我的感觉…...

从AMD Ryzen数据误读看硬件市场分析:如何辨别数据信号与噪声

1. 从一则旧闻谈起:数据解读的陷阱与行业洞察2017年7月,一则关于AMD Ryzen处理器市场份额的新闻在科技圈引发了不小的讨论。当时,多家媒体援引第三方基准测试软件Passmark的数据,宣称AMD凭借新发布的Ryzen架构,正在从英…...

Obsidian Quiz Generator:用AI从笔记生成交互测验,打造学习闭环

1. 项目概述:用AI将笔记变成互动测验 如果你和我一样,是个重度Obsidian用户,同时又经常需要备考、复习或者制作教学材料,那你肯定体会过那种痛苦:面对几十上百页的笔记,想要生成一些高质量的练习题来检验学…...

TTS听觉校对法:技术写作质量提升的工程实践指南

1. 为什么我们需要“听”自己的文字:一个被忽视的校对革命作为一名写了十几年技术文档和博客的老兵,我敢说,最让我头疼的不是构思,也不是码字,而是最后那一步——校对。你肯定也经历过:一封精心撰写的邮件发…...

ATE PCB组装:半导体测试中的精密工艺与挑战解析

1. ATE PCB组装:半导体测试的基石与挑战 在半导体行业,一颗芯片从设计到最终封装出厂,其性能与可靠性的验证是决定产品成败的最后一环。随着芯片工艺节点不断微缩,集成度呈指数级增长,对测试环节的要求也达到了前所未有…...

无线充电技术:从手机标配到多场景应用的挑战与机遇

1. 无线充电市场现状:繁荣表象下的应用困境手机无线充电,现在几乎成了旗舰机的标配。从咖啡馆、机场到汽车中控台,充电垫的身影随处可见。作为一名在电源管理和消费电子领域摸爬滚打了十几年的工程师,我亲眼见证了Qi标准从实验室走…...

Blender 3MF插件:5分钟掌握3D打印文件格式转换的完整方案

Blender 3MF插件:5分钟掌握3D打印文件格式转换的完整方案 【免费下载链接】Blender3mfFormat Blender add-on to import/export 3MF files 项目地址: https://gitcode.com/gh_mirrors/bl/Blender3mfFormat 你是否曾经在Blender中精心设计了完美的3D模型&…...

从1991年Wescon展会看测试测量技术演进:DSP、GPIB与经典仪器解析

1. 从一份老杂志的周五测验说起:重温1991年Wescon展会的测试测量世界最近在整理资料时,翻到一篇2016年《EE Times》上的老文章,标题叫“周五测验:Wescon测试产品”。文章的核心是带读者回顾1991年EDN杂志为Wescon展会出版的一份厚…...

从专利数量到创新质量:解读中国专利申请背后的产业逻辑与价值评估

1. 从“专利数量”到“创新质量”:一个从业者的深度观察最近和几位在半导体和物联网领域做研发的朋友聊天,话题不约而同地转到了知识产权上。大家普遍的感觉是,现在无论是产品立项、技术合作还是出海竞争,专利已经从一个“锦上添花…...

【领域驱动设计 开篇】零 来源及学习路径

DDD是什么 2003 年,Eric Evans 写了《领域驱动设计:软件核心复杂性应对之道》一书,正式提出了这种方法。领域驱动设计的英文是 Domain-Driven Design,简称 DDD。 按照作者自己的说法,“DDD 是一种开发复杂软件的方法”…...