C++ 深拷贝和浅拷贝详解

C++ 深拷贝和浅拷贝详解

C++ 深拷贝和浅拷贝详解

在这里插入图片描述

一、C++ 深拷贝和浅拷贝详解

在 C++ 编程中,对象的拷贝操作是一个常见需求,尤其在涉及动态内存管理时。拷贝行为分为深拷贝(Deep Copy)和浅拷贝(Shallow Copy),理解它们的区别和实现方式至关重要。

1、基本概念

  • 浅拷贝(Shallow Copy):只复制对象本身,而不复制对象指向的底层数据(如指针指向的内存)。多个对象共享同一块内存,可能导致资源管理问题。
  • 深拷贝(Deep Copy):不仅复制对象本身,还复制所有底层数据(如指针指向的内存),创建一个完全独立的副本,避免共享资源问题。

在 C++ 中,拷贝行为主要通过拷贝构造函数和赋值运算符实现。默认情况下,编译器生成的拷贝操作是浅拷贝。如果类涉及动态内存分配(如指针成员),则必须自定义深拷贝以避免错误。

2、浅拷贝详解

  • 默认行为:当类未自定义拷贝构造函数或赋值运算符时,编译器会生成默认版本,执行浅拷贝。这意味着:
    • 对于非指针成员(如 intdouble),直接复制值。
    • 对于指针成员,只复制指针地址,而不是指针指向的数据。
  • 潜在问题
    • 悬挂指针(Dangling Pointer):如果原对象被析构,其指针指向的内存被释放,拷贝对象中的指针仍指向已释放的内存,导致未定义行为。
    • 内存泄漏(Memory Leak):如果多个对象共享同一内存,析构时可能多次释放同一块内存,引发崩溃。
    • 数据不一致:一个对象修改共享数据会影响其他对象。

以下是一个浅拷贝示例,展示潜在问题:

#include<iostream>usingnamespace std;classShallowCopy{private:int* data;// 指针成员public:// 构造函数,分配动态内存ShallowCopy(int value){ data =newint(value);}// 默认拷贝构造函数(浅拷贝)ShallowCopy(const ShallowCopy& other):data(other.data){}// 只复制指针地址// 析构函数,释放内存~ShallowCopy(){delete data;// 释放动态内存}// 打印数据voidprint(){ cout <<"Data: "<<*data << endl;}};intmain(){ ShallowCopy obj1(10); ShallowCopy obj2 = obj1;// 浅拷贝:obj2.data 指向 obj1.data 的同一内存 obj1.print();// 输出: Data: 10 obj2.print();// 输出: Data: 10// obj1 析构时释放内存,obj2 的指针变为悬挂指针return0;}// 运行后可能崩溃:obj1 析构后,obj2 尝试访问已释放内存

在这个示例中,obj2 通过浅拷贝共享 obj1data 内存。当 obj1 析构时释放内存,obj2 的指针失效,后续操作可能导致崩溃。

在这里插入图片描述

3、深拷贝详解

  • 实现方式:通过自定义拷贝构造函数和赋值运算符,显式复制底层数据:
    • 拷贝构造函数:为新对象分配新内存,并复制原对象的数据。
    • 赋值运算符:类似拷贝构造函数,但需处理自赋值和资源释放。
  • 好处
    • 资源安全:每个对象拥有独立内存,避免悬挂指针和内存泄漏。
    • 数据隔离:修改一个对象不会影响其他对象。
  • 适用场景:当类有指针成员、动态数组、文件句柄等资源时,必须实现深拷贝。

以下是一个深拷贝示例,展示正确实现:

#include<iostream>usingnamespace std;classDeepCopy{private:int* data;// 指针成员public:// 构造函数DeepCopy(int value){ data =newint(value);}// 深拷贝构造函数DeepCopy(const DeepCopy& other){ data =newint(*other.data);// 分配新内存并复制值}// 深拷贝赋值运算符 DeepCopy&operator=(const DeepCopy& other){if(this!=&other){// 避免自赋值delete data;// 释放当前内存 data =newint(*other.data);// 分配新内存并复制值}return*this;}// 析构函数~DeepCopy(){delete data;// 安全释放内存}// 打印数据voidprint(){ cout <<"Data: "<<*data << endl;}};intmain(){ DeepCopy obj1(20); DeepCopy obj2 = obj1;// 深拷贝:obj2 有独立内存 obj1.print();// 输出: Data: 20 obj2.print();// 输出: Data: 20 obj2 = obj1;// 赋值运算符深拷贝// 对象析构时不会冲突,内存安全return0;}

在这个示例中,深拷贝确保 obj2 拥有自己的 data 内存副本,避免了浅拷贝的问题。

在这里插入图片描述

4、深拷贝与浅拷贝的比较

  • 何时使用
    • 使用浅拷贝:当类没有指针或动态资源时(如只包含基本类型),默认浅拷贝安全高效。
    • 使用深拷贝:当类涉及动态内存分配、文件资源等时,必须实现深拷贝以防止错误。

区别总结

特性浅拷贝深拷贝
复制内容只复制指针地址复制指针地址及指向的数据
内存管理共享内存,易出错独立内存,安全
实现复杂度简单(编译器默认)复杂(需自定义)
适用性无动态资源时可用有动态资源时必须使用

5、最佳实践

  • 遵循 Rule of Three:如果一个类定义了析构函数、拷贝构造函数或赋值运算符中的一个,通常需要定义全部三个,以确保资源管理一致。
  • 使用智能指针:在现代 C++ 中,推荐使用智能指针(如 std::unique_ptrstd::shared_ptr)代替原始指针,可以自动管理内存,减少深拷贝的手动实现需求。
  • 测试验证:在实现深拷贝后,通过单元测试验证拷贝行为,确保无内存错误。

二、示例

1、代码示例

#include<iostream>#include<stdexcept>classMatrix{private:int** data;// 指向二维数组的指针 size_t rows; size_t cols;public:// 构造函数:分配内存并初始化Matrix(size_t r, size_t c):rows(r),cols(c){ data =newint*[rows];for(size_t i =0; i < rows;++i){ data[i]=newint[cols]();// 分配并初始化为0} std::cout <<"Matrix 构造函数: ["<< rows <<"x"<< cols <<"]"<< std::endl;}// 析构函数:释放内存~Matrix(){if(data !=nullptr){for(size_t i =0; i < rows;++i){delete[] data[i];// 释放每一行}delete[] data;// 释放指针数组 data =nullptr;} std::cout <<"Matrix 析构函数"<< std::endl;}// 设置元素值voidsetValue(size_t r, size_t c,int value){if(r >= rows || c >= cols){throw std::out_of_range("索引越界");} data[r][c]= value;}// 获取元素值intgetValue(size_t r, size_t c)const{if(r >= rows || c >= cols){throw std::out_of_range("索引越界");}return data[r][c];}// 打印矩阵内容voidprint()const{for(size_t i =0; i < rows;++i){for(size_t j =0; j < cols;++j){ std::cout << data[i][j]<<" ";} std::cout << std::endl;}}// ===== 浅拷贝: 使用编译器生成的默认拷贝构造函数和赋值运算符 =====// 编译器生成的版本只是简单地复制指针(data成员),导致两个对象共享同一块动态内存。// 这会导致析构时双重释放内存,引发未定义行为(通常是崩溃)。// ===== 深拷贝: 自定义拷贝构造函数和赋值运算符 =====// 拷贝构造函数 (深拷贝)Matrix(const Matrix& other):rows(other.rows),cols(other.cols){ std::cout <<"Matrix 深拷贝构造函数"<< std::endl; data =newint*[rows];for(size_t i =0; i < rows;++i){ data[i]=newint[cols];// 复制数据内容for(size_t j =0; j < cols;++j){ data[i][j]= other.data[i][j];}}}// 拷贝赋值运算符 (深拷贝) Matrix&operator=(const Matrix& other){ std::cout <<"Matrix 深拷贝赋值运算符"<< std::endl;if(this==&other){// 防止自赋值return*this;}// 释放当前对象的旧资源if(data !=nullptr){for(size_t i =0; i < rows;++i){delete[] data[i];}delete[] data;}// 复制尺寸 rows = other.rows; cols = other.cols;// 分配新内存并复制内容 data =newint*[rows];for(size_t i =0; i < rows;++i){ data[i]=newint[cols];for(size_t j =0; j < cols;++j){ data[i][j]= other.data[i][j];}}return*this;}};intmain(){try{// 创建原始矩阵 mat1 Matrix mat1(2,3); mat1.setValue(0,0,1); mat1.setValue(1,1,2); std::cout <<"mat1 内容:"<< std::endl; mat1.print();// 使用深拷贝构造函数创建 mat2 (复制mat1) Matrix mat2 = mat1;// 调用深拷贝构造函数 std::cout <<"mat2 内容 (初始复制自mat1):"<< std::endl; mat2.print();// 修改 mat2 mat2.setValue(0,0,10); mat2.setValue(0,1,20); std::cout <<"修改后的 mat2 内容:"<< std::endl; mat2.print();// 验证 mat1 未被修改 (独立内存) std::cout <<"修改后 mat1 内容 (应保持原样):"<< std::endl; mat1.print();// 创建 mat3 Matrix mat3(1,1); mat3.setValue(0,0,100);// 使用深拷贝赋值运算符: mat3 = mat2 mat3 = mat2;// 调用深拷贝赋值运算符 std::cout <<"mat3 内容 (赋值后等于mat2):"<< std::endl; mat3.print();// 修改 mat3 mat3.setValue(1,2,30); std::cout <<"修改后的 mat3 内容:"<< std::endl; mat3.print();// 验证 mat2 未被修改 (独立内存) std::cout <<"修改后 mat2 内容 (应保持原样):"<< std::endl; mat2.print();}catch(const std::exception& e){ std::cerr <<"错误: "<< e.what()<< std::endl;}return0;}

2、运行结果

Matrix 构造函数:[2x3] mat1 内容:100020 Matrix 深拷贝构造函数 mat2 内容 (初始复制自mat1):100020 修改后的 mat2 内容:10200020 修改后 mat1 内容 (应保持原样):100020 Matrix 构造函数:[1x1] Matrix 深拷贝赋值运算符 mat3 内容 (赋值后等于mat2):10200020 修改后的 mat3 内容:102000230 修改后 mat2 内容 (应保持原样):10200020 Matrix 析构函数 Matrix 析构函数 Matrix 析构函数 C:\Users\徐鹏\Desktop\新建文件夹\Project1\x64\Debug\Project1.exe(进程 29384)已退出,代码为 0(0x0)。 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。 按任意键关闭此窗口...
在这里插入图片描述

3、关键点解释

  1. Matrix 类: 管理一个动态分配的二维数组 data。构造函数分配内存,析构函数释放内存。
  2. 浅拷贝问题: 如果使用编译器自动生成的拷贝构造函数或赋值运算符,它们只会进行成员变量的逐位复制。对于指针 data,这意味着新旧对象都指向同一块内存
    • 后果: 当其中一个对象被析构时(比如离开作用域),它会释放那块内存。当另一个对象试图使用或释放这个指针时(比如它也被析构),就会发生双重释放,导致程序崩溃或其他未定义行为。
    • 示例中未直接演示崩溃,但注释说明了为什么默认行为是危险的。
  3. 深拷贝:
    • 拷贝构造函数 (Matrix(const Matrix& other)): 创建一个新对象时,它会为新对象分配全新的内存,并将原对象的数据逐一复制到新内存中。这样两个对象拥有完全独立的数据副本。
    • 拷贝赋值运算符 (operator=): 当将一个对象赋值给另一个现有对象时:
      • 首先检查是否是自赋值 (if (this == &other)),避免不必要的操作和潜在错误。
      • 释放目标对象(this)原有的内存资源。
      • 复制源对象(other)的尺寸信息。
      • 分配新的内存空间给目标对象。
      • 复制源对象的数据到目标对象的新内存中。
      • 返回 *this 以支持链式赋值。
  4. main 函数演示:
    • 创建 mat1 并设置值。
    • 使用深拷贝构造函数创建 mat2(初始内容是 mat1 的副本)。
    • 修改 mat2 后,打印 mat1 显示其内容未受影响,证明内存独立。
    • 创建 mat3 并设置一个值。
    • 使用深拷贝赋值运算符将 mat2 赋值给 mat3
    • 修改 mat3 后,打印 mat2 显示其内容未受影响,再次证明内存独立。
  5. 输出: 程序通过 std::cout 打印了构造、析构、深拷贝操作以及各个矩阵的内容,清晰地展示了对象创建、复制和修改的过程,验证了深拷贝保证了数据的独立性。
在这里插入图片描述

Read more

在 VSCode 中本地运行 DeepSeek,打造强大的私人 AI

在 VSCode 中本地运行 DeepSeek,打造强大的私人 AI

本文将分步向您展示如何在本地安装和运行 DeepSeek、使用 CodeGPT 对其进行配置以及开始利用 AI 来增强您的软件开发工作流程,所有这些都无需依赖基于云的服务。  步骤 1:在 VSCode 中安装 Ollama 和 CodeGPT         要在本地运行 DeepSeek,我们首先需要安装Ollama,它允许我们在我们的机器上运行 LLM,以及CodeGPT,它是集成这些模型以提供编码辅助的 VSCode 扩展。 安装 Ollama Ollama 是一个轻量级平台,可以轻松运行本地 LLM。 下载Ollama 访问官方网站:https://ollama.com * 下载适合您的操作系统(Windows、macOS 或 Linux)的安装程序。 * 验证安装 安装后,打开终端并运行: ollama --version  如果 Ollama 安装正确,

By Ne0inhk
DeepSeek-R1是真码农福音?我们问了100位开发者……

DeepSeek-R1是真码农福音?我们问了100位开发者……

从GitHub Copilot到DeepSeek-R1,AI编程工具正在引发一场"效率革命",开发者们对这些工具的期待与质疑并存。据Gartner预测,到2028年,将有75%的企业软件工程师使用AI代码助手。 眼看着今年国产选手DeepSeek-R1凭借“深度思考”能力杀入战场,它究竟是真码农福音还是需要打补丁的"潜力股"? ZEEKLOG问卷调研了社区内来自全栈开发、算法工程师、数据工程师、前端、后端等多个技术方向的100位开发者(截止到2月25日),聚焦DeepSeek-R1的代码生成效果、编写效率、语法支持、IDE集成、复杂代码处理等多个维度,一探DeepSeek-R1的开发提效能力。 代码生成效果:有成效但仍需提升 * 代码匹配比例差强人意 在代码生成与实际需求的匹配方面,大部分开发者(58人)遇到生成代码与实际需求完全匹配无需修改的比例在40%-70%区间,12人遇到代码匹配比例在70%-100%这样较高的区间。 然而,有30人代码匹配比例低于40%。这说明DeepSeek-R1在代码生成方面有一定效果,但在部分复杂或特定场景下,仍有很大的提升空间。

By Ne0inhk
AI+游戏开发:如何用 DeepSeek 打造高性能贪吃蛇游戏

AI+游戏开发:如何用 DeepSeek 打造高性能贪吃蛇游戏

文章目录 * 一、技术选型与准备 * 1.1 传统开发 vs AI生成 * 1.2 环境搭建与工具选择 * 1.3 DeepSeek API 初步体验 * 二、贪吃蛇游戏基础实现 * 2.1 游戏结构设计 * 2.2 初始化游戏 * 2.3 DeepSeek 生成核心逻辑 * 三、游戏功能扩展 * 3.1 多人联机模式 * 3.2 游戏难度动态调整 * 3.3 游戏本地保存与回放 * 3.4 跨平台移植 * 《Vue.js项目开发全程实录/软件项目开发全程实录》 * 编辑推荐 * 内容简介 * 作者简介 * 目录 一、

By Ne0inhk
[DeepSeek] 入门详细指南(上)

[DeepSeek] 入门详细指南(上)

前言 今天的是 zty 写DeepSeek的第1篇文章,这个系列我也不知道能更多久,大约是一周一更吧,然后跟C++的知识详解换着更。 来冲个100赞兄弟们 最近啊,浙江出现了一匹AI界的黑马——DeepSeek。这个名字可能对很多人来说还比较陌生,但它已经在全球范围内引发了巨大的关注,甚至让一些科技巨头感到了压力。简单来说这 DeepSeek足以改变世界格局                                                   先   赞   后   看    养   成   习   惯  众所周知,一篇文章需要一个头图                                                   先   赞   后   看    养   成   习   惯   上面那行字怎么读呢,让大家来跟我一起读一遍吧,先~赞~后~看~养~成~习~惯~ 想要 DeepSeek从入门到精通.pdf 文件的加这个企鹅群:953793685(

By Ne0inhk