owned this note
owned this note
Published
Linked with GitHub
# 參數傳遞方式
###### tags: `讀書會`
主要分為三種:
* call by value
複製 原有變數的值 到新的變數上。
* call by reference
引用參考到 新的變數 上,修改或賦予新值都會改變 原有變數的值。
* call by sharing
複製參考到新的變數上,修改會改變原有變數,但賦予新值則會產生新的參考。
## 用實例分析 JS 屬於哪種
```javascript=
function swap(a, b) {
var temp = a;
a = b;
b = temp;
}
var x = 10;
var y = 20;
swap(x, y);
console.log(x, y) // 10, 20
```
執行 `swap` 後可以發現 `x` , `y` 並沒有被改變
可以得知 `x` , `y` 在做為參數傳進 `swap` 時,並不是真的把 `x` , `y` 傳進去,而是他們的拷貝而已。
所以改變 `a` , `b` 時不會影響 `x` ,`y`。
這種把值拷貝的做法就是 call by value
![](https://blog.techbridge.cc/img/huli/value/value.gif)
在傳遞 **基本型別** 時要注意到一點
>All primitives are immutable, i.e., they cannot be altered.
>A primitive can be replaced, but it can't be directly altered.
JS 對於 基本型別 來說是不可能發生 call by reference 的
因為絕對不可能透過 `function` 內的引數去改變 `function` 外面的變數。
所以還不能我們還不能確定 JS 就是 call by value
因為還有 object , array 還沒測試!
```javascript=
function add(obj) {
obj.number++
}
var o = {number: 10}
add(o)
console.log(o.number) // 11
```
喔?居然在 function 裡面成功改變外面的東西了,但還不能確定這就是 call by reference 或 sharing。
來試試 重新賦予新的值 或發生什麼:
```javascript=
function add(obj) {
// 讓 obj 變成一個新的 object
obj = {
number: obj.number + 1
}
}
var o = {number: 10}
add(o)
console.log(o.number) // 10
```
如果是 call by reference ,在 `function` 裡面把 `obj` 的值改掉了,外面的 `o` 也會一起被改掉,變成那個新的 `object`。
而 call by sharing 基本上行為跟 reference 很像,差異在於在 `function` 裡面把 `obj` 重新賦值, `obj` 會重新指向一個新的 object 的記憶體位置,所以外面的 `o` 依舊還是原來的值。
![](https://i.imgur.com/HaaGfkT.png)
#### 結論就是:「在 JavaScript,primitive types 是 call by value,object 是 call by sharing」?
### 有一派的說法是 JavaScript 只有 call by value
在宣告一個 object 的時候,在底層實作上,其實這個 object 存的是一個記憶體位置。
將 object 傳遞給 `function` 時是將 記憶體位置 做為值傳進去的。
但老實說 這套用到 call by sharing 跟 call by reference也都通
因為以底層來說兩者都是傳記憶體位置進去。
那到底是哪一種?
### **ECMA 聖經** 完全沒有說
不論是 ECMA-262 edition 8 或是 ECMA-262 第一版都沒有明確的說 JS 到底是用何種傳遞方式。
## C ++
### pointer
指標可指向特定的記憶體位址,而不直接操作變數或物件
```cpp=
type* ptr; //每一個指標都有一個相對應的型態
int* iptr;
float* fptr;
char* cptr;
```
形態的指標差異在於說,今天要對記憶體位置做修改時,像是記憶體位置 `+1` , 其差異就會差一個型別這麼多。
![](https://i.imgur.com/MrHwm3X.jpg)
#### 指標擁有兩種操作特性
* 操作指標所儲存的位址
* 操作指標所指向位址之資料
使用提取 (Dereference)運算子 `*` 來提取指標所指向位址的資料
```cpp
int var = 10;
int *ptr = &var;
cout << "指標ptr儲存的值:" << ptr //0x22ff74
<< endl;
cout << "取出ptr指向的記憶體位置之值:" << *ptr //10
<< endl;
```
### reference
在c++底層中,reference 是通過 pointer實現的。也就是說,在實現層面上,reference 就是 pointer。
直接取得變數或物件的位址,並間接透過參考型態別名來操作物件
區別在於 :
* reference 代表了變數或物件的一個別名( alias ),本身沒有自己的記憶體位置
```cpp
int i = 10;
int* ptr_i = &i;
int& ref_i = i;
cout<< "i\t" << i << "\t&i\t"<< &i << endl;
cout<< "*ptr_i\t" << *ptr_i << "\tptr_i\t"<< ptr_i << "\t&ptr_i\t"<<&ptr_i<<endl;
cout<< "ref_i\t" << ref_i << "\t&ref_i\t"<< &ref_i << endl;
```
![](https://i.imgur.com/UYZV4u4.png)
* pointer 可以為空,雖然很危險!
```cpp
int* ptr_i;
```
* reference 不可為空,編譯會報錯!
```cpp
int& ref_i;
//error: ‘ref_i’ declared as reference but not initialized
```
* reference 一旦確定就不可被修改 , pointer 可更改指向其他記憶體位置
**reference**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
int i = 10;
int j = 20;
int& ref_i = i;
//i:10 , j:20 , ref_i:10
cout<< "ref_i\t" << ref_i << "\t&ref_i "<< &ref_i << endl;
cout<< "i\t" << i << "\t&i\t"<< &i << endl;
cout<< "j\t" << j << "\t&j\t"<< &j << endl<<endl;
ref_i = j;
//i:20 , j:20 , ref_i:20
cout<< "ref_i\t" << ref_i << "\t&ref_i "<< &ref_i << endl;
cout<< "i\t" << i << "\t&i\t"<< &i << endl;
cout<< "j\t" << j << "\t&j\t"<< &j << endl;
return 0;
}
```
![](https://i.imgur.com/qAk2fUC.png)
**pointer**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
int i = 10;
int j = 20;
int* ptr_i = &i;
//i:10 , j:20 , *ptr_i:10
cout<< "*ptr_i\t" << *ptr_i << "\tptr_i\t"<< ptr_i << endl;
cout<< "i\t" << i << "\t&i\t"<< &i << endl;
cout<< "j\t" << j << "\t&j\t"<< &j << endl<<endl;
//i:10 , j:20 , *ptr_i:20
ptr_i = &j;
cout<< "*ptr_i\t" << *ptr_i << "\tptr_i\t"<< ptr_i << endl;
cout<< "i\t" << i << "\t&i\t"<< &i << endl;
cout<< "j\t" << j << "\t&j\t"<< &j << endl;
}
```
![](https://i.imgur.com/YH8RGsM.png)
很少會直接如上的方式來使用參考,而是用於函式傳遞時一種「傳參考」(Pass by reference)方式,目的在於可於函式中直接操作目標變數或物件,或者是避免複製一個大型物件。
```cpp
void swap(int &a, int &b) {
// 印出 a 跟 b 所存的值與記憶體位置
cout<< "a\t" << a << "\t\tb\t"<<b<<endl;
cout<< "&a\t" << &a << "\t&b\t"<<&b<<endl;
int temp = b;
b = a;
a = temp;
}
int main(){
int x = 10;
int y = 20;
// 印出 x 跟 y 的記憶體位置
cout<< "&x\t" << &x << "\t&y\t"<<&y<<endl;
swap(x, y); // 傳記憶體位置進去
cout<< "x\t" << x << "\t\ty\t"<<y<<endl;
}
```
![](https://i.imgur.com/WKrKcHz.png)
就像上面 call by reference 舉例的那段程式碼一樣,x的記憶體位置跟a一樣,y的記憶體位置跟b一樣,因此你可以說他們兩者是「一模一樣」的東西。
可是在 call by value 的範例中,就算你傳的是指標好了,只有「指標裡面存的值(也就是指到的記憶體位置)」是一樣的,但指標本身還是有不同的記憶體位置。
```cpp
void swap(int *a, int *b) {
// 印出 a 跟 b 所存的值
cout<< "a\t" << a << "\tb\t"<<b<<endl;
cout<< "&a\t" << &a << "\t&b\t"<<&b<<endl;
int temp = *b;
*b = *a;
*a = temp;
}
int main(){
int x = 10;
int y = 20;
cout<< "x\t" << x << "\t\ty\t"<<y<<endl;
cout<< "&x\t" << &x << "\t&y\t"<<&y<<endl;
swap(&x, &y); // 傳記憶體位置進去
cout<< "x\t" << x << "\t\ty\t"<<y<<endl;
}
```
![](https://i.imgur.com/Wj1dwDi.png)
換句話說,在 call by value 的時候我們是「新建了一個變數a,並且讓a存的值跟傳進來的參數一樣」。
所以 call by value 會把傳進去的值複製(無論那個值是數字也好,記憶體位置也好,都會複製一份)
而在 call by reference 的時候,我們只是「讓a作為x的 alias,兩個是同樣的變數」,這也許是兩間之間最大的差異。
但 call by reference 在「最底層的實作」上當然也會有類似的行為,畢竟 reference 是通過 pointer實現的,但是你感覺不出來,因為 C++ 已經幫你處理掉了。
## 結論: 從「行為」上面來判別到底是屬於哪一種
每個人都對 call by reference 以及 call by value 的「定義」其實都不盡相同,而且也沒有一個權威性的出處能夠保證這個定義是正確的。
要說 call by reference 及 call by value 其實都是 call by value 也沒錯,畢竟妳是以 把 reference 當做 value 傳進 function 做為出發點思考的。
所以在該語言在其自身沒有表明自己是何種方式情況下,與其不斷探究,不如從行為上來思考會比較合乎邏輯。
有關技術名詞的解釋,在 **技術名詞紛爭多** 這本書中一段話說道:
>程式開發的世界中,名詞的創造經常是隨意的,曾經在 Java 中爭執不斷的考古題之一是:「Java 中有沒有 Pass by reference」,就現今來說,大家公認的答案是沒有,Java 只有 Pass by value,不過還是有人面對 Java 文件中經常出現 reference,而搞不清楚。
>說穿了,這個名詞與 C++ 中的 reference 定義不同,只不過 Java 最初不知道為什麼,也用了 reference 一詞,重點也不在搞清楚 Pass by value,重點是搞清楚透過參數操作物件時,會有什麼樣的行為。
![](https://i.imgur.com/pF6vat1.png)
面幾句話都是正確的:
* JavaScript 裡面只有 pass by value
* JavaScript 的 primitive type 是 pass by value,object 是 pass by sharing
### 參考資料
[深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?](https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/)
[詳解c++ 引用(reference)與指針(pointer)的區別與聯繫](https://blog.csdn.net/tianxiaolu1175/article/details/46889523)
[JavaScript - 參數傳遞方式 (2)](https://ithelp.ithome.com.tw/articles/10194299)
[指標與記憶體位址](https://openhome.cc/Gossip/CppGossip/Pointer.html)
[參考(reference)](https://openhome.cc/Gossip/CppGossip/Reference.html)