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

2026最新 Python+AI 入门指南:0基础也能快速上手,避开90%新手坑

2026最新 Python+AI 入门指南:0基础也能快速上手,避开90%新手坑

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、为什么2026年入门AI,首选Python?(新颖热点解读) * 二、Python+AI入门必备:前提+环境搭建(10分钟搞定) * 2.1 核心前提(不用啃硬骨头) * 2.2 环境搭建(Windows/Mac通用,避版本冲突) * 三、Python+AI入门实战:3个热门案例(附完整代码) * 案例1:数据处理(AI入门必备,80%AI开发第一步) * 案例2:机器学习入门(线性回归,房价预测) * 案例3:2026热门·大模型对接(LangChain快速调用) * 四、

By Ne0inhk
IoTDB Python原生接口全攻略:从基础读写到高级实战

IoTDB Python原生接口全攻略:从基础读写到高级实战

IoTDB Python原生接口全攻略:从基础读写到高级实战 做IoTDB时序数据开发的小伙伴,用Python对接肯定是高频需求,IoTDB官方的Python原生接口封装得特别友好,不管是基础的数据库连接、数据读写,还是高级的连接池管理、SSL加密、Pandas适配,全都能实现。今天就从环境搭建、基础使用,到DDL/DML操作、高级特性,再到测试和DBAPI适配,把IoTDB Python原生接口的用法一次性讲透,新手也能直接上手开发。 一、前期准备:安装依赖与包 用IoTDB Python原生接口前,得先装好两个核心依赖,一步到位不踩坑: 1. 安装thrift框架(要求版本≥0.13),是IoTDB底层的通信依赖 2. 安装IoTDB Python官方包(建议版本≥2.0),提供所有原生操作接口 直接用pip命令安装就行,执行以下两行: pip3 install thrift>=0.13 pip3

By Ne0inhk
Java 常见Exception全面解析:出现场景、错误排查与代码修正实战

Java 常见Exception全面解析:出现场景、错误排查与代码修正实战

文章目录 * 课程导言 * 适用对象 * 学习目标 * 课程安排 * 教学方式 * 第一部分:Java异常体系回顾(约10分钟) * 1.1 异常是什么? * 1.2 Java异常体系结构 * 1.3 异常信息解读 * 第二课时(上):运行时异常深度剖析(约30分钟) * 2.1 NullPointerException(空指针异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法流程图 * 代码修正与预防 * 2.2 ArrayIndexOutOfBoundsException(数组下标越界异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法 * 代码修正与预防 * 2.3 ClassCastException(类型转换异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法 * 代码修正与预防 * 2.

By Ne0inhk
Python 调用 Ollama 本地大模型 API 完全指南

Python 调用 Ollama 本地大模型 API 完全指南

Python 调用 Ollama 本地大模型 API 完全指南 Ollama 是一个开源工具,允许开发者在本地轻松运行 Llama、Mistral、Gemma 等主流大语言模型(LLM)。它不仅提供命令行交互,还内置了 HTTP API 服务,使得我们可以通过 Python 等编程语言远程调用本地模型,实现私有化、低延迟、无网络依赖的 AI 应用开发。 本文将手把手教你如何在 Python 中通过 HTTP 请求调用 Ollama 的 API,完成文本生成、对话交互等任务。 一、前提准备 1. 安装并启动 Ollama * 官网下载安装:https://ollama.com/ * 首次运行会自动下载模型(需联网),之后即可离线使用。 安装后,

By Ne0inhk