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

【C++】022、深拷贝浅拷贝

一、深拷贝&浅拷贝

深拷贝与浅拷贝的根本区别,不在于复制了多少字节,在于拷贝后,两个对象是否共享了同一个底层资源

  • 浅拷贝:仅仅逐字节复制对象本身的内存布局,两个对象中的指针成员指向同一块动态内存。

  • 深拷贝:递归复制指针所指向的全部底层数据,为副本独立分配一块新的动态内存。

二、代码解析与示意图

1、浅拷贝,编译器默认行为

原始对象 s1 (栈) 堆内存 (0x1000) ┌─────────────┐ ┌─────────────┐ │ char* m_ptr │──────│ H E L L O \0│ │ int m_len=5 │ └─────────────┘ └─────────────┘ 拷贝对象 s2 (栈) ┌─────────────┐ │ char* m_ptr │──────(指向同一块 0x1000) ❌ 共享了! │ int m_len=5 │ └─────────────┘

2、深拷贝,手动处理

原始对象 s1 (栈) 堆内存 (0x1000) ┌─────────────┐ ┌─────────────┐ │ char* m_ptr │──────│ H E L L O \0│ │ int m_len=5 │ └─────────────┘ └─────────────┘ 拷贝对象 s2 (栈) 堆内存 (0x2000) ✅ 独立新家! ┌─────────────┐ ┌─────────────┐ │ char* m_ptr │──────│ H E L L O \0│ │ int m_len=5 │ └─────────────┘ └─────────────┘

3、代码演示

#include <cstring> #include <iostream> class MyString { private: char* m_data; // 指向堆内存的指针 size_t m_len; public: // ---------- 构造函数 ---------- MyString(const char* str = "") : m_len(strlen(str)), m_data(new char[m_len + 1]) { strcpy(m_data, str); std::cout << "构造: " << m_data << std::endl; } // ---------- 析构函数 ---------- ~MyString() { delete[] m_data; // 释放堆内存 std::cout << "析构: 释放内存" << std::endl; } // ========================================== // ❌ 浅拷贝(编译器默认生成,如果不禁用会出大问题) // ========================================== // 默认拷贝构造:MyString(const MyString& other) // 默认拷贝赋值:MyString& operator=(const MyString& other) // 它们会执行:m_data = other.m_data; (逐字节复制指针值) // 后果:两个对象指向同一块内存,析构时双重释放(Double Free)-> 崩溃! // ========================================== // ✅ 深拷贝(必须手动实现) // ========================================== // 1. 拷贝构造函数(深拷贝) MyString(const MyString& other) : m_len(other.m_len), m_data(new char[m_len + 1]) { strcpy(m_data, other.m_data); // 复制数据到新内存 std::cout << "深拷贝构造: " << m_data << std::endl; } // 2. 拷贝赋值运算符(深拷贝 + 自我赋值检测) MyString& operator=(const MyString& other) { std::cout << "深拷贝赋值: " << other.m_data << std::endl; if (this == &other) { // 防止自我赋值:s = s; return *this; } // 步骤1:释放旧资源 delete[] m_data; // 步骤2:分配新内存并复制数据 m_len = other.m_len; m_data = new char[m_len + 1]; strcpy(m_data, other.m_data); return *this; } // 获取数据(打印用) const char* c_str() const { return m_data; } }; // ========== 测试代码 ========== int main() { MyString s1("Hello"); MyString s2(s1); // 调用深拷贝构造(独立内存) MyString s3("World"); s3 = s1; // 调用深拷贝赋值(s3 释放旧内存,申请新内存) std::cout << "s1: " << s1.c_str() << std::endl; std::cout << "s2: " << s2.c_str() << std::endl; std::cout << "s3: " << s3.c_str() << std::endl; return 0; // 三个对象析构,各自释放各自的内存,安全! }

三、工程实战原则

1、三/五法则:

  • 如果一个类需要自定义析构函数释放资源,那么一定需要自定义拷贝构造函数和拷贝赋值运算符,以及C11后的移动构造和移动赋值

  • 反之,如果不需要析构函数,就不要自定义拷贝操作,直接让编译器生成默认的浅拷贝。

2、在业务代码中,使用string替换char*,用vector替换动态数组

  • 因为他们内部已实现了深拷贝。

四、C11中的移动语义

  • C++中的移动语义既不是深拷贝,也不是浅拷贝,只是一种资源窃取

解析:

  • 浅拷贝会导致两个对象共享资源,存在重复释放资源危险

  • 深拷贝会复制一份数据(昂贵)

  • 移动是把源对象的指针直接置空(nullptr),并把指针值偷给目标对象,移动后的源对象处于有效但未指定的状态,他的析构函数不会再释放资源。

移动语义完美的解决了深拷贝的性能开销问题。

http://www.cnnetsun.cn/news/3824941.html

相关文章:

  • STM32F407 USB Custom HID免驱通信:从CubeMX配置到Python上位机实战
  • 基于MATLAB SISO Tool的PID控制器交互式设计与仿真实践
  • C++代码冗余消除与性能优化实战
  • 医学大模型深度研究报告(2026年8月版第二部分)
  • Java实现智能集群仿真:Boids模型与并发优化实践
  • 【2026热端攻防系列 11/12】前端AI风控攻防实战:验证码缺陷、人机验证绕过、智能爬虫对抗与企业智能风控加固方案
  • 利用废旧电脑搭建个人服务器:Ubuntu与frp内网穿透实战
  • 十字军之王II双字节补丁:终极中文显示解决方案
  • ncmdumpGUI:3步解锁网易云音乐NCM文件,让音乐真正属于你
  • SpringBoot+Vue构建体育商品智能推荐系统实践
  • 作业2222
  • 数论核心:从算术基本定理到约数个数、和与最大公约数的计算与应用
  • 技术人如何构建可持续的个人系统:从精力管理到职业复利
  • 告别格式烦恼:5分钟搞定国家自然科学基金LaTeX模板终极指南
  • 【AI客服系统搭建终极指南】:20年架构师亲授5大避坑法则与3套高转化落地模板
  • Unity离线语音识别实战:基于Whisper.unity的集成、优化与多平台部署指南
  • Synapse Term:让 AI 直接操作你已经连好的 SSH、容器和 WSL 终端
  • 2025届最火的十大AI科研方案解析与推荐
  • CTF无数字字母命令执行:绕过字符集限制的底层原理与实战
  • 文献综述方法论:高效检索与批判性分析技巧
  • 几何题配图 API 怎么选?从文本生成到可编辑源文件的接入指南
  • 客户沟通音视频纪要生成工具功能分析——基于商务场景的实证比较
  • SpringBoot在线考试系统开发实战与架构设计
  • 串行与并行、同步与异步:嵌入式通信核心概念全解析
  • VS2022+QT5.15.2进行CAN通讯上位机开发(2)
  • 终极Windows界面优化指南:用ExplorerPatcher完美定制任务栏与开始菜单
  • Linux系统root密码重置全攻略:GRUB单用户、Live CD与救援模式详解
  • 算法竞赛核心考点精讲:线段树、线性基与状压DP实战解析
  • 苹果AI战略转向:Siri智能化升级与商业模式重塑
  • 利用iPhone与ARKit实现低成本高精度3D面部表情捕捉全流程