值类型和引用类型
栈和堆
程序运行时,它的数据必须存储在内存中。一个数据项需要多大的内存、存储在什么地方、以及如何存储都依赖于该数据项的类型
运行中的程序使用两个内存区域来存储数据:栈和堆
栈
-
定义:栈是一个内存数组,是一个LIFO(Last_In First_Out)后进先出的的数据结构。栈存储几种类型的数据:
-
某些类型变量的值;
-
程序当前的执行环境
-
传递给参数的方法
我们可以简单的把栈形象地看成一筒羽毛球,当往筒里放球时,球从筒底排到筒口,当从筒里拿球的时候,球从筒口往下依次被拿出来,直到筒底的最后一个球被拿出来。
-
操作 栈有如下几个特征:
-
数据只能从栈的顶端插入和删除
-
向栈中添加元素,此过程被称为"进栈"(入栈或压栈);
-
从栈中提取出指定元素,此过程被称为"出栈"(或弹栈);
系统管理所有的栈操作。作为程序员,你不需要显式地对它做任何事情。但了解栈的基本功能可以更好地了解程序在运行时正在做什么,并能更好地了解C#文档和著作。
堆
堆是一块内存区域,在堆里可以分配大块的内存用于存储某类型的数据对象。与栈不同,堆里的内存能够以任意顺序存人和移除。如图所示一个堆里存放四个数据
虽然程序可以在堆里保存数据,但并不能显式地删除它们。CLR的自动GC(GarbageCollector,垃圾收集器)在判断出程序的代码将不会再访问某数据项时,自动清除无主的堆对象。我们因此可以不再操心这项使用其他编程语言时非常容易出错的工作了。如图阐明了垃圾收集过程。
值类型和引用类型
数据项的类型定义了存储数据需要的内存大小及组成该类型的数据成员。类型还决定了对象在内存中的存储位置--栈或堆。
类型被分为两种:值类型和引用类型,这两种类型的对象在内存中的存储方式不同。
-
值类型只需要一段单独的内存,用于存储实际的数据。
-
引用类型需要两段内存。
-
第一段存储实际的数据,它总是位于堆中。
-
第二段是一个引用,指向数据在堆中的存放位置。
-
如图展示了每种类型的单个数据项是如何存储的。对于值类型,数据存放在栈里。对于引用类型,实际数据存放在堆里而引用地址存放在栈里。
下表中列出了C#中所有的类型以及他们的类别:值类型或引用类型。
值类型和引用类型的区别
-
值类型存取速度快,引用类型存取速度慢。
-
值类型表示实际数据,引用类型表示存储在堆中的数据的引用和地址。
-
值类型都继承自System.ValueType,引用类型都继承自System.Object。
-
栈中的内存是自由分配自动释放的,而引用类型会由.NET的GC来回收释放。
引用类型和值类型调用
1.引用类型
假设有一个引用类型的实例,名称为student,它有两个成员:一个值类型成员和一个引用类型成员。它将如何存储呢?是否是值类型的成员存储在栈里,而引用类型的成员如上图所示的那样在栈和堆之间分成两半呢?答案是否定的。
请记住,对于一个引用类型,其实例的数据部分全部存放在堆里。既然两个成员都是对象数据的一部分,那么它们都会被存放在堆里,无论它们是值类型还是引用类型。
对于引用类型的任何对象,它所有的数据成员都存放在堆里,无论它们是值类型还是引用类型。
值类型的变量存储的是值本身(存储到栈中) 引用类型的变量存储的是数据的内存地址(存储到堆中,变量保存的是内存地址) = 运算符在进行复制操作的时候,复制的是被复制变量栈中存储的内容 = 进行赋值操作的时候,操作的也是栈中存储的内容
int a = 1;
int b = a;
a = 2;
Console.WriteLine(a);//2
Console.WriteLine(b);//1
Book book1 =new Book();
book1.Name = "西游记";
book1.Price = 100;
Book book2 = book1;
book1.Name = "红楼梦";
Console.WriteLine(book1.Name);//红楼梦
Console.WriteLine(book2.Name);//红楼梦
class Book
{
public string Name;
public float Price;
}
2. String
在 C# 中,string 类型被视为引用类型。但是,与其他引用类型不同的是,对 string 变量的操作被视为对值类型的操作,因此,在对 string 变量进行赋值或传递到方法时,其实是进行值传递。
这意味着,当您将一个字符串变量传递给一个方法时,该方法会接收该字符串的一个副本,而不是原始字符串的引用。任何对传递的字符串副本的修改都不会影响原始字符串。
值类型和引用类型的区别在于,值类型存储在栈中,而引用类型存储在堆中。当您创建一个字符串变量时,它实际上是一个引用类型,但是 .NET 框架会将其优化为值类型,以提高性能。
需要注意的是,字符串是不可变的,即一旦创建就无法更改。因此,在对字符串进行操作时,实际上是创建了一个新的字符串对象,而不是修改原始字符串对象。这也是为什么 C# 中的字符串变量是值传递的原因。
总之,在 C# 中,string 类型是引用类型,但是在对其进行操作时,会被视为值类型,因此是值传递的。
String str1 = "123";
String str2 = str1;
str2 = "321";
Console.WriteLine(str1)
// output: "123"
上面代码的最终输出结果是123,上面刚讲过引用类型的调用,同学一定会问:str2不是在存储的是str1的引用么?那么str2不是和str1指向堆中同一块内存空间么?为什么在引用了str2使其改变数据后再打印出str1最终还是打印出来123?
在c#中,String的存储方式很特殊,在c#的内存中,在常量区里会分配一块空间叫做String暂存池(常量池),在某些时候,我们的字符串数据是存储在这个常量池中的,而地址依然是存放在栈中。
例如用 String str = "XXXX" 的方式来创建String变量的话,那么String的值便会存储在String常量池中,在我们以这种方式创建String变量时,编译器会先判断你这个内容有没有已经在常量池出现过了,如果已经出现过,那么不会再在常量池中使用空间来存放一个相同的内容,这个内容只会固定有一个引用,所以在创造相同内容的String的时候,他们的引用都是相同的。又有一种情况:一开始A和B内容相同,就是说A与B的引用都相同时,此时将B的内容更改,那么B的内容在常量池中就会使用另一块空间,那么相应的B的引用也会改变,而A的引用并不会改变,因为A此时还是存储的原来的内容。我们可以来看简易的图解:
标签:存储,String,内存,类型,数据,引用 From: https://www.cnblogs.com/Honsen/p/18306224