[算法与数据结构][c++][python]:C++与Python中的赋值、浅拷贝与深拷贝

news2025/1/18 6:54:20

C++与Python中的赋值、浅拷贝与深拷贝

写在前面:Python和C++中的赋值与深浅拷贝,由于其各自语言特性的问题,在概念和实现上稍微有点差异,本文将这C++和Python中的拷贝与赋值放到一起,希望通过对比学习两语言实现上的异同点,加深对概念的理解。

1.Python中的赋值、浅拷贝、深拷贝

C++中所谓的**浅拷贝就是由(系统默认的)拷贝构造函数对数据成员进行逐一的赋值,通常默认的拷贝构造函数就是可以达到该效果的,但是如果类中有指针类型的数据(需要在堆上分配内存),那么此时使用默认的拷贝构造函数就会带来错误。因为此时采用简单的浅拷贝,则两个类中的两个指针将指向同一个地址**,当对象释放时,会调用两次析构函数,而导致指针悬挂现象(悬浮指针)

而C++的**深拷贝则是,使用自定义的拷贝构造函数**,将原有对象的所有成员变量拷贝给新对象,对于指针等数据还会为新对象重新在堆上分配一块内存,并将原有对象所持有的堆上的数据也拷贝过来,这样能保证原有对象和新对象所持有的动态内存都是相互独立的,更改一个对象的数据不会影响另一个对象,同时也不会造成double free的错误。

C++中的**赋值,默认调用的是默认的拷贝构造函数即浅拷贝**,如果要使用深拷贝需要重载赋值运算符,为动态内存在堆上分配空间即可~

C++ 浅拷贝示例:

  #include <iostream>

  // 浅拷贝 使用默认的构造函数
  class shallowCopy {
  public:
      shallowCopy(int len) : m_len(len) {
          m_ptr = new int(0); // m_ptr指向一个值为0的int
      }
      shallowCopy() {}

      ~shallowCopy() {
          delete m_ptr;
      }

  public: // 定义为public,方便输出
      int* m_ptr;
      int m_len;
  };

int main()
{
  shallowCopy sc(1);
  auto sc1 = sc; // 浅拷贝
  std::cout << "shallowCopy: " << std::endl;
  std::cout << "sc.m_ptr = " << sc.m_ptr << std::endl;
  std::cout << "sc1.m_ptr = " << sc1.m_ptr << std::endl;  
}

>>>shallowCopy: 
sc.m_ptr = 0x560c930aeeb0
sc1.m_ptr = 0x560c930aeeb0
free(): double free detected in tcache 2  // 尝试两次释放同一地址!!!报错
Aborted

C++ 深拷贝示例:

#include <iostream>

  
class deepCopy {
  public:
      deepCopy(int len) : m_len(len) {
          std::cout << "call deepCopy(int len) " << std::endl;
          m_ptr = new int(1);
      }
      deepCopy(const deepCopy& deepcopy) {
          std::cout << "call deepCopy(const deepCopy& deepcopy) " << std::endl;
          m_len = deepcopy.m_len;
          m_ptr = new int(*(deepcopy.m_ptr)); // 重新分配内存,并且赋值
      } // 拷贝构造函数
      ~deepCopy() {
          delete m_ptr;
      }

  public:
      int* m_ptr;
      int m_len;
 };

int main()
{	
  std::cout << "deepCopy: " << std::endl;
  deepCopy dc(1);
  deepCopy dc1(dc); // 深拷贝
  std::cout << "dc.m_ptr = " << dc.m_ptr << std::endl;
  std::cout << "dc1.m_ptr = " << dc1.m_ptr << std::endl;    
}

>>>deepCopy: 
call deepCopy(int len) 
call deepCopy(const deepCopy& deepcopy) 
dc.m_ptr = 0x560c930af2e0
dc1.m_ptr = 0x560c930af300

2.C++中的赋值、浅拷贝、深拷贝

在Python参数传递,“值传递”还是“引用传递“?一文中我们从Python中可变对象与不可变对象的角度理解了Python中的参数传递的方式,在赋值、深拷贝、浅拷贝中,我们同样从这个角度入手,理解Python中的深浅拷贝。对可变对象、不可变对象不是很清晰的同学,可以移步链接复习一下~。

  • 不可变对象:一旦创建就不可修改的对象,包括字符串、元组、数值类型

(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)

  • 可变对象:可以修改的对象,包括列表、字典、集合

(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)


2.1 概念

  1. 赋值,类似于C++中的引用(别名),只是复制了新对象的引用,不会开辟新的内存空间,Python中赋值的一般形式为a = 'nihao',内存中实现是:内存开辟空间存储字符串nihao,将a指向这块内存空间:
image-20240109135158539
  1. 浅拷贝: 创建新对象,其内容是原对象的引用。

​ Python中的浅拷贝有三种形式: 切片操作,工厂函数,copy模块中的copy函数。

​ 如: lst = [1,2,[3,4]]

切片操作lst1 = lst[:] 或者 lst1 = [each for each in lst]

工厂函数:lst1 = list(lst)

copy函数:lst1 = copy.copy(lst)

​ 浅拷贝之所以称为浅拷贝,是因为它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已,如在lst中有一个嵌套的 list[3,4],如果我们修改了它,情况就不一样了。

​ 浅拷贝要分两种情况进行讨论:

​ 1)当浅拷贝的值是*不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的id值(id()函数用于获取对象的内存地址)***与浅拷贝原来的id值相同。

​ 2)当浅拷贝的值是**可变对象(列表、字典、集合)**时会产生一个“不是那么独立的对象”存在。

​ 2.1) 拷贝的可变对象中无复杂子对象,原来值的改变并不会影响浅拷贝的值,同时浅拷贝的值改变也并不会影响原来的值。

​ 2.2) 拷贝的可变对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅拷贝的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅拷贝的值。

  1. 深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

只有一种形式,copy模块中的deepcopy函数

2.2 示例:从例子中理解

1) 不可变对象的赋值、深拷贝、浅拷贝
import copy

# 不可变对象,无法添加删除元素
a = (1, 2, 3)

print("==========")
b = a
print(a, b)
print(id(a), id(b))

print("=====shallow copy=====")
s = copy.copy(a)
print(a, s)
print(id(a), id(s))

print("=====deep copy=====")
d = copy.deepcopy(a)
print(a, d)
print(id(a), id(d))

>>>==========
((1, 2, 3), (1, 2, 3))
(4564433008, 4564433008)
=====shallow copy=====
((1, 2, 3), (1, 2, 3))
(4564433008, 4564433008)
=====deep copy=====
((1, 2, 3), (1, 2, 3))
(4564433008, 4564433008)
2) 可变对象的赋值、浅拷贝与深拷贝
import copy

a = [1, 2, 3]
print("==========")
b = a
b.append(4)
print(a, b)
print(id(a), id(b)) # 赋值仅是变量的别名,两变量拥有相同的内存地址,无论更改哪一个另一个都会更改

a = [1, 2, 3]
print("=====shallow copy=====")
s = copy.copy(a)
print(a, s)
print(id(a), id(s))
a.append(4)
print("------append 4-------")
print(a, s)
print(id(a), id(s))

a = [1, 2, 3]
print("=====deep copy=====")
d = copy.deepcopy(a)
print(a, d)
print(id(a), id(d))
print("------append 4-------")
a.append(4)
print(a, d)
print(id(a), id(d))


>>>==========
([1, 2, 3, 4], [1, 2, 3, 4])
(4564157144, 4564157144)
=====shallow copy=====
([1, 2, 3], [1, 2, 3])
(4564158440, 4564158512)
------append 4-------
([1, 2, 3, 4], [1, 2, 3])
(4564158440, 4564158512)
=====deep copy=====
([1, 2, 3], [1, 2, 3])
(4564158368, 4564158440)
------append 4-------
([1, 2, 3, 4], [1, 2, 3])
(4564158368, 4564158440)
3) 可变对象深浅拷贝(外层、内层改变元素)

# 外层元素更改
import copy
l = [1, 2, 3, [4, 5]]

l1 = l
l2 = copy.copy(l)
l3 = copy.deepcopy(l)
l.append(6) 

print(l)
print(l1)
print(l2)
print(l3)

>>>[1, 2, 3, [4, 5], 6]
[1, 2, 3, [4, 5], 6]
[1, 2, 3, [4, 5]]
[1, 2, 3, [4, 5]]

# 内层元素更改
import copy
l = [1,2,3,[4, 5]]

l1 = l #赋值
l2 = copy.copy(l) #浅拷贝
l3 = copy.deepcopy(l) #深拷贝
l[3].append(6) 

print(l) 
print(l1)
print(l2)
print(l3)

>>> [1, 2, 3, [4, 5, 6]] 
[1, 2, 3, [4, 5, 6]] 
[1, 2, 3, [4, 5, 6]] 
[1, 2, 3, [4, 5]]
  1. 外层添加元素时,浅拷贝不会随原列表变化而变化;内层添加元素时,浅拷贝才会变化。

  2. 无论原列表如何变化,深拷贝都保持不变。

  3. 赋值对象随着原列表一起变化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1370583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超市商品管理系统设计 C++实现

超市商品管理系统设计—C实现 文章目录 超市商品管理系统设计---C实现一、内容要求大纲图 二、源代码&#xff08;包含大量注释&#xff09;1、main.cpp文件2、supermarket.h文件3、supermarket.cpp文件4、administrator.h文件5、administrator.cpp文件6、user.h文件7、user.cp…

系列十二、数组

一、数组 1.1、概述 数组是指可以同时存放固定长度的同一类型的数据。 1.2、数组的声明 数组的声明有三种方式&#xff0c;具体如下&#xff1a; 1.2.1、方式一 /*** 声明数组的第一种方式* cityArray&#xff1a;数组名&#xff0c;注意:不包括[]*/ Test public void d…

MySQL的导入导出及备份

一.准备导入之前 二.navicat导入导出 ​编辑 三.MySQLdump命令导入导出 四.load data file命令的导入导出 五.远程备份 六. 思维导图 一.准备导入之前 需要注意&#xff1a; 在导出和导入之前&#xff0c;确保你有足够的权限。在进行导入操作之前&#xff0c;确保目标数据…

C2-3.3.2 机器学习/深度学习——数据增强

C2-3.3.2 数据增强 参考链接 1、为什么要使用数据增强&#xff1f; ※总结最经典的一句话&#xff1a;希望模型学习的更稳健 当数据量不足时候&#xff1a; 人工智能三要素之一为数据&#xff0c;但获取大量数据成本高&#xff0c;但数据又是提高模型精度和泛化效果的重要因…

代码随想录-刷题第五十二天

300. 最长递增子序列 题目链接&#xff1a;300. 最长递增子序列 思路&#xff1a;动态规划五步曲&#xff1a; dp[i]表示从0到i&#xff0c;以nums[i]结尾的最长递增子序列的长度。 递推公式&#xff1a;if(nums[i]>nums[j]) dp[i] max(dp[i], dp[j] 1) 位置i的最长升序…

Spring cloud聚合父工程project

文章目录 本次微服务版本一. 新建父工程project1.1设置字符集utf-81.2注解生效激活1.3. Java8编译版本 二. 父工程 pom.xml 本次微服务版本 一. 新建父工程project 1.1设置字符集utf-8 1.2注解生效激活 1.3. Java8编译版本 二. 父工程 pom.xml <?xml version"1.0&quo…

HTTP 3xx状态码:重定向的场景与区别

HTTP 状态码是服务器响应请求时传递给客户端的重要信息。3xx 系列的状态码主要与重定向有关&#xff0c;用于指示请求的资源已被移动到不同的位置&#xff0c;需要采取不同的操作来访问。 一、301 Moved Permanently 定义&#xff1a; 服务器表明请求的资源已永久移动到一个新…

Python多线程同步

同步条件(Event) 在Python中&#xff0c;多线程同步可以通过threading模块中的Event对象来实现。Event对象允许一个或多个线程等待某个事件的发生&#xff0c;当事件发生时&#xff0c;等待的线程将被唤醒。 event.isSet()&#xff1a;返回event的状态值 event.wait()&#x…

Vue-11、Vue计算属性

Vue计算属性是Vue实例的属性&#xff0c;用来根据已有的数据进行计算得到新的数据。计算属性的值会根据它的依赖缓存起来&#xff0c;在依赖没有发生改变时直接返回缓存的值&#xff0c;提高了性能。 计算属性的定义方式为在Vue实例中使用computed关键字&#xff0c;并将计算属…

Pycharm中如何配置python环境(conda)

首先在pycharm中点击 "File" > "Settings" 再次点击如下操作&#xff1a; 点击Python Interpreter的最右侧按钮&#xff0c;点击Show All... 找到python文件 最后点击OK

YOLOv8改进 | 主干篇 | 12月最新成果UniRepLknet特征提取网络(附对比试验效果图)

一、本文介绍 本文给大家带来的改进机制是特征提取网络UniRepLknet,其也是发表于今年12月份的最新特征提取网络,该网络结构的重点在于使用Dilated Reparam Block和大核心指导原则,强调了高效的结构进行通道间通讯和空间聚合,以及使用带扩张的小核心进行重新参数化,该网络…

实现LCM在docker之间的通信

目录 1.docker容器互联 新建网络 连接容器 2.设置环境变量 3.在两个docker之间实现通信 1.docker容器互联 新建网络 $ docker network create -d bridge test-net 连接容器 运行一个容器并连接到新建的 test-net 网络: $ docker run -itd --name lcm_1 --network tes…

Postman工具初学一篇快速入门教程

文章目录 下载安装注册登录CollectionFolderRequestGet请求Post请求Header设置Response响应 EnvironmentsGlobal环境变量其他环境变量Collection变量变量使用同名变量的优先级 Postman内置变量Pre-request script和Test script脚本设置、删除和获取变量获取请求参数获取响应数据…

无失真编码之算术编码的python实现——数字图像处理

原理 无失真编码中的算术编码是一种用于将输入数据进行高效压缩的方法&#xff0c;同时保留了原始数据的完整性。 算术编码的实现过程如下&#xff1a; 数据分段&#xff1a;首先&#xff0c;将要进行编码的数据划分为一个个符号或字符。每个符号可以是文本中的一个字母、一幅…

数学建模-Matlab R2022a安装步骤

软件介绍 MATLAB是一款商业数学软件&#xff0c;用于算法开发、数据可视化、数据分析以及数值计算的高级技术计算语言和交互式环境&#xff0c;主要包括MATLAB和Simulink两大部分&#xff0c;可以进行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程…

jenkins构建git项目timeout

问题点&#xff1a; Started by user unknown or anonymous Running as SYSTEM Building in workspace /var/jenkins_home/workspace/test-one using credential f28d956-8ee1-4f20-a32b-06879b487c70 Cloning the remote Git repository Cloning repository http://git.cc.co…

[足式机器人]Part2 Dr. CAN学习笔记 - Ch02动态系统建模与分析

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - Ch02动态系统建模与分析 1. 课程介绍2. 电路系统建模、基尔霍夫定律3. 流体系统建模4. 拉普拉斯变换&#xff08;Laplace&#xff09;传递函数、微分方程4.1 Laplace Transform 拉式变换4.2 收…

关于进制在输出时的转换【C语言】

目录 输入输出格式参考文章 1. 十进制整数作为八进制/十六进制输出 2. 八进制整数作为十进制/十六进制输出 3. 十六进制整数作为八进制/十进制输出 我们处理的整数通常用十进制表示&#xff0c;在计算机内存中是以二进制补码形式存储&#xff0c;但通常二进制表示的整数比较…

C#封装服务

C#封装服务 新建服务项目&#xff1b;重构 OnStart 和 OnStop using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using S…

基础数据结构第十期 哈希表(数组+STL)

前言&#xff1a; 哈希表是一种非常重要的数据结构&#xff0c;希望大家都能够熟练掌握&#xff01;&#xff01;&#xff01; 一、哈希表的基本内容 哈希表&#xff08;Hash Table&#xff09;&#xff0c;也被称为哈希映射&#xff08;Hash Map&#xff09;或字典&#xf…