文章目录
- 知识回顾
- 一、栈(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 |
float | 32 位单精度浮点数 | ±1.5 × 10^−45 到 ±3.4 × 10^38(精度约7位有效数字) |
double | 64 位双精度浮点数 | ±5.0 × 10^−324 到 ±1.7 × 10^308(精度约15–16位有效数字) |
decimal | 128 位高精度小数 | ±1.0 × 10^−28 到 ±7.9 × 10^28(精度约28–29位有效数字) |
bool | 8 位布尔型 | true 或 false |
char | 16 位单一字符型 | 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),引用存储在栈上 |
初始化 | 默认值为零或空值(如 0 、false 、null ) | 默认值为 null |
内存管理 | 系统负责管理内存(栈上分配的内存自动释放) | 由垃圾回收器 (GC) 管理内存 |
常见类型 | 基本数据类型(如 int 、float 等)、结构体、枚举 | 类、数组、委托、字符串等 |
三、特殊的引用类型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
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~