C#【堆与栈 值类型 引用类型】
先说C#中值类型和引用类型
概念:
1.值类型:数据存储在内存的堆栈中,从堆栈中可以快速地访问这些数据,因此,值类型表示实际的数据。
2.引用类型:表示指向存储在内存堆中的数据的指针或引用(包括类、接口、数组和字符串)。
C#中定义的值类型包括原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct)
引用类型包括:类、数组、接口、委托、字符串等。
我们要非常的清楚堆和栈是两个不同的概念 ,在C#中,我们把引用类型放在堆中,把值类型放在栈中。
栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。
堆,就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。堆可以动态地扩展和收缩。
堆和堆栈区别:
堆和堆栈是两个不同的概念,在内存中的存储位置也不相同,
堆一般用于存储可变长度的数据,如字符串类型;
堆栈则用于存储固定长度的数据,如整型类型的数据int。由数据存储的位置可以得知,当把一个值变量赋给另一个值变量时,会在堆栈中保存两个完全相同的值;而把一个引用变量赋给另一个引用变量,则会在堆栈中保存对同一个堆位置的两个引用,即在堆栈中保存的是同一个堆的地址。在进行数据操作时,对于值类型,由于每个变量都有自己的值,因此对一个变量的操作不会影响到其它变量;对于引用类型的变量,对一个变量的数据进行操作就是对这个变量在堆中的数据进行操作,如果两个引用类型的变量引用同一个对象,实际含义就是它们在堆栈中保存的堆的地址相同,因此对一个变量的操作就会影响到引用同一个对象的另一个变量。
如图如果我们定义一个数组
并且赋值 int[] nums={1,2,3};
nums当中保存 的是堆中的地址:例如:0x001
nums[0]:访问的是堆中的内容
那么请接着看如下代码:
public void Method1(){ // Line 1 int i=4; // Line 2 int y=2; //Line 3 class1 cls1 = new class1(); }
(参照图片理解)
第1行:当这行代码执行时,编译器为它分配一小块栈内存。运行时栈负责提供程序所需的内存;
第2行:程序继续执行。如同名字一样,栈在第一块内存的顶部分配了一块内存。
第3行:在第3行,我们创建了一个对象。当该行执行时,真实的对象存储在另一种叫“堆”的内存中。堆用于动态分配内存。
值传递引用传递:
引用类型作为参数时:
1、在修改变量本身时,结果类似于值传递,即不会改变传递前的变量的值
2、在修改变量的属性或字段时,才是引用传递,会影响到传递前的变量的值
3、参数使用了ref后,才是真正的引用传递,不管修改变量本身还是修改变量的属性或字段,都会影响到传递前的变量的值
值传递:传的是对象的值。
引用传递:传的是栈中对象的地址。
现在用例子来说明传值跟传地址的不同:
class T { public void Display() { StringBuilder strb1 = new StringBuilder(); StringBuilder strb2 = new StringBuilder(); Test1(strb1); Test2(ref strb2); string str1 = strb1.ToString(); //str1值:"A" string str2 = strb2.ToString(); //str2值:"BC" } public void Test1(StringBuilder strb) { //strb和strb1是两个栈中对象,但指向相同的地址,这个操作是改变堆中对象 strb.Append("A"); //这里将strb指向一个新的堆中对象,所以后面的操作与strb1指向的栈中对象无关 strb = new StringBuilder("B"); strb.Append("C"); } public void Test2(ref StringBuilder strb) { //这里的strb和strb2是同一个栈中对象,所以改变strb的值使其指向另一个对象也等于改变strb2 strb = new StringBuilder("B"); strb.Append("C"); } }