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

【C++】022、深拷贝浅拷贝
一、深拷贝浅拷贝深拷贝与浅拷贝的根本区别不在于复制了多少字节在于拷贝后两个对象是否共享了同一个底层资源浅拷贝仅仅逐字节复制对象本身的内存布局两个对象中的指针成员指向同一块动态内存。深拷贝递归复制指针所指向的全部底层数据为副本独立分配一块新的动态内存。二、代码解析与示意图1、浅拷贝编译器默认行为原始对象 s1 (栈) 堆内存 (0x1000) ┌─────────────┐ ┌─────────────┐ │ char* m_ptr │──────│ H E L L O \0│ │ int m_len5 │ └─────────────┘ └─────────────┘ 拷贝对象 s2 (栈) ┌─────────────┐ │ char* m_ptr │──────(指向同一块 0x1000) ❌ 共享了 │ int m_len5 │ └─────────────┘2、深拷贝手动处理原始对象 s1 (栈) 堆内存 (0x1000) ┌─────────────┐ ┌─────────────┐ │ char* m_ptr │──────│ H E L L O \0│ │ int m_len5 │ └─────────────┘ └─────────────┘ 拷贝对象 s2 (栈) 堆内存 (0x2000) ✅ 独立新家 ┌─────────────┐ ┌─────────────┐ │ char* m_ptr │──────│ H E L L O \0│ │ int m_len5 │ └─────────────┘ └─────────────┘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并把指针值偷给目标对象移动后的源对象处于有效但未指定的状态他的析构函数不会再释放资源。移动语义完美的解决了深拷贝的性能开销问题。