基礎程式設計(12)-傳值與傳址

在我們撰寫函數與副程式的時候,可能會需要在呼叫時傳遞參數給它們,這時候有兩種傳遞參數的方式,分別是「傳值(Call by Value)」與「傳址(Call by Reference)」。顧名思義前者是傳遞”值”,也就是變數裡存放的內容;後者是傳遞”位置”,則代表記憶體位置。本篇將詳細說明兩者的差異與使用方式。

正本與副本

問你要傳值或傳址的意思,就像在問給對方正本還是副本。如果給對方正本,他在正本上做任何修改都回不去了;反之如果給副本,無論對方畫了多少塗鴉在上面,正本都是安全的。傳值就是只給函數副程式變數的”副本”(變數裡存放的內容),所以無論函數副程式怎麼修改傳來的變數,都不會影響主程式的變數。而傳址就是把變數的”正本”交給副程式修改,所以一旦變數被副程式修改了,主程式裡的變數值也會隨之變動。

VB.NET傳值與傳址

Module Module1
    Sub Main()
        Dim name As String = "摩刻部落"
        Dim articleCount As Integer = 0

        addCount(name, articleCount)

        Console.WriteLine(name & "發表的文章數已更新至 " & articleCount & " 篇")

        Console.ReadKey()   '讀取鍵盤以暫停視窗
    End Sub

    'ByVal: 傳值; ByRef: 傳址
    Sub addCount(ByVal n As String, ByRef count As Integer)
        n = "moke.tw"
        count = 308

        Console.WriteLine(n & " 目前發表了 " & count & " 篇文章")
    End Sub
End Module

利用VB.NET的範例讓我們了解傳值與傳址實際運作的狀況。首先看到第 3、4 行宣告了兩個變數,在第 6 行將它傳給 addCount 副程式,所以接著看到第 14 行副程式的宣告,n 是”傳值”,而 count 則是”傳址”。這代表我們將主程式 name 的內容複製一份給 addCount 副程式裡的變數 n,而 articleCount 則是直接給交給副程式,只不過在 addCount 副程式裡,它的名字暫時叫 count,實際上是同一個變數。

所以我們在 addCount 副程式裡試著修改這兩個變數,看看會發生什麼結果。在 15、16 行修改完畢後,我們先在 18 行輸出目前的變數,以確保變數被修改了。確認完畢後返回主程式第 6 行,繼續往下執行。第 8 行我們再把變數輸出一次,看看會不會和副程式輸出相同的結果,於是我們看到畫面上出現兩排文字:

example_byref

第一行是副程式輸出的,第二行則是主程式輸出的。我們可以看到兩個變數都在 addCount 裡被修改,但是回到主程式輸出時,只有 articleCount 的值”真正地”被副程式修改,而 name 還保持原本的內容。這就是因為 name 以傳值(副本)傳遞,而 articleCount 以傳址(正本)傳遞的結果。

下面用一張示意圖說明第 6 行與第 14 行執行時,會發生什麼事情。傳值的 name 是複製內容給 n,而傳址的 articleCount 則是傳送記憶體的位置給 count,所以 articleCount 和 count 會存取同一個記憶體位置:

diagram_byref

傳值與傳址的使用時機

在可以選擇的情況之下,撰寫函數或副程式時,盡可能使用傳值(Call by Value)的方式傳遞,而避免使用傳址(Call by Reference)。因為傳址修改錯誤就無法回頭了,而且同一個變數在不同程式位置以不同名稱被修改,這會使系統不好維護。一般來說函數(function)只會使用「傳值」的方式傳遞,因為函數的設計概念是要取得”回傳值”,因此回傳值才是重點,輸入的變數不應該在函數內被修改。

至於撰寫副程式時,如果這支副程式原本就是被設計來修改某種變數,那就使用傳址傳遞吧;如果副程式的重點不在於修改變數,而是做其它事情時,就盡量使用傳值傳遞。

Java傳值與傳址

Java語言不像VB.NET有提供程式設計師指定傳值或傳址,而是根據傳遞的資料型態自動決定變數要採取那一種方式傳遞。Java語言只有基本資料型態(int、double、char、boolean…等等)以及 String 物件會採用傳值(Call by Value),其它資料型態的變數都會使用傳址(Call by Reference)的方式傳遞。