从赋值操作理解不同类型的函数传参
从赋值操作理解不同类型的函数传参
我们都知道所谓的程序,就是对传入的数据进行操作,最终将处理的数据输出给用户。那么在我们的编程中的体现,就是通过函数(或者方法)参数传入数据,然后通过函数返回值输出处理后的数据。数据的传入涉及到函数的实参和形参的传递,数据的输出涉及到函数的返回值和返回值类型的参数。函数返回值没有什么特别之处,而其他的都是函数传参的问题,既然是传参,那么传参到底传过去了什么?
函数传参与赋值操作
我们都知道函数传参涉及到形参和实参,那么实参和形参就是两个不同的变量,所以函数传参的过程,类似(或者说就是)使用实参给形参赋值的过程。那么我们先来分析按值传参的情况
图1是值类型变量的赋值操作图,我们大家都知道值类型变量直接保存变量的内容,所以赋值的过程就是将ValA保存的内容直接拷贝到ValB中,此时两个变量除了保存的值相同外,没有其他的任何关系,所以此时修改ValB的内容并不会引起ValA的变化。这是与值类型按值传参的效果是一样的。
图 1.值类型变量赋值
图2展示的是引用类型变量的赋值操作,我们都知道引用类型的变量保存的是一个地址,这个地址指向了变量内容所在的地址,也就是说引用变量保存的是一个地址。那么引用类型的赋值操作同样也是拷贝变量保存的内容,这样ValA和ValB就都指向了同样地址。如果我们修改ValB的成员,那么ValA中也会随之产生变化。我们可以看到值类型和引用类型的赋值操作是一样的,都是对变量直接保存的内容的拷贝过程。但是如果我们使用其他对象对ValB重新赋值,这时ValB就指向了其他对象,ValA还指向原来的对象并没有变化。
图 2.引用类型变量赋值
我们知道C#中引入了ref关键字,使我们可以通过函数参数返回函数的处理结果,对于我们需要同时返回多项结果的时候比较有用。其实这也就是我们通常所说的按引用传参。图3展示了变量地址赋值操作,此时我们使用新的对象对ValB所指定的对象赋值,这是ValA也就指向了新的对象了。C#中的ref是否也是这样操作的呢?我们看以看一下下边的代码和相应的IL就知道了。
图 3.变量地址赋值操作
C#代码
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FunctionParameter
{
class PassByValue
{
public void PassValueParameterByReference(ref ReferencePara parameter)
{
parameter.a = 1;
parameter = new ReferencePara();
}
public class ReferencePara
{
public int a;
public int b;
}
}
}
对应的MSIL代码
.method public hidebysig instance void PassValueParameterByReference(class FunctionParameter.PassByValue/ReferencePara& parameter) cil managed
{
// 代码大小 17 (0x11)
.maxstack 8
IL_0000: nop
//加载参数
IL_0001: ldarg.1
//加载对象的引用
IL_0002: ldind.ref
IL_0003: ldc.i4.1
IL_0004: stfld int32 FunctionParameter.PassByValue/ReferencePara::a
IL_0009: ldarg.1
IL_000a: newobj instance void FunctionParameter.PassByValue/ReferencePara::.ctor()
//绑定对象的引用
IL_000f: stind.ref
IL_0010: ret
} // end of method PassByValue::PassValueParameterByReference
总结
从我们大学一开始接触计算机编程开始,我们就开始记忆两种函数传参的异同,其实传参的过程就是实参对形参赋值的过程,只不过有时拷贝的是变量的内容,有时拷贝的是变量的地址罢了。