C#中数据类型分为值类型和引用类型
干货:
方法声明的参数列表中的参数叫做形参,实际调用方法时传递给方法的参数叫做实参,调用方法时,程序会首先将实参的值传递给对应的形参,而后执行方法体中的代码。对于引用类型的参数,实参和形参会指向同一个对象。而对于值类型的参数,实参的值将被复制一份给形参,方法代码中的对形参的修改不会影响到实参。
此时形参是实参的”引用”,二者指向同一个变量,如果代码中修改了形参的值,实参的值也会改变,这样就能真正起到交换参数值的效果了。
先来看一个栗子:
//一会要用到的方法
private void CorrectCount(int count)
{
count = 11;
}
private void CorrectCount(ref int count)
{
count = 11;
}
int count = 0;
Debug.Log("Origin count: " + count);
CorrectCount(count);
Debug.Log("Correct count: " + count);
CorrectCount(ref count);
Debug.Log("Correct count ref : " + count);
//输出:
Origin count: 0
Correct count: 0
Correct count ref : 11
我们再看一个特殊的栗子:
//一会要用到的方法
private void CorrectCount(string count)
{
count = "11";
}
private void CorrectCount(ref string count)
{
count = "11";
}
string count = "0";
Debug.Log("Origin count: " + count);
CorrectCount(count);
Debug.Log("Correct count: " + count);
CorrectCount(ref count);
Debug.Log("Correct count ref : " + count);
//输出:
Origin count: 0
Correct count: 0
Correct count ref :11
嗯?刚刚不是说,ref是为了解决值类型无法在方法内外真正交换参数值的问题吗? string 明明是引用类型,为啥子也不能【黑人问号脸】
这里需要特别注意的是,string虽然是引用类型,但这个东东太特殊了,C#对它进行了特殊处理,虽然它本身是引用类型,但相关操作都会导致它产生一个新的string对象,你看,你传进去是小明,人家在里面改了名字叫老王了。所以如果是string类型,也要用ref。
那还有其他的吗。不,没了,其他的引用类型(就剩class了)随便用。就算你class里面定义了很多值类型,在方法里面也修改了, 没得关系。但如果你的实参直接是class.int 对不起,你这个是值类型。
out关键字,加了它的就叫输出型参数(ref 是引用型参数)。其实两个用法差不多,只是有一些小区别罢了。
那就来看看他们的区别把:
round one:
ref:我可进可出(可以传值进来,我也可以传出去)
out:我只出不进(不能传值进来,但我可以传出去)
round two:
ref:进来之前必须初始化
out:进来之前爱咋地咋地,进来后,我就给你初始化(必须赋值才能出)
round three:
ref:我们俩兄弟不能构成重载
out:对,我们俩兄弟不能构成重载(如果两个方法只是ref和out关键字不同,其他的方法名/参数类型等都相同的话,不构成重载,编译器会报错)
举个简单的out的栗子:
private void CorrectCountOut(out int count)
{
count = 33;
}
int count;
CorrectCountOut(out count);
Debug.Log("Correct count out: " + count);
//输出
Correct count out: 33
CLR默认所有方法参数都是传值的。传递引用类型的对象时,引用对象(或者说指向对象的指针)被传给方法。注意,引用(指针)本身是传值的,意味着方法能够修改对象,而调用者能看到这些修改。对于值类型的实例,传给方法的是实例的一个副本,意味着方法将获得它专用的一个值类型实例副本,调用者中的实例不受影响。
CLR允许以传引用而非传值的方式传递参数。C#用关键字out和ref支持这个功能。两个关键字都告诉C#编译器生成元数据来指明该参数是传引用的。编译器将生成代码来传递参数的地址,而非参数本身。
对于传引用方式(ref/out)传给方法的变量,他的类型必须与方法签名中声明的类型相同(需要完全一致,继承关系也不可以)。 为什么呢,因为方法预期的是某个类型的引用,而传进来的却不是这个类型的,会报错: ”无法将某类型转换成某类型“, 根本原因是为了保障类型安全,防止在方法内对类型进行了原意类型不存在的操作。想要解决这个问题,可以用泛型来定义方法就可以。
举例:ref
Func1(num)
public void Func1(int v1){} //v1是一个值,num的副本
Func2(ref num)
public void Func2(ref int v2){} //v2是一个引用(指针),指向num所在的地址(调用Func2时将num的地址传入)
Func3(obj)
public void Func3(object o1){} //o1是一个值,o1指向obj所指向的地址
Func4(ref obj)
public void Func4(ref object o2){} //o2是一个引用(指针),指向obj所在的地址(调用Func4时将obj的地址传入)
Func1(num)
public void Func1(int v1){} //v1是一个值,num的副本
Func2(out num)
public void Func2(out int v2){} //v2是一个引用(指针),指向num所在的地址(调用Func2时将num的地址传入)
Func3(obj)
public void Func3(object o1){} //o1是一个值,o1指向obj所指向的地址
Func4(out obj)
public void Func4(out object o2){} //o2是一个引用(指针),指向obj所在的地址(调用Func4时将obj的地址传入)
从CLR的角度,ref和out是一样的,都导致传递指向实例的一个指针。
但对编辑器来说,会有所区别(ref在外必须初始化,out在内必须初始化后使用),以保证代码的正确性。
对于重载,因为CLR认为ref和out是一样的,所以以下重载是不支持的,只能留取其中一个。
public void Func1(ref int num){}
public void Func1(out int num){}