一、基本概念
CLR支持两种类型,值类型和引用类型。它们从类型的定义、实例的创建、参数传递、到内存的分配都有所不同;.NET中的类型分类如下:
值类型和引用类型最本质的区别在于内存的分布上,大致可以这么说---->:值类型存在栈上,引用类型存在堆上;
1、什么是堆和栈?
栈(stack):栈是基于线程的,一个线程会包含一个线程栈。线程中的值类型在对象作用域结束的时候会自动被清理;栈是由操作系统负责管理的,用于存放值类型变量和引用类型在托管堆上的地址;
托管堆(GC Heap):进程在初始化的时候在进程地址空间上划分内存,存储.NET运行过程中的对象,所有的引用类型都存储在托管堆上,托管堆上分配的对象是由GC负责管理和释放的;托管堆是基于进程的;
二、值类型一直都存在栈上吗,引用类型一直都存在堆上吗?
1.单独的值类型变量,如局部值类型变量都是存储在栈上面的;
2.当值类型是自定义class的一个字段、属性时,它随引用类型存储在托管堆上,此时她是引用类型的一部分;
4.所有的引用类型肯定都是存放在托管堆上的。
5.结构体(值类型)中定义引用类型字段,结构体是存储在栈上,其引用变量字段只存储内存地址,指向堆中的引用实例。
三、值类型和引用类型的参数传递
值类型变量在传递给另外一个变量时,会执行一次复制,复制的是值;
引用类型在传递给另一个变量时,也会执行一次复制,但复制的却是引用对象的地址;
int v1 = 0; int v2 = v1; v2 = 100; Console.WriteLine("v1=" + v1); //输出:v1=0 Console.WriteLine("v2=" + v2); //输出:v2=100 User u1=new User(); u1.Age = 0; User u2 = u1; u2.Age = 100; Console.WriteLine("u1.Age=" + u1.Age); //输出:u1.Age=100 Console.WriteLine("u2.Age=" + u2.Age); //输出:u2.Age=100,因为u1/u2指向同一个对象
当把对象作为参数传递的时候,效果同上面一样,他们都称为按值传递;
四、引用传递:
按引用传递的两个主要关键字:out
和 ref
不管值类型还是引用类型,按引用传递的效果是一样的,都不传递值副本,而是引用的引用(类似c++的指针的指针)
out
和 ref
告诉编译器方法传递额是参数地址,而不是参数本身;
private void DoTest( ref int a) { a *= 2; } private void DoUserTest(ref User user) { user.Age *= 2; } [NUnit.Framework.Test] public void DoParaTest() { int a = 10; DoTest(ref a); Console.WriteLine("a=" + a); //输出:a=20 ,a的值改变了 User user = new User(); user.Age = 10; DoUserTest(ref user); Console.WriteLine("user.Age=" + user.Age); //输出:user.Age=20 }
out
和 ref
的主要异同:
-
out
和ref
都指示编译器传递参数地址,在行为上是相同的; - 他们的使用机制稍有不同,ref要求参数在使用之前要显式初始化,out要在方法内部初始化;
-
out
和ref
不可以重载,就是不能定义Method(ref int a)和Method(out int a)这样的重载,从编译角度看,二者的实质是相同的,只是使用时有区别;