【C++】深拷贝和浅拷贝 ③ ( 浅拷贝内存分析 )

news2025/1/12 19:03:48

文章目录

  • 一、浅拷贝内存分析
    • 1、要分析的代码
    • 2、调用有参构造函数创建 Student 实例对象
    • 3、调用默认拷贝构造函数为新对象赋值
    • 4、修改拷贝对象成员变量指针指向的数据
    • 5、析构报错





一、浅拷贝内存分析




1、要分析的代码


下面的代码中 , 没有定义拷贝构造函数 , 因此 C++ 编译器会自动生成一个 只进行 浅拷贝 的 默认拷贝构造函数 ;

调用默认拷贝构造函数 , 对新对象进行赋值 , 修改新对象的值 , 析构两个对象 , 分析整个执行过程中 栈内存 / 堆内存 的运行状态 ;


代码示例 :

#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()
	{
		// 销毁 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 = Jey
m_age = 18 , m_name = Jey
请按任意键继续. . .

在这里插入图片描述
按下任意键 , 继续向后执行 , 调用完第一个析构函数后 , 再次尝试调用第二个析构函数 , 报错了 ;

在这里插入图片描述


2、调用有参构造函数创建 Student 实例对象


调用有参构造函数 , 创建 Student 实例对象 ;

	// 调用有参构造函数 , 创建 Student 实例对象
	Student s(18, "Tom");

Student 类中有 2 个成员变量 :

	int m_age;
	char* m_name;

Student 类的有参构造函数如下 : 在有参的构造函数中 , m_age 成员直接赋值 , m_name 成员 , 需要先在堆内存中分配内存空间 , 然后再为其填充数据 ;

	// 有参构造函数
	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 s 变量定义在 栈内存 中 , 首先为其在栈内存中分配数据 ,

  • m_age 就是 普通的 int 类型变量 , 这里为其分配 4 字节 栈内存 , 设置值为 18 ;
  • m_name 是 char* 类型的指针 , 是一个字符串 , 初始化为 “Tom” , 指针占 4 字节大小 ,
    • “Tom” 字符串常量 在全局区中 ,
    • 为 m_name 在堆内存中分配内存 , 地址为 0x1000
    • 分配的内存大小是 “Tom” 字符个数 + 1 , 多余的 1 字节是 ‘\0’ 字符串结尾 , 也就是 4 字节 ;
    • m_name 最终的指针值是 堆内存中的地址值 , 是 0x1000 , 也就是指向堆内存中的 0x1000 地址对应的内存空间 ;

在这里插入图片描述


3、调用默认拷贝构造函数为新对象赋值


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

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

内存分析 :

使用 默认的 拷贝构造函数 , 将 s 拷贝赋值给 s2 , 执行的是浅拷贝 , 也就是直接将 成员变量 进行简单的拷贝赋值 ;

  • 将 s.m_age 赋值给 s2.m_age , int 类型直接复制
  • 将 s.m_name 赋值给 s2.m_name , 指针类型也是直接复制 , 但是这样复制的就是一个 堆内存的地址 , 该操作导致了 s2.m_name 和 s.m_name 两个指针指向了相同的堆内存地址 ;

上述指针的拷贝 , 只是将指针地址拷贝了 , 没有将指针指向的数据进行拷贝 , 这就是浅拷贝 , 显然浅拷贝是有问题的 ,

  • 如果对其中一个变量的 s.m_name 指针指向的地址进行修改 , 另外一个对象的成员也会进行改变 ;
  • 如果释放了一个对象的 s.m_name 指针 , 再尝试访问另外一个对象的 s.m_name 就会报错 ;

在这里插入图片描述


4、修改拷贝对象成员变量指针指向的数据


修改拷贝对象成员变量指针指向的数据 :

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

内存分析 :

浅拷贝时 指针的拷贝 , 只是将指针地址拷贝了 , 没有将指针指向的数据进行拷贝 , 这就是浅拷贝 , 显然浅拷贝是有问题的 ,

s2.m_name 和 s.m_name 两个指针指向了相同的堆内存地址 ;

如果 修改 拷贝对象 s2 的 s2.m_name 指针指向的地址存储的数据 , s 原始对象的 s.m_name 指针指向的数据也会被修改 ;

在这里插入图片描述


5、析构报错


程序执行完毕 , 对栈内存对象进行销毁时 , 逐个析构对象 ;

在下图的 栈内存 中 , 根据 栈内存 后进先出原则 , 先析构 s2 拷贝对象 , 然后析构 s 原始对象 ;

在这里插入图片描述

将 s2 拷贝对象析构后 , s2.m_name 指针指向的堆内存会被 free 释放 ;

但此时 s.m_name 指针还指向被释放的内存 ;

如果 s.m_name 继续被析构释放 , 这时就会报错 ;

在这里插入图片描述

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

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

相关文章

无涯教程-JavaScript - CSC函数

描述 CSC函数返回以弧度指定的Angular的余割值。 语法 CSC (number)争论 Argument描述Required/OptionalNumberThe angle (in radians) that you want to calculate the cosecant of.Required Notes CSC(n)等于1/SIN(n) 如果Angular为度,则将其乘以PI()/180或使用RADIANS…

每日一题 198. 打家劫舍

难度&#xff1a;中等 这是昨天的每日一题忘记做了&#xff0c;没想到正好是今天的题目的简化版&#xff0c;详细思路可以看http://t.csdn.cn/Smr0t 代码&#xff1a; class Solution:def rob(self, nums: List[int]) -> int:if len(nums) 1:return nums[0]a, b nums[0…

怒刷LeetCode的第3天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;动态规划 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;模拟 方法二&#xff1a;数学规律 方法三&#xff1a;分组 第三题 题目来源 题目内容 解决方法 方法一&#xff1a;数学方法 方法…

001-项目介绍

项目介绍 文章目录 项目介绍编写目的小小说明 项目介绍目前状态目录项目介绍第一代第二代第三代软件部署硬件篇 总结 关键字&#xff1a; Qt、 Qml、 分享、 记录、 目录 编写目的 这是我目前参与的一个真实项目&#xff0c;而且我非常幸运地能够从头到尾地参与其中。在面…

微信小程序|自定义弹窗组件

目录 引言小程序的流行和重要性自定义弹出组件作为提升用户体验和界面交互的有效方式什么是自定义弹出组件自定义弹出组件的概念弹出层组件在小程序中的作用和优势为什么需要自定义弹出组件现有的标准弹窗组件的局限性自定义弹出组件在解决这些问题上的优势最佳实践和注意事项避…

Shell脚本编写:从零到精通

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

C#-WinForm-发送邮件

登录QQ邮箱——设置——开启“POP3/SMTP服务” 登陆QQ邮箱→打开设置→开启“POP3/SMTP服务”&#xff0c;获取“授权码” 简单总结一下&#xff1a; 1、使用SmtpClient发送电子邮件是很简单的&#xff0c;只要正确创建了MailMessage对象和SmtpClient就可以很容易的发送出去电…

Mybatis-Plus入门(1)

单表的CRUD功能代码重复度很高&#xff0c;也没有什么难度。而这部分代码量往往比较大&#xff0c;开发起来比较费时。因此&#xff0c;目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国内使用较多的一个组件就是MybatisPlus. 官方网站如下&#xff1a; 简…

超级好用绘图工具(Draw.io+Github)

超级好用绘图工具&#xff08;Draw.ioGithub&#xff09; 方案简介 绘图工具&#xff1a;Draw.io 存储方式&#xff1a; Github 1 Draw.io 1.2 简介 ​ 是一款免费开源的在线流程图绘制软件&#xff0c;可以用于创建流程图、组织结构图、网络图、UML图等各种类型的图表。…

【面试题】forEach能跳出循环吗?

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 【国庆头像】- 国庆爱国 程序员头像&#xff01;总有一款适合你&#xff01; 如果面试官&#xff0c;或者有人问你foreach怎么跳出循环&#xff0c;请你…

LeetCode 2596. 检查骑士巡视方案

【LetMeFly】2596.检查骑士巡视方案 力扣题目链接&#xff1a;https://leetcode.cn/problems/check-knight-tour-configuration/ 骑士在一张 n x n 的棋盘上巡视。在有效的巡视方案中&#xff0c;骑士会从棋盘的 左上角 出发&#xff0c;并且访问棋盘上的每个格子 恰好一次 。…

关于感恩教师的演讲稿格式及范例

关于感恩教师的演讲稿格式及范例 感恩教师的演讲稿格式应该是&#xff1a;开头感谢听众&#xff0c;正文部分根据题目内容书写&#xff0c;结尾部分再次感谢听众。 以下是一篇关于感恩教师的演讲稿的例子&#xff1a; 尊敬的老师&#xff0c;亲爱的同学们&#xff1a; 大家好…

循环结构在反汇编中特征

本文将使用IDA分析C语言中循环结构(do while&#xff0c;while&#xff0c;for)在反汇编中的特征 目录 IDA分析 do whhile 循环 IDA分析 while 循环 IDA分析 for 循环 do while while和for哪个效率高 IDA分析 do whhile 循环 测试代码 #include <stdio.h> int main…

基于SSM框架的《超市订单管理系统》Web项目开发(第二天)完成登录模块和用户退出模块

《超市订单管理系统》&#xff08;第二天&#xff09; 基于SSM框架的Web项目开发 ​ 昨天我们实现了登录功能&#xff0c;但是用的是模拟数据。今天我们要链接数据库整合SpirngMybatis&#xff0c;读取数据库中的真实数据&#xff0c;用来跟我们输入的userCode和userPassword进…

iText实战--根据绝对位置添加内容

3.1 direct content 概念简介 pdf内容的4个层级 层级1&#xff1a;在text和graphics底下&#xff0c;PdfWriter.getDirectContentUnder() 层级2&#xff1a;graphics层&#xff0c;Chunk, Images背景&#xff0c;PdfPCell的边界等 层级3&#xff1a;text层&#xff0c;Chun…

2023年商会研究报告

第一章 行业发展概况 1.1 定义和功能 商会&#xff0c;通常被称为商业协会或商业会所&#xff0c;是由具有相似的行业、商业、贸易、专业或地理背景的企业和商家所组成的组织。这些组织的核心目标是促进其会员之间的交流、合作和互助&#xff0c;进而推动相关行业或商业领域的…

Docker Swarm集群部署

Docker Swarm集群部署 任务平台 3台虚拟机&#xff0c;一台作为manager 节点&#xff0c;另两台作为work节点。 文章目录 Docker Swarm集群部署安装docker配置防火墙开放端口在 manager 节点创建 Swarm 集群创建用于swarm服务的自定义的overlay网络测试跨主机容器通信 安装do…

Python教程:@符号的用法

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 &#x1f447; &#x1f447; &#x1f447; 更多精彩机密、教程&#xff0c;尽在下方&#xff0c;赶紧点击了解吧~ python源码、视频教程、插件安装教程、资料我都准备好了&#xff0c;直接在文末名片自取就可 符号在 Pyth…

LLC谐振变换器软启动过程分析与问题处理

启动步骤 1.使用Burst模式升压至200V—稳定的充电5S钟&#xff0c;保证所有电容完全充满。 Burst模式过程&#xff1a;1.开启一次脉冲发送–>判断母线电压大小&#xff0c;确定下次启动脉冲的时间档位–>连续启停控制–>不断给电容充电–>进入200V稳定充电状态–…

Acwing.240 食物链(并查集)

题目 动物王国中有三类动物A,B,C&#xff0c;这三类动物的食物链构成了有趣的环形。A吃B&#xff0c;B吃C&#xff0c;C吃A。 现有N个动物&#xff0c;以1–N编号。 每个动物都是A,B,C中的一种&#xff0c;但是我们并不知道它到底是哪一种。有人用两种说法对这N个动物所构 成…