修改參數(shù)
func main() {
p := person{name : "zhangsan"廉涕, age : 18}
modifyPerson(p)
fmt.Println("name:", p.name, "age:", p.age)
}
func modifyPerson(p person) {
p.name = "lisi"
p.age = 19
}
type person struct {
name string
age int
}
// 輸出結(jié)果
name:zhangsan, age:18 // 發(fā)現(xiàn)結(jié)果沒有被改變艇拍,我們改成指針類型試試呢狐蜕?
modifyPerson(&p)
func modifyPerson(p *person){
p.name = "lisi"
p.age = 19
}
// 輸出結(jié)果
name: lisi, age: 19 // 接收參數(shù)修改為指針參數(shù),就可以滿足需求了卸夕。
值類型
上述示例中层释,定義的普通變量p是person類型的。在Go語言中快集,**person是一個值類型贡羔,而&p獲取的指針是*person類型的,即指針類型**个初。那么為什么值類型在參數(shù)傳遞中無法修改呢乖寒? 要從內(nèi)存講起。
變量的值是存儲在內(nèi)存中的院溺,而內(nèi)存都有一個編號楣嘁,稱為內(nèi)存地址。所以要想修改內(nèi)存中的數(shù)據(jù),就要找到這個內(nèi)存地址逐虚。我們來對比值類型變量在函數(shù)內(nèi)外的內(nèi)存地址聋溜,如下
func main() {
p := person{name : "zhangsan", age : 18}
fmt.Println("main函數(shù)叭爱,p的內(nèi)存地址:", &p)
modifyPerson(p)
fmt.Println("name:", p.name, "age:", p.age)
}
func modifyPerson(p person) {
fmt.Println("modifyPerson 函數(shù)p的內(nèi)存地址:", &p)
p.name = "lisi"
p.age = 19
}
// 輸出結(jié)果
main函數(shù)撮躁,p的內(nèi)存地址: 0x0000a3020
modifyPerson 函數(shù)p的內(nèi)存地址:0x0000a3040
name:zhangsan, age:18
// 我們發(fā)現(xiàn)內(nèi)存地址不一樣,意味著买雾,在modifyPerson函數(shù)中修改的參數(shù)p和main函數(shù)中的變量p不是同一個馒胆,這就是為什么我們在modifyPerson函數(shù)中修改了參數(shù)p,但是在main函數(shù)中打印結(jié)果中沒有修改的原因
導(dǎo)致這種結(jié)果的原因是**Go語言中函數(shù)傳參都是值傳遞**凝果。值傳遞值得是傳遞原來數(shù)據(jù)的一份拷貝,而不是原來的數(shù)據(jù)本身睦尽。
image-20211217101053089
調(diào)用modifyPerson函數(shù)傳遞變量p的時候器净,Go語言會拷貝一個p放在新的內(nèi)存中,這樣新的p的內(nèi)存地址就和原來的不一樣了当凡,但是里面的值是一樣的山害。即**副本的意思**,變量中的數(shù)據(jù)一樣沿量,但是內(nèi)存地址不一樣浪慌。
除了struct外,**浮點(diǎn)型朴则、整型权纤、字符串、布爾乌妒、數(shù)組汹想,這些都是值類型**。
指針類型
指針類型的變量保存的值就是數(shù)據(jù)對應(yīng)的內(nèi)存地址撤蚊,所以在函數(shù)參數(shù)傳遞是傳值的原則下古掏,拷貝的值也是內(nèi)存地址。
func main() {
p := person{name : "zhangsan"侦啸, age : 18}
fmt.Println("main函數(shù)槽唾,p的內(nèi)存地址:", &p)
modifyPerson(&p)
fmt.Println("name:", p.name, "age:", p.age)
}
func modifyPerson(p *person) {
fmt.Println("modifyPerson 函數(shù)p的內(nèi)存地址:", &p)
p.name = "lisi"
p.age = 19
}
// 輸出結(jié)果
main函數(shù),p的內(nèi)存地址: 0x0000a3020
modifyPerson 函數(shù)p的內(nèi)存地址:0x0000a3020
name:lisi, age:19
**指針類型的參數(shù)是永遠(yuǎn)可以修改原數(shù)據(jù)的光涂,因?yàn)樵趨?shù)傳遞時庞萍,傳遞的是內(nèi)存地址。**
提示: 值傳遞的是指針忘闻,即內(nèi)存地址挂绰。通過內(nèi)存地址可以找到元數(shù)據(jù)的那塊內(nèi)存,所以修改它也就等于修改了原數(shù)據(jù)。
引用類型
引用類型葵蒂,包括map交播、slice和chan。
**map**
func main() {
m := make(map[string]int)
m["奔跑的蝸牛"] = 18
fmt.Println("age:" : m["奔跑的蝸牛"])
modifyMap(m)
fmt.Println("age:" : m["奔跑的蝸牛"])
}
func modifyMap(m map[string]int) {
p["奔跑的蝸牛"] = 19
}
// 輸出結(jié)果
age:18
age:19
// 我們發(fā)現(xiàn)修改成功了践付。為什么沒有使用指針秦士,只是使用了map類型的參數(shù),按照Go語言值傳遞原則永高,modifyMap函數(shù)中map是一個副本隧土,為什么修改成功了呢?
一切原因要從 **make 這個Go語言內(nèi)建的函數(shù)說起**命爬。在Go語言中曹傀,任何創(chuàng)建map的代碼(不管是字面量還是make函數(shù))最終調(diào)用的都是 **runtime.makemap函數(shù)**。
提示: 用字面量或者make函數(shù)的方式創(chuàng)建map饲宛,并轉(zhuǎn)換成 makemap 函數(shù)的調(diào)用皆愉,這個轉(zhuǎn)換是Go語言編譯器自動幫我們做的。
func makmap(t *maptype, hint int, h*hmap) *hamp{
}
從源碼可以看出艇抠,Go語言的map類型本質(zhì)上就是 *hmap幕庐,所以根據(jù)替換原則,`modifyMap(a map) 函數(shù)其實(shí)就是modifyMap(a *hmap)`家淤。 這就和上面的指針類型的參數(shù)調(diào)用一樣了异剥,也就是通過map類型的參數(shù)可以修改原始數(shù)據(jù)的原因,因?yàn)楸举|(zhì)上就是指針絮重。
為了驗(yàn)證創(chuàng)建的map是一個指針冤寿,修改上述示例,
func main() {
m := make(map[string]int)
m["奔跑的蝸牛"] = 18
fmt.Println("age:" : m["奔跑的蝸牛"])
fmt.Println("main函數(shù)內(nèi)存地址:", m)
modifyMap(m)
fmt.Println("age:" : m["奔跑的蝸牛"])
}
func modifyMap(m map[string]int) {
p["奔跑的蝸牛"] = 19
fmt.Println("modifyMap函數(shù)內(nèi)存地址:", m)
}
// 輸出結(jié)果
age:18
main函數(shù)內(nèi)存地址:0x000060170
age:19
modifyMap函數(shù)內(nèi)存地址:0x000060170
// 從輸出結(jié)果看青伤,內(nèi)存地址一模一樣疚沐,所以可以修改原始數(shù)據(jù)。而且在打印指針的時候潮模,直接使用的是變量m和p亮蛔,并沒有用取地址符&,因?yàn)樗麄儽臼【褪侵羔樓嫦幔詻]必要在使用&取地址了
Go語言通過make函數(shù)或字面量的包裝為我們省去了指針的操作究流,讓我們可以更容易的使用map。其實(shí)就是**語法糖**动遭,這是編程界的老傳統(tǒng)了芬探。
注意: 這里的map可以理解為引用類型, 但是它本質(zhì)是指針厘惦,只是可以叫做引用類型而已偷仿。在參數(shù)傳遞時哩簿,它還是值傳遞,并不是其他編程語言中所謂的引用傳遞酝静。
chan
channel也可以理解為引用類型节榜, 而它本質(zhì)也是一個指針。
func makechan(t *chantype, size int64) *hchan{}
// 從源碼可以看到别智,所創(chuàng)建的chan其實(shí)是個*hchan宗苍,所以它在參數(shù)傳遞中也和map一樣
**嚴(yán)格的說,Go語言沒有引用類型**薄榛,但是我們可以把map讳窟、chan稱為引用類型,這樣便于理解敞恋。除了map丽啡、chan外,Go語言中的函數(shù)硬猫、接口补箍、slice切片都可以稱為引用類型。
類型零值
在Go語言中浦徊,定義變量要么通過聲明、要么通過make和new函數(shù)天梧,不一樣的是make和new函數(shù)屬于顯示聲明并初始化盔性。如果我們聲明的變量沒有顯示聲明初始化,那么該變量的默認(rèn)值就是對應(yīng)類型的零值呢岗。
類型 | 零值 |
---|---|
數(shù)值類型(int冕香、float) | 0 |
bool | false |
string | ""(空字符串) |
struct | 內(nèi)部字段零值 |
slice | nil |
map | nil |
指針 | nil |
函數(shù) | nil |
chan | nil |
interface | nil |
總結(jié):在Go語言中,函數(shù)的參數(shù)傳遞只有值傳遞后豫,而且傳遞的實(shí)參都是原始數(shù)據(jù)的一份拷貝悉尾。如果拷貝的內(nèi)容是值類型的,那么在函數(shù)中無法修改原始數(shù)據(jù)挫酿,如果拷貝的內(nèi)容是指針(或者可以理解為引用類型)构眯,那么可以在函數(shù)中修改原始數(shù)據(jù)。