C++ 笔记 深赋值 浅赋值(面向对象)

在 C++ 面向对象编程中,赋值操作 是对象间数据传递的核心行为,而 ** 浅赋值(浅拷贝) 深赋值(深拷贝)是处理对象成员(尤其是指针成员)的两种核心机制。二者的核心区别在于:是否为指针成员分配独立的内存空间,错误使用浅赋值会导致内存泄漏、重复释放、数据污染等严重问题。

本文将从概念、原理、代码示例、应用场景四个维度,彻底讲清浅赋值与深赋值的区别与实践。

一、基础概念:什么是赋值操作?

C++ 中,当使用=运算符将一个对象赋值给另一个同类型对象时,会调用拷贝赋值运算符

  • 若我们不手动定义,编译器会自动生成一个默认拷贝赋值运算符,这是浅赋值的源头;
  • 浅赋值与深赋值,本质是拷贝赋值运算符对对象成员的两种不同处理方式。

二、浅赋值(浅拷贝)

1. 核心定义

浅赋值是逐字节拷贝 对象的成员变量:对于普通成员(int、char 等)直接拷贝值;对于指针成员 ,仅拷贝指针存储的内存地址不拷贝指针指向的实际数据

2. 核心特点

  1. 两个对象的指针成员指向同一块内存空间
  2. 操作简单,编译器自动生成的默认赋值运算符就是浅赋值;
  3. 致命缺陷:修改一个对象的指针数据,另一个对象会同步变化;对象销毁时,同一块内存会被重复释放,导致程序崩溃。

3. 代码示例(浅赋值的问题)

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

// 测试类:包含指针成员
class Student {
public:
    char* name; // 指针成员(核心风险点)
    int age;

    // 构造函数:为指针分配内存
    Student(const char* n, int a) {
        age = a;
        // 为name指针分配堆内存
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    // 析构函数:释放指针内存
    ~Student() {
        // 浅赋值下,同一块内存会被释放两次!
        delete[] name;
        cout << "对象已销毁,内存释放成功" << endl;
    }
};

int main() {
    Student s1("张三", 20);
    Student s2("李四", 18);

    // 编译器自动生成的默认赋值运算符 → 浅赋值
    s2 = s1; 

    // 问题1:修改s1的name,s2的name同步变化(指向同一块内存)
    strcpy(s1.name, "王五");
    cout << "s1姓名:" << s1.name << endl; // 输出:王五
    cout << "s2姓名:" << s2.name << endl; // 输出:王五(预期应为张三)

    // 问题2:程序结束时,s1和s2的析构函数会重复释放同一块内存 → 程序崩溃
    return 0;
}

4. 浅赋值适用场景

仅当类中没有指针、引用等动态分配的成员变量时,浅赋值完全安全(如仅包含 int、double、普通数组的类)。

三、深赋值(深拷贝)

1. 核心定义

深赋值是手动重载拷贝赋值运算符 :对于指针成员,先分配一块独立的新内存 ,再将原指针指向的数据拷贝到新内存中。最终两个对象的指针成员指向不同的内存空间

2. 核心特点

  1. 两个对象拥有独立的指针数据,互不干扰;
  2. 避免内存重复释放、数据污染问题;
  3. 需要手动编写拷贝赋值运算符,不能依赖编译器默认实现。

3. 代码示例(深赋值解决问题)

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

class Student {
public:
    char* name;
    int age;

    // 构造函数
    Student(const char* n, int a) {
        age = a;
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    // 析构函数
    ~Student() {
        delete[] name;
        cout << "对象已销毁,内存释放成功" << endl;
    }

    // 【关键】手动重载拷贝赋值运算符 → 实现深赋值
    Student& operator=(const Student& s) {
        // 1. 防止自赋值(如 s1 = s1)
        if (this == &s) {
            return *this;
        }

        // 2. 释放当前对象原有的指针内存(避免内存泄漏)
        delete[] this->name;

        // 3. 分配新内存,拷贝数据(深赋值核心步骤)
        this->age = s.age;
        this->name = new char[strlen(s.name) + 1];
        strcpy(this->name, s.name);

        // 4. 返回当前对象(支持连续赋值:s1 = s2 = s3)
        return *this;
    }
};

int main() {
    Student s1("张三", 20);
    Student s2("李四", 18);

    // 调用重载的赋值运算符 → 深赋值
    s2 = s1; 

    // 验证:修改s1的数据,s2不受影响
    strcpy(s1.name, "王五");
    cout << "s1姓名:" << s1.name << endl; // 输出:王五
    cout << "s2姓名:" << s2.name << endl; // 输出:张三(符合预期)

    // 程序结束:两个对象的指针指向不同内存,分别释放,无崩溃
    return 0;
}

4. 深赋值核心步骤(必记)

重载拷贝赋值运算符时,必须遵循 4 步标准流程:

  1. 判断自赋值:避免自己释放自己的内存;
  2. 释放原有内存:防止当前对象的指针内存泄漏;
  3. 分配新内存 + 拷贝数据:实现真正的深赋值;
  4. 返回当前对象引用:支持连续赋值语法。

四、浅赋值 vs 深赋值:核心对比表

表格

特性 浅赋值(浅拷贝) 深赋值(深拷贝)
实现方式 编译器自动生成,无需手动编写 必须手动重载拷贝赋值运算符
指针成员处理 仅拷贝内存地址,共享数据 分配新内存,拷贝数据,独立存储
数据安全性 低,易出现数据污染、重复释放 高,对象数据完全独立
内存开销 稍大(需要分配独立内存)
适用场景 无指针 / 动态内存的类 包含指针、动态数组、字符串的类

五、关键总结

  1. 编译器默认的赋值都是浅赋值 ,只要类中有指针成员、动态分配内存new/malloc),必须手动实现深赋值
  2. 浅赋值的核心风险:指针共享内存,导致数据篡改、内存重复释放;
  3. 深赋值的核心逻辑:为指针分配独立内存,拷贝实际数据,从根源解决浅赋值问题;
  4. 面向对象编程中,遵循三法则:如果类需要手动实现析构函数、拷贝构造函数、拷贝赋值运算符中的任意一个,通常三个都需要手动实现(核心都是为了处理深拷贝)。

总结

  1. 浅赋值逐字节拷贝,指针共享内存,仅适用于无动态成员的类;
  2. 深赋值手动重载赋值运算符,指针独立分配内存,解决动态内存安全问题;
  3. 含指针 / 动态内存的类,禁止使用编译器默认浅赋值,必须实现深赋值。
相关推荐
烛之武4 小时前
SpringCloud基础(上)
笔记·spring·spring cloud
今儿敲了吗4 小时前
算法复盘——差分
数据结构·c++·笔记·学习·算法
Lyyaoo.4 小时前
【JAVA基础面经】JAVA的面向对象特性
java·开发语言·windows
小温冲冲4 小时前
Qt WindowContainer 完整实战示例:QWidget 嵌入 QML
开发语言·数据库·qt
_李小白4 小时前
【OSG学习笔记】Day 23: ClipNode(动态裁剪)
android·笔记·学习
MyBFuture4 小时前
Halcon条形码与二维码识别全攻略
开发语言·人工智能·halcon·机器视觉
郭涤生4 小时前
std::async 和 std::future的使用
c++
墨韵流芳5 小时前
CCF-CSP第41次认证第一题——平衡数
c++·算法·ccf·平衡数
AI+程序员在路上5 小时前
新手进入嵌入式行业方法与方向选择
c语言·开发语言·单片机·嵌入式硬件