在C#中,变量的类型分为两种:值类型和引用类型。
不管是在学生时期,还是在职业生涯中,这个问题都是最基本的概念,也几乎在实际使用过程中不会涉及到。但是却是各种考试、各种面试都会提及的问题。
这里根据个人的经验,谈谈对值类型和引用类型的理解。
一、基本概念
什么是值类型,什么是引用类型
概念:值类型直接存储其值,而引用类型存储对其值的引用。
值类型包括:byte,short,int,long,float,double,decimal,char,bool 和 struct
引用类型包括: class、interface、deleggate、object、数组和string
值类型和引用类型的区别如下:
- 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
- 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
- 值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。
- 引用类型的对象总是在进程堆中分配(动态分配)
二、值类型和引用类型在内存中的部署
经常听说,并且经常在书上看到:值类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。MSDN上说:托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:
object reference = new object();
关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。
综上所述:
当声明一个引用类型时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
值类型变量声明后,不管是否已经赋值,编译器为其在栈中分配内存。
三、值类型和引用类型实例解析
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Dowork();
}
static void Dowork()
{
int i = 0; // int 是值类型
Console.WriteLine(i); // i = 0
Pass.value(i); // 值类型使用的是 i 的副本,i不变
Console.WriteLine(i); // i = 0
WrappendInt wi = new WrappendInt(); // 创建类 WrappendInt 的另外一个实例
Console.WriteLine(wi.Number); // 0 // 被默认构造器初始化为 0
Pass.Reference(wi); // 调用方法,wi 和 param 将引用同一个对象
Console.WriteLine(wi.Number); // 42
}
}
class Pass
{
public static void value(int param)
{
param = 42; // 赋值操作使用的是值类型参数的一个副本,原始参数不受影响
}
public static void Reference(WrappendInt param) // 创建类 WrappendInt 的一个实例
{
param.Number = 42; // 此参数是引用类型的参数
}
}
class WrappendInt // 类是引用类型
{
public int Number;
}
}
小结:
c#的值类型是为变量在栈上分配了一块内存,用于存储数据,而引用类型分为两部分,声明时只在栈上分配了一小部分内存,堆上没分配,而new引用变量时,是在堆上分配了一块内存,存储的是栈上的内存地址,注意:struct是值类型,string和class是引用类型,string不需要new是因为想要语义化“字符串”省略创建过程