昨天在学习设计模式的时候,遇到了“原型模式”,在这个模型中有一个很关键的点就是: 传值和传址。针对这个问题我们小组又重新回到小杨视频中去学习了一番,貌似很清晰,但是回到设计模式之后每个人的意见也是大不相同。
所以针对值类型和引用类型这个问题,我展开了一系列的学习。下面是我的一些收获,分享给大家。
在C#中的类型分为俩种: 值类型和址类型
注: 结构体属于值类型
所有的类都属于引用类型,包括接口。
值类型包括:数值类型,结构体,bool型,用户定义的结构体,枚举,可空类型。 |
引用类型包括:数组,用户定义的类、接口、委托,object,字符串,null类型,类。 |
值类型和引用类型的区别是什么:
值类型的实例都被分配到线程栈当中,所有的引用类型实例都被分配在托管堆上
性能:
由于值类型实例的值是自己本身,而引用类型的实例的值是一个引用,所以如果将一个值类型的变量赋值给另一个值类型的
变量,会执行一次逐字段的复制,将引用类型的变量赋值给另一个引用类型的变量时,只需要复制内存地址。
例如下面的代码:
class SomRef
{
public int x;
}
struct SomeVal {
public int x;
}
class Program
{
static void ValueTypeDemo()
{
SomRef r1 = new SomRef();//在堆上分配
SomeVal v1 = new SomeVal();//在栈上分配
r1.x = 5;//提领指针
v1.x = 5;//在栈上修改
SomRef r2 = r1;//只复制引用(指针)
SomeVal v2 = v1;//在栈上分配并复制成员
}
}
再来一个例子:
public class Person
{
public string Name { get; set; }// 定义个Name 属性
public int Age { get; set; }//定义个Age 属性
}
public static class ReferenceAndValue
{
public static void Demonstration()
{
Person zerocool = new Person { Name = "ZeroCool", Age = 25 };
Person anders = new Person { Name = "Anders", Age = 47 };
int age = zerocool.Age;
zerocool.Age = 22;
Person guru = anders;// 这个用到了引用,引用了前面定义好的 类anders;
anders.Name = "Anders Hejlsberg";
Console.WriteLine("zerocool's age:\t{0}", zerocool.Age);
Console.WriteLine("age's value:\t{0}", age);
Console.WriteLine("anders' name:\t{0}", anders.Name);
Console.WriteLine("guru' name:\t{0}", guru.Name);
}
}
首先我们定义了一个 Person 类,毋庸置疑 这个类是引用类型
然后,我们声明了两个Person类的实例对象,zerocool和anders,前面提到过,这两个对象都被分配在堆上,而zerocool和anders本身其实只是对象所在内存区域的起始地址引用,换句话说就是指向这里的指针。
我们声明一个值类型变量age,直接在初始化时把zerocool的Age值赋给它,显然,age的值就是25了
我们声明age值类型变量,并将zerocool.Age赋给它,编译器在栈上分配了一块空间,然后把zerocool.Age的值填进去
但是引用类型就不一样了,我们在声明guy的时候把anders赋给它,前面说过,引用类型包含的是只想堆上数据区域地址的引用,其实就是把anders的引用也赋给guy了,因此这二者从此指向了同一块内存区域,既然是指向同一块区域,那么甭管谁动了里面的“奶酪”,另一个变现出来的结果也会跟着变
最终结果:
一个特殊的引用类型: String
昨天我们在讨论设计模式的时候,遇到了一个尖锐的问题,那就是String类型在网上说的也是一种引用类型,但是为什么它的结果和普通的引用类型不同呢?
因为它是一种特殊的引用类型。
string与引用类型在常见的操作上有一些区别。
例如字符串是不可改变的。修改其中一个字符串,就会创建一个全新的string对象,而另一个字符串不会发生任何变化。
using System;
class StringExample
{
public static int Main()
{
string s1 ="aaaa";
string s2 = s1;
Console.WriteLine("s1:" + s1);
Console.WriteLine("s2:" + s2);
s1 = "bbbb";
Console.WriteLine("s1:" + s1);
Console.WriteLine("s2:" + s2);
return 0;
}
}
输出结果:
s1: aaaa
s2: aaaa
s1: bbbb
s2: aaaa
改变s1的值对s2没有影响,这与引用类型的操作相反,当用"aaaa"初始化s1时,就在堆上分配了一个新的string对象。在初始化s2时,引用也指向这个对象,所以s2的值也是"aaaa",但是当改变s1的值时,并不会替换原来的值,堆上会为新值分配一个新的string对象
然后竟然还有人怀疑我,于是我画了一张图来解释这个现象:
之后有伙伴认为数组和字符串都是同样的效果,然后我们又通过一个代码来解决了一下:
static void Main(string[] args)
{
int[] s1 = new int[] { 1, 2, 3 };
int[] s2 = s1;
Console.WriteLine("s1:{0}{1}{2}", s1[0], s1[1], s1[2]);
Console.WriteLine("s2:{0}{1}{2}", s2[0], s2[1], s2[2]);
s1[1] = 4;
Console.WriteLine("s1:{0}{1}{2}", s1[0], s1[1], s1[2]);
Console.WriteLine("s2:{0}{1}{2}", s2[0], s2[1], s2[2]);
Console.ReadKey();
}
最终显示结果
s1:123
s2:123
s1:143
s2:143
显然结果是不一样的,所以这样我们就可以用一般的引用类型“数组”来衬托出特殊引用类型“string”,他们是不同的
注意:
static void StrChange(string str) 是值传递
static void StrChange(ref string str) 是引用传递