2011-07-07 61 views
2

好的,在你跳到腳之前,你需要了解與傳遞通過相比傳遞值是多少。您可能不同意這種按值傳遞的定義,但這只是語義,因爲真正的問題是堆棧分配和堆分配之間發生的問題。傳遞值作爲參考返回時會發生什麼?

傳遞值:要傳遞的對象被複制並且該對象的副本作爲參數提交給函數(OK,OO純粹主義者稱你稱它爲「方法」 - 語義!)。因此,在函數結束/返回時,不管對象副本做了什麼,原始對象都不會被修改。

所以Java(也可能是C#)是一種傳值語言。有些人認爲他們是通過引用,但實際上傳遞的參數是引用。所以引用的副本被傳遞給函數。也就是說,引用是按值傳遞的arg,因爲在函數的結尾/返回時原始引用不會更改。

既然我們已經完成了這項工作,並接受我的價值傳遞,那麼這裏就是一個問題。

所以一個函數參數是原始對象/引用的副本。它被分配在堆棧上。堆棧很好,因爲分配的值只是在函數結束/返回時立即丟棄。當我的函數從棧中傳遞值arg並返回它時會發生什麼。看,它在堆棧上。該對象/引用的堆棧分配是否被複制並重新分配到堆上?

Java和C#中究竟發生了什麼/精確的事情?

回答

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調用狗的堆棧分配變量來引用這個內存。狗的「價值」是指對象;它佔用了一個記憶詞。

  2. 該值的副本通過寄存器或堆棧傳遞給f。 f得到它自己的棧幀,除非編譯器優化。但是,讓我們說它確實得到了一個堆棧框架。一個簡單的單詞,包含地址拷貝的值被放置在這個堆棧幀中。實際上,它與以某種方式傳遞普通舊整數沒有什麼不同,因爲您正確地指出Java中的所有內容都是按值傳遞的。

  3. 當f返回時,它傳遞object的值,它本身只是一個指向原始Dog對象的內存單詞,返回給它的調用者。這個簡單的指針值通常通過寄存器傳回。關鍵是,只有一個字被傳回。

-1

在C#上,只有結構體在堆棧上創建,並且不可能在堆棧上創建對象。

創建新的結構和對象時有所不同。

當使用新關鍵字創建新對象時,無論如何都始終在堆中創建對象,而這是在C#中創建對象的唯一方法。垃圾回收器不會釋放堆中對象的內存,直到不存在其他引用;直到所有對該對象的引用都超出範圍。

當您使用新的關鍵字創建新結構時,無論如何,該結構總是在堆棧中創建。當您將結構分配給另一個結構時,會發生一個成員智能副本,而不是參考副本,因爲它發生在對象中。

當一個對象通過值傳遞給一個方法時,你在該方法中得到的是對該對象的引用(指向它的指針:所有C#對象都存儲爲指向它們在內存中的位置的指針)的值。當一個結構體通過值傳遞給一個方法時,你收到的是它的一個成員方式副本。

注意:當將對象傳遞給方法時使用 ref關鍵字時,這意味着該方法可以更改引用指向的內存位置。

最後,您無法在方法內的堆棧中創建對象,並且通過返回傳遞給您的方法的對象,您將返回接收到的相同引用。當你返回一個被傳遞的結構體時,會返回一個成員級的副本。

有關Java,所述概念是類似的,不同之處在於不存在結構,也不 REF參數。

+0

如果您進入並刪除「無論如何總是在堆棧上創建的結構」,我將刪除downvote。這根本不是事實。首先,如果結構是一個類的成員,它住在哪裏?有關更多信息,請參閱:http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx http://blogs.msdn.com/b/ ericlippert/archive/tags/value + types/ –

+0

@Anthony:他實際上是對的,當'newobj' MSIL指令被用來創建一個結構實例時,新的對象留在堆棧上。 [「但是,newobj指令可用於在堆棧上創建值類型的新實例」](http://msdn.microsoft.com/zh-cn/library/system.reflection.emit.opcodes.newobj .aspx)當然,並不是所有的值類型實例都在堆棧上創建,但是使用'newobj'創建的實例是。 –

+0

@Sid:你的錯誤忽略了引用變量本身,它與它所指向的類實例是截然不同的。引用變量可以在堆棧上,就像問題所聲明的一樣。 –

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 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中的第一個對象。

+0

請詳細說明內存分配的動態。 –

0

考慮'int'的情況。

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

和到功能

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

當調用該函數時,一個電話,「在」的內容被複制到堆棧。當函數執行'return arg'時,內容被複制到(當然,我不知道JVM中的位置,在某些體系結構中它是指向當前堆棧頂部的寄存器)。

然後從堆棧中回收'arg',但它的值已被複制。

當分配發生時,它不是從'arg'複製它從複製值的位置複製。

(當然,這也許是所有的「現實生活」優化掉了一個例子這種簡單)

那是你問的是什麼?

+0

有人投了這個票。請解釋爲什麼它被拒絕。 –

+0

熊,你的意思是{int out = returnIt(in)}而不是{int out = returnIt(int)}? –

+0

的確我做到了。謝謝你的收穫。 –