做int参数获取框?

时间:2015-01-07 07:47:37

标签: c# .net recursion

说我有以下代码:

void Main()
{
    int a = 5;
    f1(ref a);
}

public void f1(ref int a)
{
    if(a > 7) return;
    a++;
    f1(ref a);
    Console.WriteLine(a);
}

输出为:

8 8 8 

即。当堆栈展开时,保持ref参数的值。

这是否意味着将ref keyword添加到int parameter会导致它被装箱?
在递归调用期间,实际堆栈的外观如何?

6 个答案:

答案 0 :(得分:25)

通过引用传递值类型会导致堆栈上的位置传递而不是值本身。它与拳击取消装箱无关。这使得思考堆栈在递归调用期间的外观相当容易,因为每个调用都指的是"相同的"在堆栈上的位置。

我认为很多混淆来自MSDN's paragraph on boxing and unboxing

  

拳击是给予进程的名称,其中值类型被转换为引用类型。当您装入变量时,您将创建一个指向堆上的新副本的引用变量。引用变量是一个对象,......

可能会让您在两个不同的事物之间感到困惑: 1)"转换"如您所愿,值类型表示一个对象,根据定义它是一个引用类型

int a = 5;
object b = a; // boxed into a reference type

2),通过引用传递值类型参数

main(){
   int a = 5;
   doWork(ref a);
}
void doWork(ref int a)
{
    a++;
}

这是两件不同的事情。

答案 1 :(得分:9)

根据ref int是否装箱,可以轻松创建可能产生不同结果的程序:

static void Main()
{
    int a = 5;
    f(ref a, ref a);
}

static void f(ref int a, ref int b)
{
    a = 3;
    Console.WriteLine(b);
}

你得到了什么?我看到3已打印出来。

拳击涉及创建副本,因此如果装箱ref a,则输出将为5。相反,ab都是对a中原始Main变量的引用。如果它有帮助,你可以大多数(不完全)将它们视为指针。

答案 2 :(得分:3)

您的Console.WriteLine(a); 将在递归完成后执行。当int的值变为8时,递归完成。为了使其为8,它递​​归3次。因此,在最后它将打印8然后将控制传递给上面的递归,其将再次打印8作为变量,其值已变为8.

同时检查ILDASM输出

.method public hidebysig static void  f1(int32& a) cil managed
{
  // Code size       26 (0x1a)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldind.i4
  IL_0002:  ldc.i4.7
  IL_0003:  ble.s      IL_0006
  IL_0005:  ret
  IL_0006:  ldarg.0
  **IL_0007:  dup**
  IL_0008:  ldind.i4
  IL_0009:  ldc.i4.1
  IL_000a:  add
  IL_000b:  stind.i4
  IL_000c:  ldarg.0
  IL_000d:  call       void ConsoleApplication1.Program::f1(int32&)
  IL_0012:  ldarg.0
  IL_0013:  ldind.i4
  IL_0014:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0019:  ret
} // end of method Program::f1

答案 3 :(得分:3)

添加现有答案如何实施:

CLR支持所谓的托管指针。 ref将托管指针传递给堆栈上的变量。您还可以传递堆位置:

var array = new int[1];
F(ref array[0]);

您也可以将引用传递给字段。

这不会导致钉扎。运行时(尤其是GC)可以理解托管指针。它们是可重新定位的。它们是安全可靠的。

答案 4 :(得分:2)

我认为你错误地说int参数被装箱了。来自MSDN

  

拳击是将值类型转换为类型对象或的过程   到此值类型

实现的任何接口类型

这里有一个int参数通过引用传递,特别是它是一个通过引用传递的“值类型”。

您可以参考Jon Skeet关于参数传递的优秀explanation以获取详细信息。

答案 5 :(得分:2)

这不是拳击。

MSDN ref keyword documentation中有明确的解释:

  

不要将引用传递的概念与引用类型的概念混淆。这两个概念不尽相同。无论是值类型还是引用类型,都可以通过ref修改方法参数。通过引用传递时,没有值类型的装箱。