值类型和引用类型

值类型/引用类型包含有:

  • 值类型:int、double、bool、char、decimal、struct、enum
  • 引用类型:string、自定义类、数组、集合、object、接口

二者的区别:

  • 1、值类型和引用类型在内存上存储的地方不同:

    值类型存储在栈中,引用类型存储在堆中。

  • 2、在传递值类型和传递引用类型时,传递方式不同;

值类型的传递

值类型在传递的时候,传递的是值本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

namespace 值传递和引用传递
{
class Program
{
static void Main(string[] args)
{
int a = 1;
int b = a;
b = 2;
Console.WriteLine("a={0}",a);
Console.WriteLine("b={0}",b);
}
}
}

运行结果:

内存图:

在执行 int b=a; 时,在栈中开辟一块空间用于存储b,并将a中的值传给b。执行完 b=2; 后,b被赋予了新值,并覆盖了原来的值。根据输出结果,值传递后,b的值与a的值无关,互不影响。

引用类型的传递

注意:这里引用传递的运行结果放在string类型上会有所不同,这是因为string类型具有不可变性,string类型的不可变性在下文会有介绍。

引用类型传递的时候,传递的是对这个对象的引用(即存在堆中的地址)。

下面以自定义类(Person)来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;

namespace 值传递和引用传递
{
class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "张三";
Person p2 = p1;
p2.Name = "李四";
Console.WriteLine("p1.Name={0}",p1.Name);
Console.WriteLine("p2.Name={0}", p2.Name);
}
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}
}

运行结果:

内存图:

创建完成p1对象后,在堆上开辟一块存储空间,并将该存储空间的地址放在栈上,栈中这块空间的地址(标识)是p1。当p1对象传递给p2后,只是将对堆中的存储的引用(堆中的地址)复制一份传给p2,完成传递后p1和p2共同指向同一块内存,所以p1或p2中任意一个更改存储内容后,另一个也会改变。

string类型的不可变性

先按照上述“引用类型的传递”的思路来看string类型的传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

namespace 值传递和引用传递
{
class Program
{
static void Main(string[] args)
{
string a = "张三";
string b = a;
b = "李四";
Console.WriteLine("a={0}", a);
Console.WriteLine("b={0}", b);
}
}
}

运行结果:

从运行结果可以看出,string类型的传递和一般引用类型不同,这是因为string类型具有不可变性:

  • 当给一个字符串重新赋值以后,旧值并没有销毁,而是重新开辟了一块空间用于存储新值。

内存图:

在a赋值“张三”并传递给b后,根据“引用类型的传递”,a和b共同指向“张三”(如图:堆地址为10001)这块内存。但是,在b重新被赋值为“李四”后,堆中开辟一块空间存储“李四”,旧值“张三”依然存在,新值的堆地址(如图:堆地址为10002)赋给栈中b的存储以使b指向“李四”。至此,a和b就不再指向同一块堆空间了。故运行结果与一般引用类型有所不同。