当按值传递作为引用返回时会发生什么

本文关键字:什么 返回 按值传递 引用 | 更新日期: 2023-09-27 18:01:30

好的,在你开始之前,你需要理解什么是传值和传ref。您可能不同意这种按值传递的定义,但这仅仅是语义上的问题,因为真正的问题是堆栈分配和堆分配之间发生了什么。

按值传递:要传递的对象被复制,对象的副本作为参数提交给函数(好吧,OO纯粹主义者喜欢称之为"方法"——语义!)。因此,在函数结束/返回时,无论对对象的副本做了什么,原始对象都不会被修改。

所以Java(大概c#也是)是一种按值传递的语言。有些人认为它们是通过引用传递的,但实际上传递的参数是引用。所以引用的副本被传递给函数。也就是说,引用是按值传递的,因为原始引用在函数结束/返回时没有改变。

现在我们已经解决了这个问题,并开始接受我的按值传递的定义,这里有一个问题。

所以函数实参是原始对象/引用的副本。它是在堆栈上分配的。堆栈很好,因为分配的值在函数结束/返回时简单且立即被丢弃。当我的函数从堆栈中获取按值传递的参数并返回它时会发生什么?看,它在堆栈上。该对象/引用的堆栈分配是否复制并realloc到堆上?

在Java和c#中究竟发生了什么?

当按值传递作为引用返回时会发生什么

听起来你是在问这样的东西在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的堆栈分配变量来引用该内存。dog的"值"是对对象的引用;

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

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

在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);
}

这将产生" 5010 "。更改0将影响AllDistance中的第一个对象。

考虑'int'的情况。

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

和调用函数

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

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

当函数执行时返回参数,内容复制到(好吧,我不知道在JVM中,在某些架构的注册一些当前堆栈的顶部)。

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

当赋值发生时,它不是从'arg'复制,而是从返回值的位置复制。

(当然,对于这么简单的例子,这可能在"现实生活"中都被优化掉了)

这是你想问的吗?

在c#中,只有结构体是在堆栈上创建的,而在堆栈上创建对象是不可能的。

创建新的结构体和对象是有区别的。

当你使用new关键字创建一个新对象时,对象总是在堆中创建,无论如何,这是c#中创建对象的唯一方法。垃圾收集器不会为堆上的对象释放内存,直到不存在对它的其他引用;直到对该对象的所有引用都超出作用域为止。

使用new关键字创建新结构体时,无论如何,该结构体总是在堆栈中创建。当将一个结构体赋值给另一个结构体时,发生的是成员复制,而不是对象的引用复制。

当一个对象按值传递给一个方法时,你在方法中收到的是该对象引用的值(指向它的指针:所有c#对象都以指针的形式存储在内存中,指向它们的位置)。当一个结构体按值传递给一个方法时,你得到的是它的一个成员副本。

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

最后,你不可能在方法内部的堆栈中创建对象,并且通过返回传递给你的方法的对象,你将返回与接收到的相同的引用。当你返回一个被传递的结构体时,将返回一个面向成员的副本。

在java中,除了没有结构体,也没有refout形参之外,概念是类似的。