這里我們著重討論參數(shù)傳遞的方式以及在 Golang 中函數(shù)調用前后(當然包括參數(shù)傳遞)對實參的影響荔茬。先了解一些基本概念废膘。
參數(shù)傳遞
定義
參數(shù)傳遞慕蔚,是在程序運行中,實際參數(shù)就會將參數(shù)值傳遞給相應的形式參數(shù)灌闺,然后在函數(shù)中實現(xiàn)對數(shù)據(jù)處理和返回的過程坏瞄。
- 實際參數(shù):簡稱實參,在調用函數(shù)/方法時鸠匀,從主調過程傳遞給被調用過程的參數(shù)值。實參可以是變量名缀棍、數(shù)組名、常數(shù)或者表達式父腕。
- 形式參數(shù):簡稱形參,指出現(xiàn)在函數(shù)/方法形參表中的變量名璧亮。函數(shù)/方法在被調用前沒有為他們分配內存斥难,其作用是說明自變量的類型和形態(tài)以及在過程中的作用。
- 實參與形參的關系:
- 形參只能是變量(要指明它的數(shù)據(jù)類型)蘸炸;實參可以是變量、常量或者表達式穷当。
- 實參與形參的個數(shù)淹禾、位置以及它們對應的數(shù)據(jù)類型應當一致馁菜。
- 調用函數(shù)時若出現(xiàn)實參時數(shù)組名铃岔,則傳遞給形參的時數(shù)組的首地址。
- 實參傳遞給形參是單向傳遞智嚷。形參變量在未出現(xiàn)函數(shù)調用時并不占用內存,只在調用時才占用盏道。調用結束后將釋放內存载碌。
方法
按值傳遞參數(shù)
按值傳遞參數(shù)時,是將實參變量的值復制到一個臨時存儲單元中嫁艇。如果在調用過程中改變了形參的值,不會影響實參變量本身论皆,即實參變量保持調用前的值不變。
按地址傳遞參數(shù)
按地址傳遞參數(shù)時纯丸,把實參變量的地址傳送給被調用過程静袖,實參和形參共用同一內存地址。在被調用過程中队橙,形參的值一旦改變。相應實參的值也跟著改變捐康。如果實參是一個常數(shù)或者表達式(不含變量的表達式,也可當作常數(shù))贮匕,則按傳值方式處理花枫。
按數(shù)組傳遞參數(shù)
按照按地址傳遞的方式傳遞數(shù)組掏膏。當數(shù)組作為實參傳遞給函數(shù)/方法敦锌,系統(tǒng)將實參數(shù)組的起始地址傳給過程使形參數(shù)組也具有與實參數(shù)組相同的起始地址。
Golang 中的參數(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ù)中改變實參的值崔列,則使用指針傳遞:
var i int = 5
func main() {
modify(&i)
fmt.Println(i)
}
func modify(i *int) {
*i = 6
}
// Output:
// 6
關于 slice 的參數(shù)傳遞
數(shù)組的參數(shù)傳遞
使用數(shù)組元素(array[x])或者數(shù)組(array)作為函數(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ù)也是進行的值傳遞
。但是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ù)里面對 slice 進行重新賦值不會改變它的地址(因為這里輸出了兩個相同的地址)。但是下面一段代碼可能有點讓人迷惑:
// 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ù)其實是傳遞 slice 的值惹挟,這個值被稱作一個 header
缝驳,它只包含了一個指向底層數(shù)組的指針。當向函數(shù)傳遞一個 slice 用狱,將復制一個 header 的副本,這個副本包含一個指向同一個底層數(shù)組的指針摇展。修改 slice 的元素間接地修改底層數(shù)組的元素,也就是所有指向同一個底層數(shù)組的 slice 會響應這個變化咏连,主函數(shù)的 slice 也就一同修改了 s[0] 的值鲁森。
關于這個問題,其實我們只要在每個操作上面輸出它的地址歌溉,只要地址不變,就說明修改會對 main
函數(shù)里面的 slice 產(chǎ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ù)的只是一個 slice 的副本匙头,它們不是指向同一個內存地址的。在 main
函數(shù)和 modify2
函數(shù)里面我們打印了 s[0] 的內存地址蹂析,發(fā)現(xiàn)它們的內存地址是相同的,所以當我們在 modify2
函數(shù)里面修改 s[0] 會影響 s[0] 的原始值识窿。
在sof搜了一下,發(fā)現(xiàn)這個回答比較易懂缩宜,做個記錄。
Github锻煌, Go 愛好者,不定時更新宋梧。