【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),并把指针值偷给目标对象,移动后的源对象处于有效但未指定的状态,他的析构函数不会再释放资源。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值