将pass-by-value作为参考返回时会发生什么?

时间:2011-07-07 04:29:46

标签: c# java pass-by-value

好的,在你跳起来之前,你需要了解与pass-by-ref相比的传值。您可能不同意传值的这个定义,但这仅仅是语义,因为真正的问题是堆栈分配和堆分配之间发生的事情。

Pass-by-value:复制要传递的对象,并将对象的副本作为参数提交给函数(好的,OO纯粹是你想把它称为“方法” - 语义!)。因此,在函数结束/返回时,无论对象的副本做了什么,都不会修改原始对象。

所以Java(也可能是C#)是一种按值传递的语言。有些人认为它们是pass-by-ref,但实际上传递的args是引用。因此,引用的副本将传递给函数。也就是说,引用是按值传递arg,因为原始引用在函数的结束/返回时不会更改。

现在我们已经解决了这个问题,接受了我的价值传递,这就是问题所在。

因此函数参数是原始对象/引用的副本。它在堆栈上分配。堆栈很好,因为在函数的结束/返回时简单地立即丢弃分配的值。当我的函数从堆栈中获取pass-by-value arg并返回它时会发生什么。看,它在堆栈上。是否将该对象/引用的堆栈alloc复制并重新分配到堆上?

Java和C#究竟/精确地发生了什么?

4 个答案:

答案 0 :(得分:7)

听起来你在问Java中这样的效果是什么:

public static void f(Object object) {
    return object;
}

public static void g() {
    Dog dog = new Dog("Spike");
    System.println(f(dog));
}

如果是这样,答案是,当调用g时:

  1. 在堆上分配内存,并且在g中称为dog的堆栈分配变量用于引用此内存。狗的“价值”是对象的参考;它占用了一个记忆词。

  2. 该值的副本通过寄存器或堆栈传递给f。 f获取自己的堆栈帧,除非编译器优化掉。但是,让我们说它确实得到了一个堆栈框架。包含地址副本值的简单单词放在此堆栈帧中。实际上,它与在某种程度上传递一个普通的旧整数没什么不同,正如你正确地指出Java中的所有内容都是按值传递。

  3. 当f返回时,它传递object的值,它本身只是一个指向原始Dog对象的内存字,返回给它的调用者。这个简单的指针值通常通过寄存器传回。关键是,只传回一个单词。

答案 1 :(得分:0)

在C#中,按值返回引用。

在下面的示例中,进入的内容与返回的内容相同。

Distance FindMinimum (Distance threshold)
{
    Distance min = null;
    foreach (Distance compare in AllDistance) {
        if (compare > threshold && (min == null || compare < min))
            min = compare;
    }
    if (min == null)
        return threshold;
    return min;
}

在下面的示例中,将返回对新找到的Distance对象的引用。

Distance FindNewThreshold (Distance threshold)
{
    foreach (Distance compare in AllDistance) {
        if (compare < threshold)
            threshold = compare;
    }
    return threshold;
}

在上述两种情况下,传入的原始对象都不会更改。但在下面的示例中,将替换原始对象。

void FindNewThreshold (Distance threshold, ref Distance output)
{
    foreach (Distance compare in AllDistance) {
        if (compare < threshold)
            threshold = compare;
    }
    output = threshold;
}

void Test ()
{
    Distance d = new Distance (50);
    Distance o;
    AllDistance.Add(new Distance(10));
    FindNewThreshold (d, ref o);
    Console.WriteLine ("{0} {1}", d, o);
}

这将产生“50 10”。对o进行更改将影响AllDistance中的第一个对象。

答案 2 :(得分:0)

考虑'int'的情况。

public int returnIt(int arg) { return arg;}

并调用函数

int in  = 6;
int out = returnIt(in);

调用该函数时,'in'的内容被复制到堆栈中。

当函数执行'return arg'时,内容被复制到(好吧,我不知道JVM中的哪个位置,在某些架构中它是指向堆栈中当前顶部的寄存器)。 / p>

然后从堆栈中回收'arg',但它的值已被复制。

当分配发生时,它不会从'arg'复制它从返回值的位置复制。

(当然,这可能都是在“现实生活中”进行了优化,这个例子很简单)

这就是你问的问题吗?

答案 3 :(得分:-1)

在C#上,只在堆栈上创建了结构,并且无法在堆栈上创建对象。

创建新结构和对象时存在差异。

使用 new 关键字创建新对象时,无论如何都始终在堆中创建对象,这是在C#中创建对象的唯一方法。垃圾收集器不会释放堆上对象的内存,直到没有其它对它的引用为止;这是直到对象的所有引用超出范围。

使用 new 关键字创建新结构时,无论如何都始终在堆栈中创建结构。将结构分配给另一个结构时,会发生成员方式的副本,而不是像对象一样发生的引用副本。

当一个对象按值传递给一个方法时,你在方法中收到的是对象引用的值(指向它的指针:所有C#对象都存储为指向它们在内存中的位置的指针)。当一个struct通过值传递给一个方法时,你收到的是它的成员副本。

注意:在将对象传递给方法时使用 ref 关键字时,意味着该方法可以更改引用指向的内存位置。

最后,您无法在方法内的堆栈中创建对象,并且通过返回传递给您的方法的对象,您将返回收到的相同引用。当您返回传递的结构时,将返回成员方式的副本。

在java上,概念是相似的,除了没有结构,也没有 ref out 参数。