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

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 copya = [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.hqwc.cn/news/334341.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Volcano Scheduler调度器源码解析

Volcano Scheduler调度器源码解析 本文从源码的角度分析Volcano Scheduler相关功能的实现。 本篇Volcano版本为v1.8.0。 Volcano项目地址: https://github.com/volcano-sh/volcano controller命令main入口: cmd/scheduler/main.go controller相关代码目录: pkg/scheduler 关联…

HarmonyOS 整体容器组件(Navigation)

今晚 我们一起来看看 Navigation 我们可以编写代码如下 Entry Component struct Index {build() {Row() {Column() {Navigation() {}.width(100%).height(100%).backgroundColor("#F1F1F1")}.width(100%)}.height(100%)} }Navigation 通常是作为容器被使用 这里 我…

什么是全链路压测?

随着互联网技术的发展和普及&#xff0c;越来越多的互联网公司开始重视性能压测&#xff0c;并将其纳入软件开发和测试的流程中。 阿里巴巴在2014 年双11 大促活动保障背景下提出了全链路压测技术&#xff0c;能更好的保障系统可用性和稳定性。 什么是全链路压测&#xff1f;…

工业异常检测AnomalyGPT-Demo试跑

写在前面&#xff1a;如果你有大的cpu和gpu可以使用&#xff0c;直接根据官方的安装说明就可以&#xff0c;如果没有&#xff0c;可以点进来试着看一下我个人的安装经验。 一、试跑环境 NVIDIA4090显卡24g,cpu内存33G&#xff0c;交换空间8g,操作系统ubuntu22.04(试跑过程cpu…

Uibot (RPA设计软件)培训前期准备指南————课前材料三

(本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北的前两篇博客&#xff0c;友友们我们即将开展新课的学习~RPA 培训前期准备指南——安装Uibot(RPA设计软件&#xff09;-CSDN博客https://blog.csdn.net/Zhiyilang/article/details/1…

互联网上门洗衣洗鞋小程序开发搭建;

互联网搭建的洗衣洗鞋小程序&#xff0c;具备多重功能。首先&#xff0c;用户轻松注册与登录&#xff0c;获取一站式洗涤服务体验。接着&#xff0c;用户可在线提交洗衣、洗鞋订单&#xff0c;并随时查看订单状态和历史记录&#xff0c;全程跟踪无忧。再有&#xff0c;您可便捷…

【Flutter 开发实战】Dart 基础篇:从了解背景开始

想要学会用 Flutter 开发 App&#xff0c;就不可避免的要学习另一门很有意思的编程语言 —— Dart。很多小伙伴可能在学习 Flutter 之前可能都没听说过这门编程语言&#xff0c;我也是一样&#xff0c;还以为 Dart 是为了 Flutter 而诞生的&#xff1b;然而&#xff0c;当我们去…

大创项目推荐 深度学习图像风格迁移

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

系统概要设计说明书

系统概要设计说明书 1.整体架构 2.功能架构 3.技术架构 4.运行环境设计 5.设计目标 6.接口设计 7.性能设计 8.运行设计 9.出错设计 全文档获取进主页

Linux———head,tail命令详解(狠狠爱住)

目录 head 命令&#xff1a; head 命令基本语法&#xff1a; 常用选项 示例 显示文件的前 10 行&#xff1a; 显示文件的前 5 行&#xff1a; 显示文件的前 100 个字节&#xff1a; 不显示文件名的标题信息&#xff1a; 显示文件名的标题信息&#xff1a; tail 命令&…

vscode使用npm安装element-UI并添加router路由

npm安装vue&#xff0c;添加淘宝镜像-CSDN博客 elementUI安装与配置 安装可以看我上一篇文章 vscode控制台输入指令 npm i element-ui -S 安装完成后在目录结构打开下图文件 可以看到多了一行elementui就代表安装成功了 下面是项目常用的结构 安装完成后需要启用elementU…

什么是API网关代理?

带有API网关的代理服务显着增强了用户体验和性能。特别是对于那些使用需要频繁创建和轮换代理的工具的人来说&#xff0c;使用 API 可以节省大量时间并提高效率。 了解API API&#xff08;即应用程序编程接口&#xff09;充当服务提供商和用户之间的连接网关。通过 API 连接&a…