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

Qwen3.5-4B 微调实战:LLaMA-Factory 打造医疗AI助手

Qwen3.5-4B 微调实战:LLaMA-Factory 打造医疗AI助手

最近在帮一个医疗创业团队做技术支持,他们想把通用大模型改造成能回答专业医疗问题的智能助手。今天就把整个过程整理出来,希望对有类似需求的朋友有所帮助。 核心工具链:LLaMA-Factory + Qwen3.5-4B + 医疗问答数据集 Qwen3.5 是阿里最新发布的千问系列模型,4B 参数量刚好卡在"效果够用 + 显存友好"的甜蜜点;LLaMA-Factory 则是目前开源社区最成熟的微调框架,上手简单,坑也相对少。 准备工作 先说硬件要求。4B 模型用 LoRA 微调的话,一张 12GB 显存的显卡就够了(比如 RTX 4070)。如果手头只有 8GB 显存的卡,可以上 QLoRA 量化方案,牺牲一点精度换显存空间。 微调方式 4B 模型显存需求 推荐显卡 LoRA (16-bit) ~10-12 GB

By Ne0inhk
2026年高校AIGC检测新规解读:AI率多少算合格?

2026年高校AIGC检测新规解读:AI率多少算合格?

2026年高校AIGC检测新规解读:AI率多少算合格? 从2024年知网正式上线AIGC检测功能开始,短短两年时间,"AI率"已经从一个新鲜名词变成了每个毕业生必须面对的硬性指标。2026年,各高校的AIGC检测政策进一步收紧和细化,要求也越来越明确。 那么,2026年AI率到底多少才算合格?不同学校的标准差别大吗?不合格会面临什么后果?本文将对这些问题进行深入解读。 一、AIGC检测已成为毕业论文审查的标配 回顾AIGC检测在高校中的普及历程,可以用"指数级扩散"来形容: * 2024年:知网上线AIGC检测功能,少数985/211院校开始试点,大部分学校处于观望状态 * 2025年:超过60%的本科院校和80%的研究生培养单位将AIGC检测纳入论文审查流程 * 2026年:AIGC检测基本实现全覆盖,包括专科院校在内的绝大部分高等教育机构都已建立相关制度 这一进程的背后,是教育部在2025年初发布的《关于加强高等学校学位论文学术诚信管理的指导意见》,其中明确提到"鼓励各高校引入人工智能生成内容检测机制,将AIGC检测作为论文质量保障的重要环节"。 虽然教育部没

By Ne0inhk

对于VScode中Copilot插件使用卡顿问题的解决办法

copilot卡顿主要是网络和内存占用原因。 VScode内存优化解决办法: 结合链接和我补充的基本都可以解决。 解决VSCode无缘无故卡顿的问题_vscode卡顿-ZEEKLOG博客 在VScode中打开setting.json文件,打开方法ctrl+shift+p,输入Preferences: Open User Settings (JSON), 然后添加如下代码: { "search.followSymlinks": false, "git.autorefresh": false, "editor.formatOnSave": false } 结合链接和我补充的基本都可以解决。 VScode代理问题: vscode copilot长时间没反应_vscode中copilot总是卡住-ZEEKLOG博客 配置代理的话两种方法,上面是一种,推荐两种结合起来用(不冲突) 还是在setting.json文件中,添加如下代码: { "http.proxy": "http://127.

By Ne0inhk

白嫖专业版!Github Copilot Pro竟然可以免费使用

今天有个好消息要和大家分享: GitHub Copilot 已在 Visual Studio Code 上免费开放! 无论是谁,都能畅享 AI 助力下的高效开发新体验! 如何免费在vscode中使用copilot * 下载最新版本的 VS Code https://code.visualstudio.com/Download 如果下载慢,可以通过下面的网盘链接获取: https://pan.quark.cn/s/3fb6dcedfed8 * 打开vscode编译器,按照下面步骤操作 这时候就可以看到账户已经启用Copilot Free copilot Free 和copilot Pro主要功能对比 功能FreePro消息和互动每月最多 50 个无限实时代码建议每月最多 2,000无限上下文感知编码支持和解释支持支持VS Code 中的多文件编辑支持支持切换模型支持支持GitHub 中的代码审查不支持支持拉取请求中的 Copilot Workspace不支持支持VS Code 中的 Java

By Ne0inhk