【C++】深拷贝和浅拷贝 ④ ( 深拷贝示例 )

news2024/10/7 8:25:34

文章目录

  • 一、深拷贝示例
    • 1、浅拷贝问题
    • 2、自己实现深拷贝
  • 二、深拷贝完整代码示例





一、深拷贝示例




1、浅拷贝问题


在上一篇博客 【C++】深拷贝和浅拷贝 ③ ( 浅拷贝内存分析 ) 中 , 使用了浅拷贝 , 将 原始对象 Students 赋值给了 拷贝对象 Student s2 ;

使用 C++ 编译器 生成的 默认的拷贝构造函数 进行对象赋值 , 该拷贝是 浅拷贝 ;


使用浅拷贝被出现了两个问题 :

  • 浅拷贝 导致 两个对象持有相同的指针 , 修改 拷贝对象 指针指向的数据 , 原始对象 指针指向的数据也会一起修改 ;
  • 析构时 , 两个对象的指针都需要释放 , 释放第二个指针时 , 该指针已经被释放 , 再重复释放一个已经被释放的指针 , 直接报错 ;

在这里插入图片描述


2、自己实现深拷贝


上述 浅拷贝 中 , 只拷贝指针变量 , 没有重新为新对象的指针成员 变量分配内存 , 导致后续的一系列问题 ;

如果 自己要实现深拷贝操作 , 那么需要 在 拷贝构造函数中 , 一旦遇到指针成员变量 , 立刻测量该指针分配的堆内存大小 , 然后再新的内存中保存要拷贝的数据 ;


针对要拷贝的 Student 类中的 m_name 指针类型成员变量 , 深拷贝流程如下 :

  • 首先 , 获取 char* 类型指针 指向的 字符串长度 , 使用 strlen 函数测量指针指向的堆内存的大小 ;
// 获取字符串长度
int len = strlen(s.m_name);
  • 然后 , 为 对象的成员变量 m_name 指针分配内存 , 注意这是为 char* 类型字符串分配内存 , 还要为字符串结尾的 ‘\0’ 字符分配内存 ;
// 为 m_name 成员分配内存 
// 注意还要为字符串结尾的 '\0' 字符分配内存
m_name = (char*)malloc(len + 1);
  • 最后 , 使用 strcpy 函数 , 拷贝字符串内容 , 在 C++ 中需要添加 添加 #define _CRT_SECURE_NO_WARNINGS 宏定义 , 否则会报错 ;
// 拷贝字符串
// C++ 中使用该函数需要
// 添加 #define _CRT_SECURE_NO_WARNINGS 宏定义
if (m_name != NULL)
{
	strcpy(m_name, s.m_name);
}

自定义 深拷贝 拷贝构造函数代码示例 :

	// 拷贝构造函数
	// 执行 Student s2 = s; 代码时调用该函数
	// 自己实现 深拷贝 操作
	Student(const Student& s)
	{
		// 获取字符串长度
		int len = strlen(s.m_name);

		// 为 m_name 成员分配内存 
		// 注意还要为字符串结尾的 '\0' 字符分配内存
		m_name = (char*)malloc(len + 1);

		// 拷贝字符串
		// C++ 中使用该函数需要
		// 添加 #define _CRT_SECURE_NO_WARNINGS 宏定义
		if (m_name != NULL)
		{
			strcpy(m_name, s.m_name);
		}

		// 为 m_age 成员设置初始值
		m_age = s.m_age;

		cout << "调用拷贝构造函数" << endl;
	}




二、深拷贝完整代码示例



下面的代码中 , 自定义了 深拷贝 的拷贝构造函数 ;

执行 Student s2 = s; 代码时 , 自动调用了 自定义的 深拷贝 拷贝构造函数 , 拷贝时为指针成员重新再堆内存中分配了内存空间 , 并复制了字符串数据 ;

执行如下代码 , 单独修改拷贝对象 , 不会影响到原始对象 ;

// 修改 s2 对象
strcpy(s2.m_name, "Jey");

在最后析构时 , 由于 拷贝对象 和 原始对象 的 char* m_name; 指针成员变量分别指向不同的内存空间 , 两个对象析构 , 都不会影响另外一个对象的指针成员析构 ;


完整代码示例 :

#define _CRT_SECURE_NO_WARNINGS

#include "iostream"
using namespace std;

class Student
{
public:

	// 有参构造函数
	Student(int age, const char* name)
	{
		// 获取字符串长度
		int len = strlen(name);

		// 为 m_name 成员分配内存 
		// 注意还要为字符串结尾的 '\0' 字符分配内存
		m_name = (char*)malloc(len + 1);

		// 拷贝字符串
		// C++ 中使用该函数需要
		// 添加 #define _CRT_SECURE_NO_WARNINGS 宏定义
		if (m_name != NULL)
		{
			strcpy(m_name, name);
		}
			
		// 为 m_age 成员设置初始值
		m_age = age;

		cout << "调用有参构造函数" << endl;
	}

	// 拷贝构造函数
	// 执行 Student s2 = s; 代码时调用该函数
	// 自己实现 深拷贝 操作
	Student(const Student& s)
	{
		// 获取字符串长度
		int len = strlen(s.m_name);

		// 为 m_name 成员分配内存 
		// 注意还要为字符串结尾的 '\0' 字符分配内存
		m_name = (char*)malloc(len + 1);

		// 拷贝字符串
		// C++ 中使用该函数需要
		// 添加 #define _CRT_SECURE_NO_WARNINGS 宏定义
		if (m_name != NULL)
		{
			strcpy(m_name, s.m_name);
		}

		// 为 m_age 成员设置初始值
		m_age = s.m_age;

		cout << "调用拷贝构造函数" << endl;
	}

	~Student()
	{
		// 销毁 name 指向的堆内存空间
		if (m_name != NULL)
		{
			free(m_name);
			m_name = NULL;
		}
		cout << "调用析构函数" << endl;
	}

	// 该类没有定义拷贝构造函数 , C++ 编译器会自动生成默认的拷贝构造函数

	// 打印类成员变量
	void toString()
	{
		cout << "m_age = " << m_age << " , m_name = " << m_name << endl;
	}

public:
	int m_age;
	char* m_name;
};

int main()
{
	// 调用有参构造函数 , 创建 Student 实例对象
	Student s(18, "Tom");
	// 打印 Student 实例对象成员变量值
	s.toString();

	// 声明 Student 对象 s2 , 并使用 s 为 s2 赋值
	// 该操作会调用 默认的拷贝构造函数 
	// C++ 编译器提供的拷贝构造函数 只能进行浅拷贝
	Student s2 = s;
	s2.toString();

	// 修改 s2 对象
	strcpy(s2.m_name, "Jey");
	s.toString();
	s2.toString();

	// 执行时没有问题 , 两个对象都可以正常访问
	// 但是由于拷贝时 执行的是浅拷贝 
	// 浅拷贝 字符串指针时 , 直接将指针进行拷贝 , 没有拷贝具体的值
	// s 和 s2 的 m_name 成员是同一个指针
	// 如果析构时 , 先析构 s2 , 将指针释放了 
	// 之后再析构 s 时 发现 继续释放 被释放的指针 , 报错了



	// 控制台暂停 , 按任意键继续向后执行
	system("pause");
	return 0;
}

执行结果 :

调用有参构造函数
m_age = 18 , m_name = Tom
调用拷贝构造函数
m_age = 18 , m_name = Tom
m_age = 18 , m_name = Tom
m_age = 18 , m_name = Jey
请按任意键继续. . .
调用析构函数
调用析构函数

Y:\002_WorkSpace\002_VS\HelloWorld\HelloWorld\Debug\HelloWorld.exe (进程 7480)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

在这里插入图片描述

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

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

相关文章

Mysql的基本查询练习

目录 一、Create 1.1单行数据全列插入 1.2 多行数据指定列插入 1.3插入否则更新 1.4 替换 二、Retrieve 2.1全列查询 2.2指定列查询 2.3查询字段为表达式 2.4为查询结果指定别名 2.5 结果去重 2.6 where 条件 2.6 NULL的查询 2.7 结果排序 三、 Update 四、Dele…

电力系统直流潮流分析【N-1】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

VHOST-SCSI代码分析(0)VHOST概述

与VIRTIO框架相比&#xff0c;VHOST将设备放到HOST上&#xff0c;让Guest和Host Kernal Space之间共享virtqueue&#xff0c;减少Exception Level切换。 对于数据的传递&#xff0c;依次经历&#xff1a;Guest UserSpace&#xff08;EL0&#xff09;-> Guest KernelSpace&am…

MySQL索引,事务及存储引擎

目录 MySQL索引 创建索引的依据&#xff1a; 索引的类型 普通索引 唯一索引 主键索引 组合索引 全文索引 查看索引 删除索引 事务 事务的 ACID 特性 原子性 一致性 隔离性 持久性 隔离级别 设置隔离级别 事务管理操作 自动提交事务 存储引擎 M…

GODIVA论文阅读

论文链接&#xff1a;GODIVA: Generating Open-DomaIn Videos from nAtural Descriptions 文章目录 摘要引言相关工作Video-to-video generationText-to-image generationText-to-video generation GODIVA方法逐帧视频自动编码器GODIVA视频生成器 实验数据集评价指标自动评估指…

QT基础教程(对话框1)

文章目录 前言一、对话框概念二、模态对话框三、非模态对话框总结 前言 本篇文章我们来讲解QT中的对话框。 资料合集地微信公众号&#xff1a;优质程序猿一、对话框概念 在Qt中&#xff0c;对话框&#xff08;Dialog&#xff09;是一种用于与用户进行交互、收集输入或展示信…

适用于 Android 的 Windows 子系统™️发行说明

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 内部版本 2304.40000.3.0 内部版本 2303.40000.3.0 内部版本 2302.4000 内部版本 2301.40000.4.0 内部版本 221…

【wxWidgets 如何实现wxAccordion 手风琴组件】

1. 简要说明 wxWidgets 并没有提供wxAccordion 手风琴组件面板, 网上也基本没有找到资料,CSDN有个标题:wxAccordion:wxWidgets的手风琴控件下载链接,再无资料可查, 但是其他语言是提供了手风琴面板组件的, 那么怎么办呢,开干喽… 2. 效果展示 3. 交流探讨 代码就暂时不发了,…

【LeetCode-简单题】225. 用队列实现栈

文章目录 题目方法一&#xff1a;单个队列实现 题目 方法一&#xff1a;单个队列实现 入栈 和入队正常进行出栈的元素其实就是队列的尾部元素&#xff0c;所以直接将尾部元素弹出即可&#xff0c;其实就可以将除了最后一个元素的其他元素出队再加入队&#xff0c;然后弹出队首元…

第二章 进程与线程 八、处理机调度(时机切换、过程调度方式)

一、进程调度的时机 二、进程调度的方式 1、非剥夺调度方式&#xff1a; 非剥夺调度方式&#xff0c;又称非抢占方式。即只允许进程主动放弃处理机。在运行过程中即便有更紧迫的任务到达&#xff0c;当前进程依然会继续使用处理机&#xff0c;直到该进程终止或主动要求进入阻…

virtualbox配置ubuntu1804虚拟机相关流程

virtualbox配置ubuntu1804虚拟机相关流程 相关版本能解决的问题安装流程1&#xff1a;新建虚拟机安装流程2&#xff1a;配置虚拟机安装流程3&#xff1a;安装虚拟机系统安装流程4&#xff1a;设置ubuntu 相关版本 virtualbox使用VirtualBox官网下载的6.1.34 r150636 版。ubunt…

CSRF攻击原理详解

CSRF概念&#xff1a; CSRF定义&#xff1a; 跨站请求伪造&#xff08;英语&#xff1a;Cross-site request forgery&#xff09;是一种对网站的恶意利用&#xff0c;也被称为 one-click attack 或者 session riding&#xff0c;通常缩写为 CSRF 或者 XSRF&#xff0c; 是一种…

通用商城项目(下)之——Nginx的安装及使用

&#xff08;作为通用商城项目的一个部分&#xff0c;单独抽离了出来。查看完整见父页面&#xff1a; &#xff09; 加入Nginx-完成反向代理、负载均衡和动静分离 1.配置SSH-使用账号密码&#xff0c;远程登录Linux 1.1配置实现 1、配置sshd 1)sudo vi /etc/ssh/sshd_confi…

Rasa:使用大语言模型进行意图分类

Rasa:使用大语言模型进行意图分类 在Rasa的最新版本(3.x)中,引入了一种新的意图分类方法,即使用大型语言模型(LLM)和一种称为检索增强生成(RAG)的方法进行意图分类。 LLM意图分类器是一种全新的意图分类器,利用大型语言模型(LLM)来对意图进行分类。LLM意图分类器…

汽车租赁系统设计与实现

汽车租赁系统 1&#xff0e;需求分析 1.1任务概述 1.2开发环境和使用技术 1.3数据库设计 2&#xff0e;登陆模块 2.1登陆页面的代码是&#xff1a; 2.2登录类login.java的代码如下&#xff1a; 2.3LoginServlet.java的代码如下&#xff1a; 3&#xff0e;公共…

ESP-IDF学习——1.环境安装与hello-world

ESP-IDF学习——1.环境安装与hello-world 0.前言一、环境搭建1.官方IDE工具2.vscode图形化配置 二、示例工程三、自定义工程四、点灯五、总结 0.前言 最近在学习freertos&#xff0c;但由于买的书还没到&#xff0c;所以先捣鼓捣鼓ESP-IDF&#xff0c;因为这个比Arduino更接近底…

神秘字符(acm模式)

#include<iostream> #include<cctype> #include<string> using namespace std; int main() {int n;cin >> n;getchar();while (n--){string str, str1, str2"";//cin >> str >> str1;getline(cin, str);//遇到换行符就忽略&…

GB28181学习(五)——实时视音频点播(信令传输部分)

要求 实时视音频点播的SIP消息应通过本域或其他域的SIP服务器进行路由、转发&#xff0c;目标设备的实时视音频流宜通过本域的媒体服务器进行转发&#xff1b;采用INVITE方法实现会话连接&#xff0c;采用RTP/RTCP协议实现媒体传输&#xff1b;信令流程分为客户端主动发起和第…

CCC数字钥匙设计【BLE】--PE进入流程建立BLE加密连接

1、PE进入流程 PE进入的流程大体可以拆解为如下步骤&#xff1a; 1、手机与车端建立BLE加密连接&#xff1b; 2、创建URSK流程&#xff1b; 3、时间同步流程&#xff1b; 4、UWB测距流程&#xff1b; 5、根据各锚点BLE的RSSI及UWB的距离值&#xff0c;通过定位算法进行手…

Android 图片加载框架Glide源码详解

我们看Glide的源码从Glide类入手&#xff0c;使用的时候我们先调用的with方法&#xff0c;源码中with有3个多载的方法&#xff1a;下图翻译过来就是activity用FragmentActivity Applicationcontext用 with&#xff08;Context&#xff09;还有一个with&#xff08;View)的 殊途…