一文搞懂引數傳遞原理
阿新 • • 發佈:2021-01-12
![](https://i.loli.net/2021/01/12/1s37bXrxSl8Cp2f.jpg)
# 前言
最近一年多的時間陸續接觸了一些對我來說陌生的語言,主要就是 `Python` 和 `Go`,期間為了快速實現需求只是依葫蘆畫瓢的擼程式碼;並沒有深究一些細節與原理。
就拿引數傳遞一事來說各個語言的實現細節各不相同,但又有類似之處;在許多新手入門時容易搞不清楚,導致犯一些低階錯誤。
# Java
## 基本型別傳遞
先拿我最熟悉的 `Java` 來說,我相信應該沒人會寫這樣的程式碼:
```java
@Test
public void testBasic() {
int a = 10;
modifyBasic(a);
System.out.println(String.format("最終結果 main a==%s", a));
}
private void modifyBasic(int aa) {
System.out.println(String.format("修改之前 aa==%s", aa));
aa = 20;
System.out.println(String.format("修改之後 aa==%s", aa));
}
```
輸出結果:
```java
修改之前 aa==10
修改之後 aa==20
最終結果 main a==10
```
不過從這段程式碼的目的來看應該是想要修改 `a` 的值,從直覺上來說如果修改成功也是能理解的。
至於結果與預期不符合的根本原因是理解錯了引數的值傳遞與引用傳遞。
---
在這之前還是先明確下值傳遞與引用傳遞的區別:
![](https://i.loli.net/2021/01/12/ztqLVPTjmacZf1X.jpg)
這裡咱們先丟擲結論,`Java` 採用的是值傳遞;這樣也能解釋為什麼上文的例子沒有成功修改原始資料。
參考下圖更好理解:
![](https://i.loli.net/2021/01/12/XJpdDAKOBhwtSxo.jpg)
當發生函式呼叫的時候 `a` 將自己傳入到 `modifyBasic` 方法中,同時將自己的值複製了一份並賦值給了一個新變數 `aa` 從圖中可以看出這是 `a` 和 `aa` 兩個變數沒有一毛錢關係,所以對 `aa` 的修改並不會影響到 `a`。
有點類似於我把蘋果給了老婆,她把蘋果削好了;但我手裡這顆並沒有變化,因為她只是從餐盤裡拿了一顆一模一樣的蘋果削好了。
如果我想要她那顆,只能讓她把削好的蘋果給我;也就類似於使用方法的返回值。
```java
a = modifyBasic(a);
```
## 引用型別傳遞
下面來看看引用型別的傳遞:
```java
private class Car{
private String name;
public Car(String name) {
this.name = name;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
'}';
}
}
@Test
public void test01(){
Car car1 = new Car("benz");
modifyCar1(car1);
System.out.println(String.format("最終結果 main car1==%s", car1));
}
private void modifyCar1(Car car){
System.out.println(String.format("修改之前 car==%s", car));
car.name = "bwm";
System.out.println(String.format("修改之後 car==%s", car));
}
```
在這個例子裡先建立了一個 `benz` 的 `car1`,通過一個方法修改為 `bmw` 那最開始的 `car1` 會受到影響嘛?
```java
修改之前 car==Car{name='benz'}
修改之後 car==Car{name='bwm'}
最終結果 main car1==Car{name='bwm'}
```
結果可能會與部分人預期相反,這樣的修改卻是可以影響到原有資料的?這豈不是和`值傳遞`不符,看樣子這是`引用傳遞`吧?
別急,通過下圖分析後大家就能明白:
![](https://i.loli.net/2021/01/12/StL2o1wKqvTazcI.jpg)
在 `test01` 方法中我們建立了一個 `car1` 的物件,該物件存放於堆記憶體中,假設記憶體地址為 `0x1102` ,於是 `car1` 這個變數便應用了這塊記憶體地址。
當我們呼叫 `modifyCar1` 這個方法的時候會在該方法棧中建立一個變數 `car` ,接下來重點到了:
這個 `car` 變數是由原本的入參 `car1` 複製而來,所以它所對應的堆記憶體依然是 `0x1102`;
所以當我們通過 `car` 這個變數修改了資料後,本質上修改的是同一塊堆記憶體中的資料。從而原本引用了這塊記憶體地址的 `car1` 也能檢視到對應的變化。
這裡理解起來可能會比較繞,但我們記住一點就行:
傳遞引用型別的資料時,傳遞的並不是引用本身,依然是值;只是這個`值` 是**記憶體地址**罷了。
因為把相同的記憶體地址傳過去了,所以對資料的操作依然會影響到外部。
所以同理,類似於這樣的程式碼也會影響到外部原始資料:
```java
@Test
public void testList(){