[转载]从赋值操作理解不同类型的函数传参 – 无风听海 – 博客园.
我们都知道所谓的程序,就是对传入的数据进行操作,最终将处理的数据输出给用户。那么在我们的编程中的体现,就是通过函数(或者方法)参数传入数据,然后通过函数返回值输出处理后的数据。数据的传入涉及到函数的实参和形参的传递,数据的输出涉及到函数的返回值和返回值类型的参数。函数返回值没有什么特别之处,而其他的都是函数传参的问题,既然是传参,那么传参到底传过去了什么?
函数传参与赋值操作
我们都知道函数传参涉及到形参和实参,那么实参和形参就是两个不同的变量,所以函数传参的过程,类似(或者说就是)使用实参给形参赋值的过程。那么我们先来分析按值传参的情况
图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; 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
总结
从我们大学一开始接触计算机编程开始,我们就开始记忆两种函数传参的异同,其实传参的过程就是实参对形参赋值的过程,只不过有时拷贝的是变量的内容,有时拷贝的是变量的地址罢了。