【从零开始入门unity游戏开发之——C#篇04】栈(Stack)和堆(Heap),值类型和引用类型,以及特殊的引用类型string

文章目录

  • 知识回顾
  • 一、栈(Stack)和堆(Heap)
    • 1、什么是栈和堆
    • 2、为什么要分栈和堆
    • 3、栈和堆的区别
    • 4、总结
  • 二、值类型和引用类型
    • 1、那么值类型和引用类型到底有什么区别呢?
      • 值类型
      • 引用类型
    • 2、总结
  • 三、特殊的引用类型string
    • 1、为什么说string是特殊的引用类型?
    • 2、理解字符串(string)引用类型
    • 3、如何证明呢?
      • 使用 `GetHashCode` 方法
      • 通过断点调试直接查看变量指针内存地址
    • 4、总结
  • 专栏推荐
  • 完结

知识回顾

C# 中的变量类型可以分为 值类型引用类型 两大类。

值类型

变量类型描述范围
byte无符号8位整数0 到 255
sbyte有符号8位整数-128 到 127
short有符号16位整数-32,768 到 32,767
ushort无符号16位整数0 到 65,535
int有符号32位整数-2,147,483,648 到 2,147,483,647
uint无符号32位整数0 到 4,294,967,295
long有符号64位整数-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
ulong无符号64位整数0 到 18,446,744,073,709,551,615
float32 位单精度浮点数±1.5 × 10^−45 到 ±3.4 × 10^38(精度约7位有效数字)
double64 位双精度浮点数±5.0 × 10^−324 到 ±1.7 × 10^308(精度约15–16位有效数字)
decimal128 位高精度小数±1.0 × 10^−28 到 ±7.9 × 10^28(精度约28–29位有效数字)
bool8 位布尔型truefalse
char16 位单一字符型Unicode字符(0 到 65,535)

引用类型

变量类型描述范围
string字符串任意长度的字符序列(理论上最多可达到 2GB)

一、栈(Stack)和堆(Heap)

要了解值类型和引用类型的区别,我们得先得栈和堆的概率有个了解。

1、什么是栈和堆

简单理解就是,程序运行时,它的数据必须存储在内存中。栈和堆就是计算机内存中的两种不同的存储区域。

2、为什么要分栈和堆

通过分栈和堆,程序可以在性能和内存管理上做出平衡,从而让程序既高效又灵活。

3、栈和堆的区别

  • 栈空间比较小,但是读取速度快。
  • 栈存储的是一些简单的数据
  • 栈遵循先进后出原则,栈就像一个堆叠的盘子。你每次放入一个新盘子(数据),都会把它放在最上面。拿东西的时候,也都是从最上面拿,所以非常快速。
  • 栈里的数据只在当前函数或方法运行时有效,一旦方法执行完毕,这些数据就会自动被销毁。
    在这里插入图片描述

  • 堆空间比较大,但是读取速度慢。
  • 堆存储的是一些较大的数据。
  • 堆就像一个大大的垃圾堆,可以随意放东西。它不按照顺序来存放数据,而是根据需要分配空间,可以存储更复杂的对象
  • 堆中的数据不会像栈那样自动清理。(但在 C# 中,垃圾回收会自动清理不再使用的对象)

在这里插入图片描述

4、总结

实际上,我们写程序并不需要关心内存是如何使用的,C#已经帮我们做好了。这里只是简单介绍这个概念,有些知识看不懂也没关系,比如垃圾回收,后面肯定还会详细介绍。现在有个印象就行。


二、值类型和引用类型

在 C# 中,数据类型分为两大类:值类型(Value Types)和 引用类型(Reference Types)。

我们目前学了值类型和引用类型只有变量,但是其实不止

  • 值类型其实还有结构体(Struct)枚举(Enum)
  • 引用类型还有类(Class)数组(Array)委托(Delegate)

这些我们后面会一一介绍,现在了解一下就行。

1、那么值类型和引用类型到底有什么区别呢?

因为只学了变量,这里就用变量举例。

值类型

  • 直接存储数据,值类型的变量直接保存它们的数据。值类型直接存储在上。
  • 值类型赋值时,会复制值本身

比如

int a, b;
a = 100;
b = a;
Console.WriteLine("a的值:" + a);
Console.WriteLine("b的值:" + b);a = 20; // 重新给a赋值,b的值不会改变
Console.WriteLine("a的值:" + a);
Console.WriteLine("b的值:" + b);

打印结果,正如前面所说,重新给a赋值,b的值不会改变
在这里插入图片描述

画图说明
在这里插入图片描述
解释

  • 声明变量 int a, b;
    当你声明两个整数变量 a 和 b 时,编译器会在栈上为它们各自分配32位的存储空间。此时,这两个存储空间是空的,没有初始化任何值。

  • 赋值 a = 100;
    当你给变量 a 赋值为 20 时,栈上的存储空间 a 会被写入值 20。这个操作不会重新分配内存,而是直接在已经分配的内存位置写入新的值。

  • 赋值 b = a;
    当你执行 b = a; 时,栈上的存储空间 b 会被写入 a 的当前值。此时,a 和 b 都存储了值 20,但它们是独立的存储空间。

  • 重新赋值 a = 20;
    当你再次给 a 赋值为 20 时,栈上的存储空间 a 会被更新为新的值 20。这不会重新分配内存,而是直接在已经分配的内存位置写入新的值。

引用类型

  • 间接存储数据,引用类型的变量保存的是对实际数据所在位置的引用或地址(也叫指针),而不是数据本身。引用类型存储在栈上的引用(或指针)和堆上的实际数据
  • 引用类型赋值时,会复制引用,但实际的数据不会复制。

画图说明,假设a b c都是引用类型
在这里插入图片描述
解释

  • 声明引用类型 a 和 b;

    这时 a 和 b 都是空引用,它们在栈上分配了空间,但它们指向的堆内存地址尚未确定,二者目前都没有引用任何实际的对象。

  • 给 a 赋值:

    此时,a 作为栈上的一个引用变量,指向堆上的值。b 仍然是空引用。

  • b = a; b 也指向 a 的堆值:

    此时,a 和 b 都存储相同的堆内存地址,指向相同的堆对象。

  • a 修改为新值:

    这时候a b的值就都变成了新值

  • 重新定义 c,并给 c 赋值

    此时,c 是一个新的引用类型变量,它在栈上存储了指向堆上c值的地址,且与 a 和 b 的值互不影响。

ps:有些小伙伴可能会说了,前面不是说了string不就是引用类型吗,为什么不用string举例呢,这样不是更加直观?其实是因为string是特殊的引用类型,这个问题我接下来会说。

2、总结

特性值类型 (Value Type)引用类型 (Reference Type)
存储方式存储数据的值本身存储数据的引用(内存地址)
赋值行为赋值时会复制数据,原始值和复制值互不影响赋值时会复制引用,两个变量指向同一个对象
存储位置通常存储在栈上 (stack),但结构体和其他类型可能存储在堆上存储在堆上 (heap),引用存储在栈上
初始化默认值为零或空值(如 0falsenull默认值为 null
内存管理系统负责管理内存(栈上分配的内存自动释放)由垃圾回收器 (GC) 管理内存
常见类型基本数据类型(如 intfloat 等)、结构体、枚举类、数组、委托、字符串等

三、特殊的引用类型string

1、为什么说string是特殊的引用类型?

学了前面引用类型的知识,我们可以拿string测试一下试试。

string str1, str2;
str1 = "名";
str2 = str1;
Console.WriteLine("str1的值:" + str1);
Console.WriteLine("str2的值:" + str2);str1 = "字";//重新赋值str1
Console.WriteLine("str1的值:" + str1);
Console.WriteLine("str2的值:" + str2);

按前面引用类型的概念,可能你想说第二次打印的结果应该是"字" "字"

实际上真是这样吗?我先来看看执行结果
在这里插入图片描述
有人会说,如果是值类型,结果倒还说的过去.但是不是说string是引用类型么?如果是引用类型的话。输出的结果难道不应该是: "名""名""字" "字"么?

2、理解字符串(string)引用类型

理解字符串(string)在C#中的行为确实可能有些困惑,因为它们在某种程度上表现出值类型和引用类型的特性。让我们来详细解释一下。

  • 字符串是不可变
    字符串在C#中是不可变的,这意味着一旦你创建了一个字符串对象,就不能修改它的内容。当你尝试修改一个字符串时,实际上是创建了一个新的字符串对象。

  • 字符串为什么是引用类型
    因为它们在堆上分配内存,并且在栈上存储对堆上对象的引用。因此,多个变量可以引用同一个字符串对象。

3、如何证明呢?

使用 GetHashCode 方法

虽然这并不返回内存地址,但 GetHashCode 方法会返回一个与字符串内容相关的哈希值。这个值可以作为字符串的“标识符”,有时候在调试中,它能帮助你判断是否为同一个字符串实例。

string str1 = "xxxx";
string str2 = str1;
Console.WriteLine(str1.GetHashCode());
Console.WriteLine(str2.GetHashCode());str1 = "yyyy";
Console.WriteLine(str1.GetHashCode());
Console.WriteLine(str2.GetHashCode());

结果
在这里插入图片描述

通过断点调试直接查看变量指针内存地址

在这里插入图片描述
值类型,一开始内存地址就不一样
在这里插入图片描述
string引用类型,开始地址一样,重新赋值后地址不一样了
在这里插入图片描述
在这里插入图片描述

4、总结

字符串不叫值类型,因为它们确实具有引用类型的基本特性:在堆上分配内存,并且在栈上存储引用。尽管字符串的不可变性使得它们在某些方面表现得像值类型,但从技术上讲,它们仍然是引用类型。

由于字符串的不可变性,即使它们是引用类型,修改一个字符串变量不会影响其他引用相同字符串的变量。这是因为当你修改字符串时,实际上是创建了一个新的字符串对象,并将变量的引用指向了这个新对象。

string虽然方便,但是有一个小缺点就是频繁的改变string重新赋值会产生内存垃圾,优化替代方案我们会在后面进行讲解


专栏推荐

地址
【从零开始入门unity游戏开发之——C#篇】
【从零开始入门unity游戏开发之——unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

【C语言实现:用队列模拟栈与用栈模拟队列(LeetCode 225 232)】

LeetCode刷题记录 🌐 我的博客主页:iiiiiankor🎯 如果你觉得我的内容对你有帮助,不妨点个赞👍、留个评论✍,或者收藏⭐,让我们一起进步!📝 专栏系列:LeetCode…

【Python】Selenium 爬虫的使用技巧和案例

引言 Selenium 是 Python 中功能强大的自动化测试工具,因其能够操控浏览器进行模拟操作,被广泛应用于网页数据爬取。相比传统的 requests 等库,Selenium 能更好地应对动态加载内容和复杂交互场景。本文将详细介绍 Selenium 爬虫的使用技巧,并提供实际案例来帮助读者快速上…

MySQL SQL语句性能优化

MySQL SQL语句性能优化指南 一、查询设计优化1. 避免 SELECT *2. 使用 WHERE 进行条件过滤3. 避免在索引列上使用函数和表达式4. 使用 LIMIT 限制返回行数5. 避免使用子查询6. 优化 JOIN 操作7. 避免全表扫描 二、索引优化1. 使用合适的索引2. 覆盖索引3. 索引选择性4. 多列索引…

Mybatis动态sql执行过程

动态SQL的执行原理主要涉及到在运行时根据条件动态地生成SQL语句,然后将其发送给数据库执行。以下是动态SQL执行原理的详细解释: 一、接收参数 动态SQL首先会根据用户的输入或系统的条件接收参数。这些参数可以是查询条件、更新数据等,它们…

java jar包加密 jar-protect

介绍 java 本身是开放性极强的语言,代码也容易被反编译,没有语言层面的一些常规保护机制,jar包很容易被反编译和破解。 受classfinal(已停止维护)设计启发,针对springboot日常项目开发,重新编写安全可靠的jar包加壳加密技术,用于保护软件版权。 使用说…

Linux:Git

Git常见指令: git help xx_command git xx_command --help git --version 查看git版本git config --global user.name "xxx_name" 全局级别的签名设置,全局的放在本用 git config --global user.ema…

【WiFi】WiFi中RSSI、SNR、NF之间关系及说明

RSSI(接收信号强度指示) 定义: RSSI 是一个相对值,用于表示接收到的无线信号的强度。它通常由无线设备的硬件(如无线网卡或无线芯片)直接提供。 计算: RSSI 的计算通常是由设备的无线芯片完成的…

提升音频转录准确性:VAD技术的应用与挑战

引言 在音频转录技术飞速发展的今天,我们面临着一个普遍问题:在嘈杂环境中,转录系统常常将非人声误识别为人声,导致转录结果出现错误。例如,在whisper模式下,系统可能会错误地转录出“谢谢大家”。本文将探…

[ZMQ] -- ZMQ通信Protobuf数据结构 1

1、前言背景 工作需要域间实现zmq通信,刚开始需要比较简单的数据结构,比如两个bool,后面可能就需要传输比较大的数据,所以记录下实现流程,至于为啥选择proto数据结构去做大数据传输,可能是地平线也用这个&…

顺序表的使用,对数据的增删改查

主函数: 3.c #include "3.h"//头文件调用 SqlListptr sql_cerate()//创建顺序表函数 {SqlListptr ptr(SqlListptr)malloc(sizeof(SqlList));//在堆区申请连续的空间if(NULLptr){printf("创建失败\n");return NULL;//如果没有申请成功&#xff…

React和Vue中暴露子组件的属性和方法给父组件用,并且控制子组件暴露的颗粒度的做法

React 在 React 中,forwardRef 是一种高级技术,它允许你将 ref 从父组件传递到子组件,从而直接访问子组件的 DOM 节点或公开的方法。这对于需要操作子组件内部状态或 DOM 的场景非常有用。为了使子组件能够暴露其属性和方法给父组件&#xf…

《C++ 实时视频流物体跟踪与行为分析全解析》

在当今科技飞速发展的时代,视频监控与智能分析技术在众多领域发挥着极为重要的作用。从安防监控到智能交通,从工业自动化到人机交互,利用 C 处理实时视频流中的物体跟踪和行为分析成为了热门且极具挑战性的研究与开发方向。本文将深入探讨其中…

5G中的随机接入过程可以不用收RAR?

有朋友提到了一种不用接收RAR的RA过程,问这个是怎么回事。其实在刚刚写过的LTM cell switch篇章中就有提到,这里把所有相关的内容整理如下。 在RACH-less LTM场景,在进行LTM cell switch之前就要先知道target cell的TA信息,进而才…

git 导出某段时间修改的文件 windows

第一步:列出两次commitID之间的文件变动 git diff oldid newid --name-only// 例如 git diff 4a886c57a8b5611a2abcfcd120461c2e92f7029a HEAD --name-only 4a886c57a8b5611a2abcfcd120461c2e92f7029a 代表之前 HEAD 代表最新或者换成某次commitID 例如&#xf…

Qt 联合Halcon配置

文章目录 配置代码窗口绑定 配置 选择添加库 选择外部库 LIBS -LC:/Program Files/MVTec/HALCON-17.12-Progress/lib/x64-win64/ LIBS -lhalconcpp\-lhdevenginecpp\-lhalconINCLUDEPATH C:/Program Files/MVTec/HALCON-17.12-Progress/include DEPENDPATH C:/Program Fil…

new URL(`../assets/images/${name}`, import.meta.url).href

背景: 文章讲述了Vite框架中关于资源文件(如图片)在默认配置下,如何正确处理开发环境和打包后的不同引用方式。重点介绍了使用import.meta.url和new URL() 来动态获取并处理静态资源URL的方法,以及注意事项&#xff0…

8、笔记本品牌分类介绍:LG - 计算机硬件品牌系列文章

LG笔记本品牌以其高性能和先进技术而闻名,‌提供多种型号以满足不同用户的需求。‌ LG笔记本产品线包括多种类型,‌以满足不同用户的需求。‌其中,‌LG Gram Pro系列以其超薄设计和高性能配置受到关注。‌该系列笔记本采用16:10的OLED显示屏&…

367_C++_计算mouse移动过程中,视频框的右侧、底部边距,以及根据实时的右侧、底部边距计算—视频框的左上角位置

代码分析 1. restorePos 方法 restorePos 的作用是恢复 NavigationFrame 的位置,将其移动到父窗口或者指定矩形内的特定位置。 void NavigationFrame::restorePos() {// 获取目标矩形:优先使用 `m_pRect`,否则默认使用视频区域或父窗口区域RSRect videoRect(m_pVide

Tiptap,: 富文本编辑器入门与案例分析

Tiptap 是一个现代的富文本编辑器,基于 ProseMirror 打造,旨在提供一个灵活且功能强大的文本编辑解决方案。它具有开箱即用的能力,同时也允许开发者根据业务需求进行高度定制化扩展。与传统的富文本编辑器相比,Tiptap 提供了更精细…

scala的泛型类

泛型:类型参数化 泛型类指的是把泛型定义到类的声明上, 即:该类中的成员的参数类型是由泛型来决定的. 在创建对象时, 明确具体的数据类型. 定义格式: class 类名(成员名:数据类型) class 类名[泛型名](成员名:泛型名) 参考代…