這里我們著重討論參數(shù)傳遞的方式以及在 Golang 中函數(shù)調(diào)用前后(當(dāng)然包括參數(shù)傳遞)對實(shí)參的影響。先了解一些基本概念。
參數(shù)傳遞
定義
參數(shù)傳遞,是在程序運(yùn)行中,實(shí)際參數(shù)就會將參數(shù)值傳遞給相應(yīng)的形式參數(shù),然后在函數(shù)中實(shí)現(xiàn)對數(shù)據(jù)處理和返回的過程。
- 實(shí)際參數(shù):簡稱實(shí)參,在調(diào)用函數(shù)/方法時(shí),從主調(diào)過程傳遞給被調(diào)用過程的參數(shù)值。實(shí)參可以是變量名、數(shù)組名、常數(shù)或者表達(dá)式。
- 形式參數(shù):簡稱形參,指出現(xiàn)在函數(shù)/方法形參表中的變量名。函數(shù)/方法在被調(diào)用前沒有為他們分配內(nèi)存,其作用是說明自變量的類型和形態(tài)以及在過程中的作用。
- 實(shí)參與形參的關(guān)系:
- 形參只能是變量(要指明它的數(shù)據(jù)類型);實(shí)參可以是變量、常量或者表達(dá)式。
- 實(shí)參與形參的個數(shù)、位置以及它們對應(yīng)的數(shù)據(jù)類型應(yīng)當(dāng)一致。
- 調(diào)用函數(shù)時(shí)若出現(xiàn)實(shí)參時(shí)數(shù)組名,則傳遞給形參的時(shí)數(shù)組的首地址。
- 實(shí)參傳遞給形參是單向傳遞。形參變量在未出現(xiàn)函數(shù)調(diào)用時(shí)并不占用內(nèi)存,只在調(diào)用時(shí)才占用。調(diào)用結(jié)束后將釋放內(nèi)存。
方法
按值傳遞參數(shù)
按值傳遞參數(shù)時(shí),是將實(shí)參變量的值復(fù)制到一個臨時(shí)存儲單元中。如果在調(diào)用過程中改變了形參的值,不會影響實(shí)參變量本身,即實(shí)參變量保持調(diào)用前的值不變。
按地址傳遞參數(shù)
按地址傳遞參數(shù)時(shí),把實(shí)參變量的地址傳送給被調(diào)用過程,實(shí)參和形參共用同一內(nèi)存地址。在被調(diào)用過程中,形參的值一旦改變。相應(yīng)實(shí)參的值也跟著改變。如果實(shí)參是一個常數(shù)或者表達(dá)式(不含變量的表達(dá)式,也可當(dāng)作常數(shù)),則按傳值方式處理。
按數(shù)組傳遞參數(shù)
按照按地址傳遞的方式傳遞數(shù)組。當(dāng)數(shù)組作為實(shí)參傳遞給函數(shù)/方法,系統(tǒng)將實(shí)參數(shù)組的起始地址傳給過程使形參數(shù)組也具有與實(shí)參數(shù)組相同的起始地址。
Golang 中的參數(shù)傳遞
值傳遞
事實(shí)證明 Golang 的參數(shù)傳遞(目前我接觸的常用的類型如: string 、 int 、 bool 、array 、 slice 、 map 、 chan )都是值傳遞。
func main() {
b := false
fmt.Println("b's address is:", &b)
bo(b)
fmt.Println(b)
}
func bo(b bool) {
fmt.Println("this address is different from the original address:", &b)
b = true
}
// Output:
// b's address is: 0xc0420361ac
// this address is different from the original address: 0xc0420361fa
// false
從上面代碼可以看出在函數(shù)中修改值不會影響實(shí)參的原始值。其余的類型讀者自行嘗試輸出查看結(jié)果。若要在函數(shù)中改變實(shí)參的值,則使用指針傳遞:
var i int = 5
func main() {
modify(&i)
fmt.Println(i)
}
func modify(i *int) {
*i = 6
}
// Output:
// 6
關(guān)于 slice 的參數(shù)傳遞
數(shù)組的參數(shù)傳遞
使用數(shù)組元素(array[x])或者數(shù)組(array)作為函數(shù)參數(shù)時(shí),其使用方法和普通變量相同。即是值傳遞。
func modifyElem(a int) {
a += 100
}
func modifyArray(a [5]int) {
a = [5]int{5,5,5,5,5}
}
func main() {
var s = [5]int{1, 2, 3, 4, 5}
modifyElem(s[0])
fmt.Println(s[0])
modifyArray(s)
fmt.Println(s)
}
// Output:
// 1
// [1 2 3 4 5]
slice 的參數(shù)傳遞
slice 作為實(shí)參傳入函數(shù)也是進(jìn)行的值傳遞。但是slice引用array。
// modify s
func modify(s []int) {
fmt.Printf("%p \n", &s)
s = []int{1,1,1,1}
fmt.Println(s)
fmt.Printf("%p \n", &s)
}
func main() {
a := [5]int{1, 2, 3, 4, 5}
s := a[:]
fmt.Printf("%p \n", &s)
modify(s)
fmt.Println(s[3])
}
// Output:
// 0xc042002680
// 0xc0420026c0
// [1 1 1 1]
// 0xc0420026c0
// 4
可以看到,實(shí)參傳遞之前的地址和在函數(shù)里面的地址是不同的,而且在函數(shù)里面修改實(shí)參的值也不會影響實(shí)參的實(shí)際值。當(dāng)然,在函數(shù)里面對 slice 進(jìn)行重新賦值不會改變它的地址(因?yàn)檫@里輸出了兩個相同的地址)。但是下面一段代碼可能有點(diǎn)讓人迷惑:
// modify s[0] value
func modify(s1 []int) {
s1[0] += 100
}
func main() {
a := [5]int{1, 2, 3, 4, 5}
s := a[:]
modify(s)
fmt.Println(s[0])
}
// Output:
// 101
在 StackOverFlow 上有人做出了解釋, Are golang slices pass by value? 摘要如下:
Everything in Go is passed by value. Slices too. But a slice value is a header, describing a contiguous section of a backing array, and a slice value only contains a pointer to the array where the elements are actually stored. The slice value does not include its elements (unlike arrays).
So when you pass a slice to a function, a copy will be made from this header, including the pointer, which will point to the same backing array. Modifying the elements of the slice implies modifying the elements of the backing array, and so all slices which share the same backing array will "observe" the change.
簡單地說: slice 作為參數(shù)傳遞給函數(shù)其實(shí)是傳遞 slice 的值,這個值被稱作一個 header ,它只包含了一個指向底層數(shù)組的指針。當(dāng)向函數(shù)傳遞一個 slice ,將復(fù)制一個 header 的副本,這個副本包含一個指向同一個底層數(shù)組的指針。修改 slice 的元素間接地修改底層數(shù)組的元素,也就是所有指向同一個底層數(shù)組的 slice 會響應(yīng)這個變化,主函數(shù)的 slice 也就一同修改了 s[0] 的值。
關(guān)于這個問題,其實(shí)我們只要在每個操作上面輸出它的地址,只要地址不變,就說明修改會對 main 函數(shù)里面的 slice 產(chǎn)生影響,對上面的代碼進(jìn)一步修改:
// modify s[0] value
func modify2(s []int) {
fmt.Printf("%p \n", &s)
fmt.Printf("%p \n", &s[0])
s[0] += 100
fmt.Printf("%p \n", &s)
fmt.Printf("%p \n", &s[0])
}
func main() {
a := [5]int{1, 2, 3, 4, 5}
s := a[:]
fmt.Printf("%p \n", &s)
fmt.Printf("%p \n", &s[0])
modify2(s)
fmt.Println(s[0])
}
// Output:
// 0xc04203c400
// 0xc042039f50
// 0xc04203c440
// 0xc042039f50
// 0xc04203c440
// 0xc042039f50
// 101
實(shí)參傳遞給函數(shù)的只是一個 slice 的副本,它們不是指向同一個內(nèi)存地址的。在 main 函數(shù)和 modify2 函數(shù)里面我們打印了 s[0] 的內(nèi)存地址,發(fā)現(xiàn)它們的內(nèi)存地址是相同的,所以當(dāng)我們在 modify2 函數(shù)里面修改 s[0] 會影響 s[0] 的原始值。
在sof搜了一下,發(fā)現(xiàn)這個回答比較易懂,做個記錄。
Github, Go 愛好者,不定時(shí)更新。