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

基于 Rust 与 DeepSeek V3.2 构建高性能插件化 LLM 应用框架深度解析

基于 Rust 与 DeepSeek V3.2 构建高性能插件化 LLM 应用框架深度解析

前言 随着大语言模型(LLM)技术的飞速迭代,应用开发范式正经历从"单一脚本调用"向"复杂系统工程"的转变。在构建企业级 LLM 应用时,开发者面临的核心挑战在于如何平衡系统的稳定性与灵活性:既要适配快速更迭的模型接口(如 DeepSeek V3.2),又要满足多样化的业务场景(如代码审计、日志分析、运维自动化)。 本文将深入剖析如何利用 Rust 语言强大的类型系统与所有权机制,结合 DeepSeek V3.2 强大的推理能力,构建一个高内聚、低耦合的插件化 LLM 应用框架。该架构通过定义清晰的 Trait 边界,实现了核心逻辑与业务实现的物理隔离,确保了系统的可扩展性与类型安全。 一、 架构设计理念与分层模型 传统的大模型应用往往将 API 调用、提示词工程(Prompt

By Ne0inhk
Spring Boot RESTful API 开发与测试

Spring Boot RESTful API 开发与测试

Spring Boot RESTful API 开发与测试 20.1 学习目标与重点提示 学习目标:掌握Spring Boot RESTful API开发与测试的核心概念与使用方法,包括RESTful API的定义与特点、Spring Boot RESTful API的开发、Spring Boot RESTful API的测试、Spring Boot RESTful API的认证与授权、Spring Boot RESTful API的实际应用场景,学会在实际开发中处理RESTful API问题。 重点:RESTful API的定义与特点(资源、表现层、状态转移)、Spring Boot RESTful API的开发(@RestController、@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping)、Spring

By Ne0inhk

2025 最新 Claude Code 教程:从安装部署到 SpringBoot 项目实战(附完整 Java 示例)

前言 Claude Code 是 Anthropic 推出的 AI 编码助手,专为开发者打造,相比通用 AI,它对 Java、SpringBoot 等企业级开发场景的适配性更强,能精准生成可运行的代码、排查业务逻辑 bug、优化接口性能,大幅提升开发效率。本文从安装部署、提示词技巧、SpringBoot 项目实战三个核心维度,手把手教你玩转 Claude Code,最终实现 “AI 辅助完成完整 SpringBoot 项目开发并落地本地”。 一、Claude Code 安装部署(3 种主流方式) Claude Code 支持网页版、桌面客户端、IDE 插件三种使用形式,开发者优先推荐 IDE 插件(无缝融入本地开发流程)。 1. 环境前置要求

By Ne0inhk
【前端】-jQuery(带你让你深入了解学习使用jQuery)

【前端】-jQuery(带你让你深入了解学习使用jQuery)

引言:  jQuery 是一个轻量级的 JavaScript 库,自 2006 年发布以来,它迅速成为 Web 开发中不可或缺的工具。它通过提供简洁的语法和强大的功能,简化了 HTML 文档操作、事件处理、动画效果以及 AJAX 请求的实现。jQuery 允许开发者以更少的代码实现复杂的任务,提升开发效率。此外,jQuery 还具备良好的跨浏览器兼容性,使得开发者无需关注不同浏览器间的差异,能够专注于构建更好的用户体验。无论是初学者还是经验丰富的开发者,jQuery 都是实现现代 Web 应用的强大助手。  因为使用jQuery需要引入jQuery的js文件,所以大家需要下载jQuery相应的js文件 下载步骤:  jQuery官网:jQuery 点击显示下面的网页,然后使用快捷键ctrl+s进行保存到文件夹中,就可以在vscode上直接使用了 (我下的就是3.7.1版本的)  jQuery的使用: 1.对象: 1.1jQuery包装级对象: <

By Ne0inhk