reference用法 - pass by address java




Java是“傳遞引用”還是“按值傳遞”? (20)

無論您使用何種語言,引用始終是表示的值。

獲取框外視圖,讓我們看看Assembly或一些低級內存管理。在CPU級別,如果將任何內容寫入內存或其中一個CPU寄存器,則對任何內容的引用會立即變為。(這就是為什麼指針是一個很好的定義。它是一個值,它同時具有目的)。

內存中的數據有一個位置,在該位置有一個值(字節,字,等等)。在Assembly中,我們有一個方便的解決方案,可以為某個位置(也就是變量)提供一個名稱,但是在編譯代碼時,彙編程序只是將Name替換為指定位置,就像瀏覽器用IP地址替換域名一樣。

在核心的情況下,技術上不可能在不表示任何語言的情況下將引用傳遞給任何語言(當它立即變為值時)。

比方說,我們有一個變量富,它的位置是在內存中的第47字節,它的是5,我們有另一個變量Ref2Foo這是在第223次內存字節,並且它的值是47.這Ref2Foo可能是一個技術性的變量,不是由程序明確創建的。如果您只看5和47而沒有任何其他信息,您將只看到兩個。如果您將它們用作參考,那麼5我們必須前往:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

這就是跳轉表的工作原理。

如果我們想用Foo的值調用方法/函數/過程,有幾種可能的方法將變量傳遞給方法,具體取決於語言及其幾種方法調用模式:

  1. 5被複製到其中一個CPU寄存器(即EAX)。
  2. 5獲得PUSHd到堆棧。
  3. 47被複製到其中一個CPU寄存器
  4. 47推到堆棧。
  5. 223被複製到其中一個CPU寄存器。
  6. 223獲取PUSHd到堆棧。

在每個情況下,都創建了一個值 - 現有值的副本 - 現在由接收方法來處理它。當你在方法中寫入“Foo”時,它要么從EAX中讀出,要么自動解除引用,或者雙重解引用,這個過程取決於語言的工作方式和/或Foo的類型。這是開發人員隱藏的,直到她繞過解除引用過程。因此,引用是表示的,因為引用是必須處理的值(在語言級別)。

現在我們已經將Foo傳遞給了方法:

  • 在情況1.和2.如果您更改Foo(Foo = 9)它只影響本地範圍,因為您有一個值的副本。從方法內部我們甚至無法確定原始Foo所在的內存位置。
  • 如果您使用默認語言結構並更改Foo(Foo = 11),它可以全局更改Foo(取決於語言,即Java或類似Pascal的procedure findMin(x, y, z: integer; var m : integer);)。但是,如果該語言允許您繞過取消引用過程,您可以更改47,比方說49。在那一點上,如果你讀它,Foo似乎已被改變了,因為你已經改變了它的本地指針。如果你要在方法(Foo = 12)中修改這個Foo,你可能會搞砸程序的執行(又名.segfault),因為你會寫一個不同於預期的內存,你甚至可以修改一個注定要保存可執行文件的區域程序和寫入它將修改運行代碼(Foo現在不在47)。但是Foo的價值47沒有全局更改,只有方法內部的一個,因為47也是方法的副本。
  • 在第5和第6例中。如果你223在方法內修改它會產生與3.或4中相同的混亂。(一個指針指向一個現在錯誤的值,它再次用作指針)但這仍然是一個本地問題,因為223被複製了。但是,如果您能夠取消引用Ref2Foo(即223),達到並修改指向的值47,比如說,49它將全局影響Foo ,因為在這種情況下,方法得到了一個副本,223但引用的47只存在一次,並且更改了對49每一個會導致Ref2Foo雙解引用到一個錯誤的值。

對無關緊要的細節進行挑剔,即使是通過引用傳遞的語言也會將值傳遞給函數,但這些函數知道它們必須將它用於解除引用目的。這個傳遞參考值只是程序員隱藏的,因為它實際上是無用的,術語只是通過引用傳遞

嚴格的pass-by-value也是無用的,這意味著每次調用一個以數組為參數的方法時都必須複製一個100 MB的數組,因此Java不能嚴格按值傳遞。每種語言都會傳遞對這個巨大數組的引用(作為一個值),並且如果該數組可以在方法內部進行本地更改,或者允許該方法(如Java那樣)全局修改數組,則採用寫時復制機制(來自調用者的視圖)和一些語言允許修改引用本身的值。

因此,簡而言之,在Java自己的術語中,Java是值傳遞,其中可以是:實數值或作為引用表示的

我一直認為Java是傳遞引用的

但是,我看過一些博客文章(例如, 這個博客 )聲稱它不是。

我不認為我理解他們所做的區別。

解釋是什麼?


Java始終是按值傳遞的 。 不幸的是,他們決定將對象的位置稱為“引用”。 當我們傳遞一個對象的值時,我們將引用傳遞給它。 這對初學者來說很困惑。

它是這樣的:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false 
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

在上面的示例中, aDog.getName()仍將返回"Max" 。 使用Dog "Fifi"在函數foo不更改main的值aDog ,因為對象引用按值傳遞。 如果它是通過引用傳遞的,那麼mainaDog.getName()將在調用foo後返回"Fifi"

同樣:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

在上面的例子中, Fifi是調用foo(aDog)之後的狗的名字,因為對象的名字是在foo(...)food上執行的任何操作都是這樣的,出於所有實際目的,它們是在aDog本身上執行的(除非d被更改為指向不同的Dog實例,例如d = new Dog("Boxer") )。


Java按值傳遞引用。

因此,您無法更改傳入的引用。


Java總是按值傳遞,沒有例外。

那麼如何讓任何人都對此感到困惑,並相信Java是通過引用傳遞的,或者認為他們有一個Java作為傳遞參考的例子? 關鍵是Java在任何情況下都不會直接訪問對象本身的值。 對對象的唯一訪問是通過對該對象的引用 。 因為Java對象總是通過引用而不是直接訪問,所以通常將字段和變量以及方法參數作為對象進行討論 ,而當它們只是對對象的引用時這種混淆源於這種(嚴格來說,不正確的)命名法的變化。

所以,在調用方法時

  • 對於原始參數( intlong等),pass by value是原語的實際值 (例如,3)。
  • 對於對象,pass by value是對象引用的值。

因此,如果你有doSomething(foo)public void doSomething(Foo foo) { .. }那麼兩個Foos已經復制了指向相同對象的引用

當然,通過值傳遞對對象的引用看起來非常像(並且在實踐中無法區分)通過引用傳遞對象。


Java總是按值傳遞,而不是通過引用傳遞

首先,我們需要了解通過值傳遞的內容以及通過引用傳遞的內容。

按值傳遞意味著您在內存中復制傳入的實際參數值。這是實際參數內容的副本

通過引用傳遞(也稱為按地址傳遞)意味著存儲實際參數的地址的副本

有時Java可以給出通過引用傳遞的錯覺。讓我們看看它是如何工作的,使用下面的例子:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

該程序的輸出是:

changevalue

讓我們一步一步地理解:

Test t = new Test();

眾所周知,它將在堆中創建一個對象並將引用值返回給t。例如,假設t的值是0x100234(我們不知道實際的JVM內部值,這只是一個例子)。

new PassByValue().changeValue(t);

將參考t傳遞給函數時,它不會直接傳遞對象測試的實際參考值,但會創建t的副本,然後將其傳遞給函數。由於它是通過值傳遞的,因此它傳遞變量的副本而不是它的實際引用。由於我們說t的值是0x100234,t和f都將具有相同的值,因此它們將指向同一個對象。

如果使用引用f更改函數中的任何內容,它將修改對象的現有內容。這就是我們獲得輸出的原因changevalue,該輸出在函數中更新。

為了更清楚地理解這一點,請考慮以下示例:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

這會拋出NullPointerException嗎?不,因為它只傳遞了引用的副本。在通過引用傳遞的情況下,它可能拋出一個NullPointerException,如下所示:

希望這會有所幫助。


在Java中,只傳遞引用並按值傳遞:

Java參數都是按值傳遞的(當方法使用時會復制引用):

對於基本類型,Java行為很簡單:將值複製到基本類型的另一個實例中。

在Objects的情況下,這是相同的:對像變量是僅包含使用“new”關鍵字創建的Object 地址的指針(桶),並且像原始類型一樣被複製。

行為可能與原始類型不同:因為複制的對像變量包含相同的地址(對於同一個對象)對象的內容/成員可能仍然在方法中被修改並且稍後訪問外部,給出了(包含)對象的錯覺本身是通過引用傳遞的。

“字符串”對像似乎是城市傳說中“對象通過引用傳遞” 的完美反例

實際上,在一個永遠不可能的方法中,更新作為參數傳遞的String的值:

String對象,由聲明為final的數組保存,不能修改。只有Object的地址可能被另一個使用“new”替換。使用“new”更新變量,不會讓Object從外部訪問,因為變量最初是按值傳遞並複制的。


Java是按值調用的。

這個怎麼運作

  • 您總是傳遞參考值的位副本!

  • 如果它是原始數據類型,則這些位包含原始數據類型本身的值,這就是為什麼如果我們更改方法內的header的值,那麼它不會反映外部的更改。

  • 如果它是一個像Foo foo = new Foo()這樣的對像數據類型,那麼在這種情況下,對像地址的副本會像文件快捷方式一樣傳遞,假設我們在C:\ desktop中有一個文本文件abc.txt,並假設我們設置了快捷方式相同的文件,並把它放在C:\ desktop \ abc-shortcut中,所以當你從C:\ desktop \ abc.txt訪問文件並寫入''並關閉文件時再次從快捷方式打開文件然後你write '是程序員學習的最大的在線社區'然後總文件更改將是'是程序員學習的最大的在線社區'這意味著從打開文件的位置開始無關緊要,每次我們訪問同一個文件時,我們都可以假設Foo為文件,並假設foo存儲在123hd7h(原始地址如C:\ desktop \ abc.txt)地址和234jdid(複製地址如C:\ desktop \ abc-shortcut,其中實際上包含了文件的原始地址)。所以為了更好地理解製作快捷方式文件和感覺...


一些帖子的一些更正。

C不支持通過引用傳遞。它總是通過價值。C ++確實支持按引用傳遞,但不是默認值,而且非常危險。

Java中的值是什麼並不重要:對象的原始或地址(粗略),它總是按值傳遞。

如果Java對象“行為”就像通過引用傳遞一樣,那就是可變性的屬性,並且與傳遞機製完全無關。

我不確定為什麼會這麼混亂,也許是因為很多Java“程序員”沒有經過正式培訓,因此不明白內存中究竟發生了什麼?


基本上,重新分配Object參數不會影響參數,例如,

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

將打印"Hah!"而不是null。這個工作的原因是因為bar是一個值的副本baz,它只是一個引用"Hah!" 。如果它是實際的引用本身,那麼foo將重新定義baznull


我剛剛注意到你引用了我的文章

Java規範說Java中的所有內容都是按值傳遞的。 在Java中沒有“pass-by-reference”這樣的東西。

理解這一點的關鍵是類似的東西

Dog myDog;

不是狗; 它實際上是指向狗的指針

這意味著,就在你擁有的時候

Dog myDog = new Dog("Rover");
foo(myDog);

你實際上是將創建的Dog對象的地址傳遞給foo方法。

(我說的主要是因為Java指針不是直接地址,但最容易想到它們)

假設Dog對象駐留在內存地址42處。這意味著我們將42傳遞給該方法。

如果方法被定義為

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

讓我們來看看發生了什麼。

  • 參數someDog設置為值42
  • 在線“AAA”
    • someDog跟隨它指向的Dog (地址42處的Dog對象)
    • 要求Dog (地址為42的那個)將他的名字改為Max
  • 在線“BBB”
    • 創建了一隻新Dog 。 讓我們說他在地址74
    • 我們將參數someDog分配給74
  • 在線“CCC”
    • someDog跟隨它所指向的Dog (地址為74的Dog對象)
    • 要求Dog (地址為74的那個)將他的名字改為Rowlf
  • 然後,我們回來了

現在讓我們考慮一下方法之外會發生什麼:

myDog改變了嗎?

關鍵是。

記住myDog是一個指針 ,而不是一個真正的Dog ,答案是否定的。 myDog的值仍為42; 它仍然指向原始的Dog (但請注意,由於行“AAA”,它的名稱現在是“Max” - 仍然是相同的狗; myDog的值沒有改變。)

按照地址並改變最後的內容是完全有效的; 但是,這不會改變變量。

Java的工作方式與C完全相同。您可以分配指針,將指針傳遞給方法,按照方法中的指針操作並更改指向的數據。 但是,您無法更改指針指向的位置。

在C ++,Ada,Pascal和其他支持pass-by-reference的語言中,您實際上可以更改傳遞的變量。

如果Java具有pass-by-reference語義,那麼我們在上面定義的foo方法會更改someDog在BBB上分配someDog時指向的位置。

將引用參數視為傳入的變量的別名。分配該別名時,傳入的變量也是如此。


我覺得爭論“傳遞引用與傳遞價值”並不是非常有用。

如果你說“Java是任意的(參考/值)”,在任何一種情況下,你都沒有提供完整的答案。 這裡有一些額外的信息,有助於理解記憶中發生的事情。

在我們進入Java實現之前,堆棧/堆上的崩潰過程:值以一種非常有序的方式在堆棧中上下移動,就像在自助餐廳的堆棧一樣。 堆中的內存(也稱為動態內存)是雜亂無章的。 JVM只是在任何地方找到空間,並釋放它,因為不再需要使用它的變量。

好的。 首先,本地原語進入堆棧。 所以這段代碼:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

結果如下:

聲明和實例化對象時。 實際的對像在堆上。 什麼在堆棧上? 堆上對象的地址。 C ++程序員會將此稱為指針,但是一些Java開發人員反對“指針”這個詞。 隨你。 只要知道對象的地址就在堆棧上。

像這樣:

int problems = 99;
String name = "Jay-Z";

數組是一個對象,所以它也在堆上。 那陣列中的對象怎麼樣? 它們獲得自己的堆空間,每個對象的地址都在數組內部。

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

那麼,當你調用一個方法時會傳入什麼? 如果傳入一個對象,那麼實際傳入的是對象的地址。 有些人可能會說地址的“價值”,有些人說它只是對象的引用。 這是“參考”和“價值”支持者之間聖戰的起源。 你所說的並不像你理解的那樣重要,傳入的是對象的地址。

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

創建一個String,並在堆中分配空間,並將字符串的地址存儲在堆棧中並賦予標識符hisName ,因為第二個String的地址與第一個String的地址相同,因此不會創建新的String並且沒有分配新的堆空間,但是在堆棧上創建了新的標識符。 然後我們調用shout() :創建一個新的堆棧幀,並創建一個新的標識符, name並分配已存在的String的地址。

那麼,價值,參考? 你說“土豆”。


為了顯示對比,請比較以下C++Java段:

在C ++中: 注意:錯誤的代碼 - 內存洩漏! 但它證明了這一點。

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

在Java中

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java只有兩種類型的傳遞:內置類型的值,以及對像類型的指針值。


Java按VALUE傳遞參數,按值傳遞參數。

長話短說:

對於來自C#的人:沒有“out”參數。

對於來自PASCAL的人:沒有“var”參數

這意味著您無法更改對象本身的引用,但您始終可以更改對象的屬性。

解決方法是使用StringBuilder參數String。你總是可以使用數組!


區別,或者也許只是我記憶中的方式,因為我曾經與原始海報的印象相同:Java總是按價值傳遞。Java中的所有對象(在Java中,除了基元之外的任何東西)都是引用。這些引用按值傳遞。


您永遠不能通過Java中的引用傳遞,並且一種明顯的方法是當您想要從方法調用返回多個值時。考慮C ++中的以下代碼:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

有時你想在Java中使用相同的模式,但你不能; 至少不是直接的。相反,你可以做這樣的事情:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

正如之前的答案中所解釋的那樣,在Java中,您將指向數組的指針作為值傳入getValues。這就足夠了,因為該方法然後修改數組元素,按照慣例,您期望元素0包含返回值。顯然,您可以通過其他方式執行此操作,例如構造代碼以使其不必要,或者構造可以包含返回值或允許設置它的類。但是,在C ++中可用的簡單模式在Java中不可用。


我一直認為它是“通過副本”。它是值的原始或引用的副本。如果它是原語,則它是作為值的位的副本,如果它是Object,則它是引用的副本。

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

java PassByCopy的輸出:

name = Maxx
name = Fido

原始包裝類和字符串是不可變的,因此使用這些類型的任何示例都不會與其他類型/對象相同。


我無法相信沒人提到Barbara Liskov。當她在1974年設計CLU時,她遇到了同樣的術語問題,並且通過共享(也稱為通過對象共享調用按對象調用)發明了這個特定情況的“通過值調用,其值為參考“。


據我所知,Java只知道按值調用。這意味著對於原始數據類型,您將使用副本,對於對象,您將使用對象的引用副本。不過我覺得有一些陷阱; 例如,這不起作用:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

這將填充Hello World而不是World Hello,因為在交換函數中,您使用的copys對main中的引用沒有影響。但是如果你的對像不是不可變的,你可以改變它,例如:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

這將在命令行上填充Hello World。如果將StringBuffer更改為String,它將只生成Hello,因為String是不可變的。例如:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

但是你可以為這樣的String創建一個包裝器,這樣就可以將它與字符串一起使用:

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

編輯:我相信這也是使用StringBuffer的原因,當涉及到“添加”兩個字符串,因為你可以修改原始對象,你不能用像String這樣的不可變對象。


的問題的關鍵是,字參考在表述“通過引用傳遞”是指某物從字的通常含義完全不同的參考在Java中。

通常在Java 引用中表示對對象的引用。但是,編程語言理論中通過引用/值傳遞的技術術語正在討論對存儲變量的存儲器單元引用,這是完全不同的。


簡而言之,Java對象具有一些非常特殊的屬性。

一般來說,Java有原始類型(intboolchardouble,等等)直接由值來傳遞。然後Java有對象(從中派生出來的所有東西java.lang.Object)。實際上,對象總是通過引用來處理(引用是一個你無法觸摸的指針)。這意味著實際上,對像是通過引用傳遞的,因為引用通常不是很有趣。但它確實意味著您無法更改指向的對象,因為引用本身是按值傳遞的。

這聽起來有點奇怪和令人困惑嗎?讓我們考慮C如何通過引用傳遞並按值傳遞。在C中,默認約定是按值傳遞。void foo(int x)按值傳遞int。void foo(int *x)是一個不需要的函數int a,而是一個指向int的指針:foo(&a)。可以使用它與&運算符傳遞變量地址。

把它帶到C ++,我們有參考。引用基本上(在此上下文中)隱藏等式的指針部分的語法糖:void foo(int &x)被調用foo(a),其中編譯器本身知道它是引用,並且a應該傳遞非引用的地址。在Java中,所有引用對象的變量實際上都是引用類型,實際上強制通過引用調用大多數意圖和目的,而沒有由例如C ++提供的細粒度控制(和復雜性)。





pass-by-value