C++ 拷贝构造函数与赋值运算符:深拷贝与浅拷贝的核心辨析

C++ 拷贝构造函数与赋值运算符:深拷贝与浅拷贝的核心辨析

C++ 拷贝构造函数与赋值运算符:深拷贝与浅拷贝的核心辨析

在这里插入图片描述

💡 学习目标:掌握拷贝构造函数与赋值运算符的定义及调用场景,理解深拷贝与浅拷贝的本质区别,能够在实际开发中避免内存泄漏与野指针问题。
💡 学习重点:拷贝构造函数的触发条件、浅拷贝的缺陷、深拷贝的实现方法、赋值运算符的重载原则。

一、拷贝构造函数的概念与触发场景

结论:拷贝构造函数是一种特殊的构造函数,用于通过一个已存在的对象创建一个新对象,其参数必须是本类对象的常量引用(const 类名&)。

1.1 拷贝构造函数的语法格式

class 类名 {public:// 普通构造函数 类名(参数列表);// 拷贝构造函数 类名(const 类名& other);};

⚠️ 注意事项

  1. 拷贝构造函数的参数必须是常量引用,使用 const 防止实参被修改,使用引用避免无限递归调用拷贝构造函数。
  2. 如果没有手动定义拷贝构造函数,编译器会自动生成一个默认拷贝构造函数,实现简单的成员变量值拷贝。

1.2 拷贝构造函数的触发条件

拷贝构造函数在以下三种场景下会被自动调用:

  1. 使用一个对象初始化另一个新对象
  2. 函数参数为类对象(值传递)
  3. 函数返回值为类对象(值传递)
1.2.1 代码演示:触发场景验证
#include<iostream>#include<string>usingnamespace std;classPerson{public: string name;int age;// 普通构造函数Person(string n,int a):name(n),age(a){ cout <<"普通构造函数被调用"<< endl;}// 拷贝构造函数Person(const Person& other){this->name = other.name;this->age = other.age; cout <<"拷贝构造函数被调用"<< endl;}};// 场景2:函数参数为类对象(值传递)voidfunc(Person p){ cout <<"函数内对象姓名:"<< p.name << endl;}// 场景3:函数返回值为类对象(值传递) Person getPerson(){ Person p("王五",30);return p;}intmain(){// 普通构造创建对象 Person p1("张三",20);// 场景1:使用 p1 初始化 p2 Person p2 = p1;// 场景2:值传递传递对象func(p1);// 场景3:值传递返回对象 Person p3 =getPerson();return0;}
1.2.2 运行结果
普通构造函数被调用 拷贝构造函数被调用 拷贝构造函数被调用 函数内对象姓名:张三 普通构造函数被调用 拷贝构造函数被调用 

二、浅拷贝与深拷贝的核心区别

💡 浅拷贝是指仅拷贝对象的成员变量值,深拷贝是指不仅拷贝成员变量值,还为指针成员重新分配内存并拷贝数据,二者的核心差异体现在指针成员的处理上。

2.1 浅拷贝的实现与缺陷

默认拷贝构造函数和默认赋值运算符实现的是浅拷贝,当类中包含指针成员时,浅拷贝会导致多个对象的指针指向同一块内存,引发严重问题。

2.1.1 浅拷贝的问题代码演示
#include<iostream>#include<cstring>usingnamespace std;classString{private:char* str;// 指针成员public:// 普通构造函数:分配堆内存String(constchar* s =""){ str =newchar[strlen(s)+1];strcpy(str, s); cout <<"普通构造函数:分配内存"<< endl;}// 析构函数:释放堆内存~String(){delete[] str; cout <<"析构函数:释放内存"<< endl;}// 打印字符串voidshow(){ cout << str << endl;}};intmain(){ String s1("Hello C++");// 浅拷贝:s2.str 与 s1.str 指向同一块内存 String s2 = s1; s1.show(); s2.show();return0;}
2.1.2 运行结果与问题分析
普通构造函数:分配内存 Hello C++ Hello C++ 析构函数:释放内存 析构函数:释放内存 

问题1:重复释放内存

  • s1s2 的指针指向同一块堆内存。
  • 程序结束时,两个对象的析构函数会先后释放同一块内存,导致内存崩溃

问题2:修改一个对象影响另一个

  • 如果修改 s1.str 指向的内容,s2.str 的内容也会被改变,违背对象的独立性。

2.2 深拷贝的实现与优势

深拷贝的核心是为指针成员重新分配内存,并将原对象指针指向的数据拷贝到新内存中,从而保证每个对象的指针成员都有独立的内存空间。

2.2.1 深拷贝的实现:重写拷贝构造函数
#include<iostream>#include<cstring>usingnamespace std;classString{private:char* str;public:String(constchar* s =""){ str =newchar[strlen(s)+1];strcpy(str, s); cout <<"普通构造函数:分配内存"<< endl;}// 手动实现深拷贝构造函数String(const String& other){// 为新对象分配独立内存this->str =newchar[strlen(other.str)+1];// 拷贝数据strcpy(this->str, other.str); cout <<"深拷贝构造函数:分配独立内存"<< endl;}~String(){delete[] str; cout <<"析构函数:释放内存"<< endl;}voidshow(){ cout << str << endl;}// 提供修改字符串的方法,验证独立性voidsetStr(constchar* s){delete[] str; str =newchar[strlen(s)+1];strcpy(str, s);}};intmain(){ String s1("Hello C++"); String s2 = s1;// 调用深拷贝构造函数 cout <<"修改前:"<< endl; s1.show(); s2.show();// 修改 s1 的内容 s1.setStr("Hello Deep Copy"); cout <<"修改后:"<< endl; s1.show(); s2.show();return0;}
2.2.2 运行结果与优势分析
普通构造函数:分配内存 深拷贝构造函数:分配独立内存 修改前: Hello C++ Hello C++ 修改后: Hello Deep Copy Hello C++ 析构函数:释放内存 析构函数:释放内存 

核心优势

  1. 内存独立s1s2 的指针指向不同的内存空间,修改一个对象不会影响另一个。
  2. 避免重复释放:析构函数释放的是各自独立的内存,不会导致内存崩溃。

三、赋值运算符重载与深拷贝

💡 赋值运算符(=)的默认行为也是浅拷贝,当类中包含指针成员时,必须手动重载赋值运算符并实现深拷贝,其实现逻辑与深拷贝构造函数类似,但需要处理自赋值问题。

3.1 赋值运算符重载的语法与原则

3.1.1 核心语法
类名&operator=(const 类名& other){// 1. 处理自赋值if(this==&other){return*this;}// 2. 释放当前对象的原有内存delete[]this->指针成员;// 3. 分配新内存并拷贝数据this->指针成员 =new 类型[大小]; 拷贝数据逻辑;// 4. 返回当前对象的引用,支持链式赋值return*this;}
3.1.2 核心原则
  1. 处理自赋值:防止 a = a 这种情况导致内存提前释放。
  2. 释放原有内存:避免内存泄漏。
  3. 返回对象引用:支持链式赋值(如 a = b = c)。

3.2 代码演示:赋值运算符的深拷贝实现

#include<iostream>#include<cstring>usingnamespace std;classString{private:char* str;public:String(constchar* s =""){ str =newchar[strlen(s)+1];strcpy(str, s); cout <<"普通构造函数:分配内存"<< endl;}// 深拷贝构造函数String(const String& other){this->str =newchar[strlen(other.str)+1];strcpy(this->str, other.str); cout <<"深拷贝构造函数:分配独立内存"<< endl;}// 重载赋值运算符,实现深拷贝 String&operator=(const String& other){// 1. 处理自赋值if(this==&other){return*this;}// 2. 释放当前对象的原有内存delete[]this->str;// 3. 分配新内存并拷贝数据this->str =newchar[strlen(other.str)+1];strcpy(this->str, other.str); cout <<"赋值运算符重载:深拷贝"<< endl;// 4. 返回当前对象引用return*this;}~String(){delete[] str; cout <<"析构函数:释放内存"<< endl;}voidshow(){ cout << str << endl;}};intmain(){ String s1("Hello C++"); String s2;// 调用赋值运算符重载 s2 = s1; s1.show(); s2.show();// 测试链式赋值 String s3; s3 = s2 = s1; cout <<"链式赋值后 s3:"; s3.show();return0;}
3.2.1 运行结果
普通构造函数:分配内存 普通构造函数:分配内存 赋值运算符重载:深拷贝 Hello C++ Hello C++ 赋值运算符重载:深拷贝 链式赋值后 s3:Hello C++ 析构函数:释放内存 析构函数:释放内存 析构函数:释放内存 

四、拷贝构造函数与赋值运算符的区别

核心区别总结:拷贝构造函数用于创建新对象,赋值运算符用于给已存在的对象赋值,二者的调用时机和执行逻辑完全不同。

特性拷贝构造函数赋值运算符
调用时机用已有对象创建新对象时调用已存在的对象赋值时调用
参数要求必须是 const 类名&通常是 const 类名&
内存操作分配新内存,无原有内存需要释放需先释放当前对象原有内存
返回值无返回值(构造函数特性)必须返回 类名&,支持链式赋值
默认实现默认浅拷贝默认浅拷贝

五、深拷贝的实战案例:自定义数组类

💡 需求:设计一个自定义数组类 MyArray,支持动态扩容,要求实现深拷贝构造函数和赋值运算符重载,避免浅拷贝导致的内存问题。

5.1 需求分析

  1. 成员变量:int* arr(存储数组数据)、int size(数组大小)。
  2. 核心功能:构造函数分配内存、深拷贝构造、赋值运算符重载、打印数组、析构函数释放内存。
  3. 要求:保证多个对象的数组数据独立,修改一个对象的数组不影响其他对象。

5.2 完整代码实现

#include<iostream>#include<cstring>usingnamespace std;classMyArray{private:int* arr;// 动态数组指针int size;// 数组大小public:// 构造函数:创建指定大小的数组MyArray(int s =0):size(s){if(size >0){ arr =newint[size];// 初始化数组元素为 0memset(arr,0,sizeof(int)* size);}else{ arr =nullptr;} cout <<"构造函数:创建大小为 "<< size <<" 的数组"<< endl;}// 深拷贝构造函数MyArray(const MyArray& other){this->size = other.size;if(this->size >0){// 分配独立内存this->arr =newint[this->size];// 拷贝数组数据for(int i =0; i <this->size; i++){this->arr[i]= other.arr[i];}}else{this->arr =nullptr;} cout <<"深拷贝构造函数:拷贝大小为 "<< size <<" 的数组"<< endl;}// 赋值运算符重载:深拷贝 MyArray&operator=(const MyArray& other){if(this==&other){return*this;}// 释放当前对象原有内存if(this->arr !=nullptr){delete[]this->arr;}// 拷贝大小并分配新内存this->size = other.size;if(this->size >0){this->arr =newint[this->size];for(int i =0; i <this->size; i++){this->arr[i]= other.arr[i];}}else{this->arr =nullptr;} cout <<"赋值运算符重载:深拷贝数组"<< endl;return*this;}// 设置数组指定位置的值voidsetValue(int index,int value){if(index >=0&& index < size){ arr[index]= value;}else{ cout <<"⚠️ 索引越界"<< endl;}}// 打印数组voidprintArray(){if(arr ==nullptr){ cout <<"数组为空"<< endl;return;} cout <<"数组元素:";for(int i =0; i < size; i++){ cout << arr[i]<<" ";} cout << endl;}// 析构函数:释放内存~MyArray(){if(arr !=nullptr){delete[] arr; arr =nullptr;} cout <<"析构函数:释放数组内存"<< endl;}};intmain(){// 创建数组对象 MyArray arr1(5);// 设置数组值 arr1.setValue(0,10); arr1.setValue(1,20); arr1.setValue(2,30); arr1.printArray();// 深拷贝构造新对象 MyArray arr2 = arr1; arr2.setValue(0,100); cout <<"修改 arr2 后:"<< endl; arr1.printArray(); arr2.printArray();// 赋值运算符重载 MyArray arr3(3); arr3 = arr1; arr3.setValue(1,200); cout <<"修改 arr3 后:"<< endl; arr1.printArray(); arr3.printArray();return0;}

5.3 运行结果

构造函数:创建大小为 5 的数组 数组元素:10 20 30 0 0 深拷贝构造函数:拷贝大小为 5 的数组 修改 arr2 后: 数组元素:10 20 30 0 0 数组元素:100 20 30 0 0 构造函数:创建大小为 3 的数组 赋值运算符重载:深拷贝数组 修改 arr3 后: 数组元素:10 20 30 0 0 数组元素:10 200 30 0 0 析构函数:释放数组内存 析构函数:释放数组内存 析构函数:释放数组内存 

六、开发规范与常见问题

6.1 深拷贝的开发规范

  1. 三法则原则:当类中包含指针成员时,必须同时实现拷贝构造函数、赋值运算符重载、析构函数,三者缺一不可。
  2. 优先使用智能指针:C++11 及以上版本中,可以使用 unique_ptrshared_ptr 等智能指针替代裸指针,自动管理内存,避免手动实现深拷贝。
  3. 避免不必要的深拷贝:如果类中没有指针成员,直接使用默认的浅拷贝即可,无需手动实现深拷贝。

6.2 常见问题与解决方案

6.2.1 问题1:忘记处理自赋值

解决方案:在赋值运算符重载函数开头,添加 if (this == &other) 判断,直接返回 *this

6.2.2 问题2:释放内存后未置空指针

解决方案:析构函数或赋值运算符中释放内存后,将指针置为 nullptr,避免野指针。

6.2.3 问题3:深拷贝时内存分配失败

解决方案:可以添加异常处理逻辑,捕获内存分配失败的异常,增强程序健壮性。

七、本章总结

✅ 拷贝构造函数用于用已有对象创建新对象,赋值运算符用于给已存在对象赋值,二者的调用时机不同。
✅ 浅拷贝仅拷贝成员变量值,适用于无指针成员的类;深拷贝为指针成员分配独立内存,避免内存崩溃。
✅ 当类中包含指针成员时,必须遵循三法则原则,同时实现深拷贝构造函数、赋值运算符重载和析构函数。
✅ 合理使用深拷贝可以保证对象的独立性和内存安全,是 C++ 高级编程的核心技能之一。

Read more

Linux:早期操作系统的系统调用

Linux:早期操作系统的系统调用

相关阅读 Linuxhttps://blog.ZEEKLOG.net/weixin_45791458/category_12234591.html?spm=1001.2014.3001.5482 简介         本文将以Linux1.0为例说明早期操作系统的系统调用过程。         Linux1.0总共提供了135个系统调用(其中一些是保留或未实现),可以在源码路径linux-1.0/include/linux/sys.h下找到系统调用函数声明,在源码路径linux-1.0/include/linux/unistd.h下找到系统调用号定义。         下面列举出了一些系统调用的相关信息。 系统调用号系统调用函数名系统调用函数原型含义定义位置0sys_setupasmlinkage int sys_setup(void * BIOS)完成系统设备初始化(磁盘)、加载 RAM 盘、挂载根文件系统。linux-1.0/drivers/

By Ne0inhk
WSL2 + Ubuntu 22.04 全流程安装与避坑指南(适配 D 盘)

WSL2 + Ubuntu 22.04 全流程安装与避坑指南(适配 D 盘)

适用于:Windows 10/11 用户 目标:在 D 盘上部署完整可联网的 Ubuntu 22.04 + GPU 支持的开发环境 作者:jiahao(实际踩坑实践) 更新时间:2025-10 📋 一、前置条件检查 * Windows 已升级到 21H2 或更高版本; * 已安装 NVIDIA 显卡驱动 ≥ 510(支持 CUDA 12); * Windows 已安装 PowerShell 7+; * 本机具备管理员权限; * 可联网。 🧹 二、彻底清理旧版 WSL 环境 很多用户安装不干净是因为旧版本遗留。执行以下命令可完全重置。 1️⃣ 查看已安装发行版 wsl --list --all 如果看到:

By Ne0inhk
【鸿蒙2025领航者闯关】从技术突破到生态共建,开发者的成长与远航

【鸿蒙2025领航者闯关】从技术突破到生态共建,开发者的成长与远航

文章目录 * 前言 * 第一章 鸿蒙开发入门:认知全场景操作系统的核心魅力 * 1.1 鸿蒙操作系统的核心定位 * 1.2 鸿蒙开发的核心技术底座 * 1.2.1 分布式技术:设备协同的“灵魂” * 1.2.2 ArkUI:全场景UI开发的“利器” * 1.2.3 鸿蒙应用的两种形态:FA与HAP * 第二章 技术成长突破:从单端开发到跨设备协同的蜕变 * 2.1 成长痛点:单端开发的“能力天花板” * 2.2 核心突破一:掌握ArkUI多端自适应开发 * 2.2.1 声明式编程的思维转变 * 2.2.2 多端自适应的核心技术:布局约束与媒体查询 * 2.

By Ne0inhk
Flutter 三方库 frontend_server_client 深入鸿蒙编译后端极限热接驳管线适配:以桥连中继重组增量渲染图节点并强推微秒级热重载指令下发-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 frontend_server_client 深入鸿蒙编译后端极限热接驳管线适配:以桥连中继重组增量渲染图节点并强推微秒级热重载指令下发-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 frontend_server_client 深入鸿蒙编译后端极限热接驳管线适配:以桥连中继重组增量渲染图节点并强推微秒级热重载指令下发提振终端即时可视效能 在鸿蒙应用的跨平台引擎开发、自定义开发工具链或高频的热重载(Hot Reload)性能优化设计中,如何精准地控制 Dart 源码到 Kernel 文件(.dill)的编译转换过程?frontend_server_client 提供了一套与 Dart Frontend Server 通信的标准化客户端。本文将详解该库在 OpenHarmony 上的适配要点。 前言 什么是 frontend_server_client?它是 Dart SDK 中前端编译服务的封装包。它通过特定的交互协议,允许外部程序(如 IDE 插件或自定义 CLI

By Ne0inhk