關於傳值(Pass by value),以及傳參(Pass by reference),其實,是常見於C++圈子的名詞,曾在Java圈嘈嚷過一段時間,而沉寂一陣之後,近來,我發現類似的爭議,在JavaScript圈復出了。

於是,此時又引出了一個更古老的名詞Call by sharing說選我正解,但這不是用明朝的劍斬清朝的官嗎?

傳值、傳參之爭再起?

Java的語法與C++相近,在Java正當火紅的那個年代,吸引了許多來自C++的開發者。而他們基於過往的經驗,試圖以C++中觀念來理解(與批判)Java。

例如,面對Java中物件作為呼叫方法時的引數時,許多C++開發者為了其行為究竟是傳參還是傳值?而開戰了。

不過,Java語言的創建者James Gosling倒是出面講話了,他在《The Java Programming Language》第二版中,就談到:「Java只有傳值」。

早期昇陽Java官方教材也特別留了一節,來解釋為什麼Java中只有傳值,然而當時的我真心希望,那一節不要存在。

因為,不需要用上這個名詞,即可解釋Java中物件作為引數時的行為,這並沒有什麼問題。

此類名詞只會令戰火延燒到課堂上,特別是一些有C++背景的學員,總是為此提出異議。不巧地,由於在Java中也使用參考(reference)這個名詞,為此而始終堅持那是傳參行為的,大有人在,而我們若費盡唇舌解釋到最後,往往只得到懷疑的眼神。

最後,在Java圈中,是這麼解決的!

如果一定要使用傳值或傳參,來說明物件作為引數時發生了什麼,那麼,就用C++的定義來解釋(別管Java中的參考一詞了),而在共同的定義討論下,Java就只有傳值。

後來,或許因為以C++作為入門語言的人逐漸變少,所以,使得這類吵架文也就逐漸消失,印象中,大概有十年左右,我沒再看到傳值、傳參的相關討論了。

因此,我在不久前看到JavaScript圈子又在討論傳值、傳參時,有點訝異。而且,有些開發者還說,JavaScript中其實是Call by sharing,因為參數共享了同一物件。

為此,我特地認真地查了一下,想知道Call by sharing倒底是什麼。事實上,這個用詞最早可追溯至1979年的CLU語言參考手冊……

呃……看來又是用A語言名詞解釋B語言的行為,而有些相關的文件,看來也沒細究過名詞來源,就用來討論了。

C++中的傳參

JavaScript圈子會討論傳值、傳參,甚至Call by sharing,大家所持的理由,跟早期Java圈子會引發戰火的原因並不相同。

這是因為,JavaScript有基本型態與複合型態(也就是物件),而為了解釋兩者作為引數時的差異,所以,才引用了傳值、傳參等名詞。

其實,參考一詞在多個語言中都在使用,也有各自的定義,但是,若沒定義「參考」到底是指什麼,後續怎麼解釋,都會有問題。

例如,在C++ 11以後,其實有兩種參考,過去嘈嚷的對象是lvalue參考,可看成是變數的別名。如果int n = 10,int &r = n,那麼,r就是n,而存取r的記憶體空間,就是存取n的記憶體空間,r若被指定為20,透過n取得的就是20。

呼叫函式時,引數與參數之間的關係,就像指定的行為,在C++中參數若宣告為參考,呼叫時的引數傳遞行為,就被稱為傳參。

就這個定義來看,不管是基本型態或是複合型態,JavaScript只有一個情境會最接近這個行為,也就是非嚴格模式下,函式中arguments與參數之間的行為。

例如,若以foo({x:10})呼叫底下的foo函式:

function foo(p) {
console.log(p.x); // 顯示 10
arguments[0] = {x: 20}
console.log(p.x); // 顯示 20
p = {x: 30}
console.log(arguments[0].x); // 顯示 30
}

所以,C++中的參考為什麼會被稱為參考呢?因為它「參考了來源變數」的一切。

以int n = 10,int &r = n來說,對r求值,那麼,就會參考對n求值的結果;對&r求值,那就會參考對&n求值的結果;而對r進行指定,就會參考對n指定的結果。

事實上,參考一旦建立起來之後,就不能改變參考對象,因此,才說參考可看成是別名。

JavaScript的參考

在JavaScript當中,也使用參考這個名詞,然而「意義上就是變數」,例如,存取一個不存在的變數,在JavaScript中會引發ReferenceError,此時,參考的意思在JavaScript的使用上,是指變數可以「參考至(refer to)值」,值可以是基本型態,或是複合型態。

對於基本型態來說,let x = 10這種指定,表示x參考至10,如果之後執行x = 20,表示x參考至20。對於物件,也是同樣的描述,let f1 = new Foo(),我們可以說,f1參考建構出來的物件,若是let f2 = f1呢?就是f2「參考至」f1「參考的物件」。

若硬要以C++實作出類似的行為來比擬,會像是Foo *f1 = new Foo(),f1儲存了位址,Foo *f2 = f1的話,會將f1儲存的位址複製並儲存在f2,基本型態的話會像是int *x = new int(10),x = new int(20)這樣,指定給x的都是位址。

不過,嚴格來說,JavaScript也不適合用傳值來描述這類行為,因為,C++中的基本型態值,是直接儲存在變數,而不是像這邊的x,是儲存了new int(10)的位址。

也就是說,除了arguments與參數的關係之外,JavaScript的參考(也就是變數)是「參考值」,與C++中的參考是「參考變數」,在定義上,兩者是不同的。硬是要跟C++的參考扯上關係的話,還比較像是C++ 11以後的rvalue參考。

明朝的劍斬清朝的官?

至於那個Call by sharing呢?

因為,我沒看過CLU語言,不知道在該語言中是怎麼定義的,不過,就JavaScript圈子中所看到的解釋,概念上,這個辭彙應該是指值在變數之間共享,然後呢?我們該拿CLU的劍來斬JavaScript的官嗎?這麼一來,感覺只會更混亂,而不是釐清什麼事情!

事實上,電腦科學界所用的名詞定義,本來就不嚴謹,我們不妨來看看〈Evaluation strategy〉,被稱為call by xxx者,可多著呢!而這些名詞之間的界線,也不是完全分明的。

因此,為了避免誤會,關於傳值、傳參,還是留在C++中討論就好,既然目前沒人在用CLU,就忘了Call by sharing吧!

在Java、JavaScript、Python等這類語言中,若剛好有同名的術語存在,我們是應該搞清楚它在該語言中確切的定義,而不是硬用其他語言中的定義,甚至是不清楚來源的名詞來理解。

作者簡介


Advertisement

更多 iThome相關內容