news 2026/1/30 4:04:32

深拷贝、浅拷贝

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深拷贝、浅拷贝

一、先理解核心概念:拷贝的本质

拷贝的目的是创建一个新对象,使其与原对象的“内容”一致。而深浅拷贝的核心差异,在于对「堆内存资源」的处理方式:

  • 栈内存(如int、char、指针变量本身):所有拷贝都会复制,无深浅之分;
  • 堆内存(如new出来的数组/对象):浅拷贝只复制“指向堆内存的指针”,深拷贝会复制“指针指向的堆内存内容”。

二、浅拷贝(Shallow Copy):默认的“表层拷贝”

1. 定义

浅拷贝是C++编译器默认生成的拷贝行为:仅复制对象的「栈上成员」(包括指针变量的地址值),不复制指针指向的「堆内存资源」。新对象和原对象共享同一块堆内存,相当于“多个管家管同一个房子”。

2. 浅拷贝的问题:资源冲突(必现!)

如果对象包含堆内存资源,浅拷贝会导致两个致命问题:

  • 重复释放:两个对象析构时,都会释放同一块堆内存,触发未定义行为(程序崩溃);
  • 数据篡改:修改新对象的堆数据,原对象的堆数据也会被改(共享资源)。
3. 示例:浅拷贝的崩溃演示

我们用一个「管理动态数组」的类,演示浅拷贝的问题:

#include<iostream>#include<cstring>usingnamespacestd;// 管理动态字符数组的类(含堆内存资源)classStringWrapper{private:char*str;// 指针指向堆内存的字符数组public:// 构造函数:分配堆内存,初始化字符串StringWrapper(constchar*s){cout<<"构造:分配堆内存"<<endl;str=newchar[strlen(s)+1];// 堆内存分配strcpy(str,s);}// 析构函数:释放堆内存~StringWrapper(){cout<<"析构:释放堆内存"<<endl;if(str){delete[]str;// 释放堆数组str=nullptr;}}// 打印字符串(方便测试)voidprint(){cout<<str<<endl;}// 手动修改堆数据(演示共享问题)voidset(constchar*s){strcpy(str,s);}// 【注意】编译器默认生成的浅拷贝构造/赋值运算符:// StringWrapper(const StringWrapper& other) = default;// StringWrapper& operator=(const StringWrapper& other) = default;};intmain(){// 原对象:分配堆内存,存储"hello"StringWrappers1("hello");// 浅拷贝:s2的str指针和s1的str指向同一块堆内存StringWrapper s2=s1;// 问题1:修改s2的堆数据,s1也被篡改(共享资源)s2.set("world");cout<<"s1的值:";s1.print();// 输出world(而非hello)// 问题2:函数结束,s2先析构→释放堆内存;s1析构→重复释放同一块内存→崩溃return0;}
运行结果(崩溃+错误):
构造:分配堆内存 s1的值:world 析构:释放堆内存 析构:释放堆内存 // 随后程序崩溃(double free or corruption,重复释放内存)

三、深拷贝(Deep Copy):独立的“完整拷贝”

1. 定义

深拷贝需要手动实现:不仅复制对象的栈上成员,还会为新对象重新分配一块独立的堆内存,并将原对象堆内存中的内容完整复制过去。新对象和原对象拥有完全独立的堆资源,相当于“复制房子的所有内容,盖一栋新的一模一样的房子”。

2. 深拷贝的实现:遵循“三法则/五法则”

要实现深拷贝,必须手动编写:

  • 拷贝构造函数(创建新对象时);
  • 拷贝赋值运算符(已有对象赋值时);
  • 析构函数(释放堆资源,已有)。
3. 示例:实现深拷贝解决问题

基于上面的类,补充深拷贝的实现(关键修改处标注):

#include<iostream>#include<cstring>usingnamespacestd;classStringWrapper{private:char*str;public:// 构造函数StringWrapper(constchar*s){cout<<"构造:分配堆内存"<<endl;str=newchar[strlen(s)+1];strcpy(str,s);}// 【核心1】深拷贝构造函数StringWrapper(constStringWrapper&other){cout<<"深拷贝构造:分配新堆内存"<<endl;// 步骤1:为新对象分配独立的堆内存str=newchar[strlen(other.str)+1];// 步骤2:复制原对象堆内存的内容(而非指针)strcpy(str,other.str);}// 【核心2】深拷贝赋值运算符(遵循“先释放、再分配、最后复制”)StringWrapper&operator=(constStringWrapper&other){cout<<"深拷贝赋值:释放旧内存,分配新内存"<<endl;// 步骤0:防止自赋值(如s1 = s1)if(this==&other)return*this;// 步骤1:释放当前对象的旧堆内存delete[]str;// 步骤2:分配新堆内存str=newchar[strlen(other.str)+1];// 步骤3:复制原对象的堆内容strcpy(str,other.str);return*this;}// 析构函数~StringWrapper(){cout<<"析构:释放堆内存"<<endl;if(str){delete[]str;str=nullptr;}}voidprint(){cout<<str<<endl;}voidset(constchar*s){strcpy(str,s);}};intmain(){StringWrappers1("hello");// 深拷贝构造:s2有独立的堆内存StringWrapper s2=s1;// 修改s2的堆数据,s1不受影响(资源独立)s2.set("world");cout<<"s1的值:";s1.print();// 输出hellocout<<"s2的值:";s2.print();// 输出world// 赋值运算符测试:深拷贝StringWrappers3("test");s3=s1;// 调用深拷贝赋值cout<<"s3的值:";s3.print();// 输出hello// 析构:三个对象释放各自的堆内存,无冲突return0;}
运行结果(安全无崩溃):
构造:分配堆内存 深拷贝构造:分配新堆内存 s1的值:hello s2的值:world 构造:分配堆内存 深拷贝赋值:释放旧内存,分配新内存 s3的值:hello 析构:释放堆内存 析构:释放堆内存 析构:释放堆内存

四、深拷贝 vs 浅拷贝:核心区别对比

特性浅拷贝深拷贝
拷贝内容仅复制栈上成员(如指针地址)复制栈上成员 + 重新分配堆内存并复制内容
资源归属新对象与原对象共享堆资源新对象与原对象拥有独立堆资源
安全性有堆资源时必出问题(重复释放/篡改)安全,无资源冲突
效率高(仅复制表层数据)稍低(需分配堆内存+复制内容)
实现方式编译器默认生成(=default)手动实现拷贝构造+赋值运算符
适用对象无堆资源的纯栈对象(如Point{x,y})含堆资源的对象(如动态数组、自定义指针)

五、适用场景:什么时候用深/浅拷贝?

1. 浅拷贝的适用场景

只有当对象无堆内存资源时,才适合用浅拷贝(编译器默认生成即可):

  • 纯栈对象:如struct Point { int x; int y; }class Student { string name; int age; }(string内部已实现深拷贝,对外部是浅拷贝);
  • 明确需要共享资源的场景(需配合引用计数):如std::shared_ptr(浅拷贝指针,但通过引用计数避免重复释放)。
2. 深拷贝的适用场景

只要对象包含堆内存资源(自定义指针、动态数组、手动new的对象),必须实现深拷贝:

  • 自定义容器类(如自定义栈、队列,内部有动态数组);
  • 管理系统资源的类(如文件句柄、网络连接的封装类,需独立管理资源);
  • 要求“对象独立”的场景(修改新对象不影响原对象)。

六、补充:C++11后的优化——移动语义(替代深拷贝)

深拷贝的效率问题(分配内存+复制内容),在处理临时对象(如函数返回值)时尤为明显。C++11引入的移动语义(Move Semantics)可以替代深拷贝:

  • 移动构造/赋值:直接“偷走”临时对象的堆资源(转移指针所有权),无需分配新内存,效率和浅拷贝一样高;
  • 实现:编写StringWrapper(StringWrapper&& other)(移动构造)和operator=(StringWrapper&& other)(移动赋值),用std::move转移资源。

示例(简化的移动构造):

// 移动构造函数:转移堆资源,而非复制StringWrapper(StringWrapper&&other)noexcept{cout<<"移动构造:转移堆资源"<<endl;str=other.str;// 直接拿走指针other.str=nullptr;// 原对象放弃资源所有权,避免析构释放}

七、总结(关键点回顾)

  1. 核心区别:深浅拷贝的本质是「是否复制堆内存资源」——浅拷贝复制指针地址(共享资源),深拷贝复制指针指向的内容(独立资源);
  2. 浅拷贝:编译器默认生成,仅适用于无堆资源的纯栈对象,有堆资源时必须禁用(=delete);
  3. 深拷贝:手动实现拷贝构造+赋值运算符,适用于含堆资源的对象,保证资源独立、安全;
  4. 优化:临时对象场景用移动语义替代深拷贝,兼顾安全和效率。

简单来说:只要你的类里有new/malloc,就必须手动实现深拷贝;如果没有,用编译器默认的浅拷贝就够了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/27 18:58:43

定義 type hints 時,你在設計;寫實作時,你在執行

類型提示與實作&#xff1a;設計與執行的雙重奏引言&#xff1a;軟體工程的雙重性在現代軟體開發中&#xff0c;類型提示(Type Hints)與實作(Implementation)代表著軟體設計過程中的兩個不同維度&#xff0c;它們之間的關係猶如建築藍圖與實際施工&#xff0c;或樂譜與演奏之間…

作者头像 李华
网站建设 2026/1/28 19:31:27

FAANG內部Python專案:達成90%+ Type Hints覆蓋率的最佳實踐

FAANG內部Python專案&#xff1a;達成90% Type Hints覆蓋率的最佳實踐引言&#xff1a;類型提示的現代意義在當今大型軟體開發環境中&#xff0c;程式碼的可維護性和可讀性已成為決定專案成敗的關鍵因素。FAANG&#xff08;Facebook/ Meta、Amazon、Apple、Netflix、Google&…

作者头像 李华
网站建设 2026/1/27 1:49:18

铟片和碳纤维导热片对比

&#x1f393;作者简介&#xff1a;科技自媒体优质创作者 &#x1f310;个人主页&#xff1a;莱歌数字-CSDN博客 &#x1f48c;公众号&#xff1a;莱歌数字 &#x1f4f1;个人微信&#xff1a;yanshanYH 211、985硕士&#xff0c;职场15年 从事结构设计、热设计、售前、产品设…

作者头像 李华
网站建设 2026/1/28 10:46:14

基于知识图谱的AI Agent推理系统

基于知识图谱的AI Agent推理系统 关键词:知识图谱、AI Agent、推理系统、语义理解、知识推理 摘要:本文深入探讨了基于知识图谱的AI Agent推理系统。首先介绍了该系统提出的背景、目的和适用读者范围,对相关术语进行了清晰定义。接着阐述了知识图谱和AI Agent的核心概念及它…

作者头像 李华
网站建设 2026/1/25 23:18:54

环保公益项目评估AI:社会效益量化新方式

环保公益项目评估AI&#xff1a;社会效益量化新方式 在一片曾经荒芜的山地&#xff0c;无人机正缓缓掠过树冠层&#xff0c;拍摄下最新一轮植被恢复的影像。与此同时&#xff0c;千里之外的数据中心里&#xff0c;一个深度学习模型正在几秒内完成对数百平方公里遥感图像的分析—…

作者头像 李华