go語(yǔ)言陷阱:循環(huán)與指針的誤用導(dǎo)致輸出全為“博客”
Go語(yǔ)言簡(jiǎn)潔高效,但其細(xì)微之處也容易造成困擾。本文剖析一個(gè)常見(jiàn)的Go語(yǔ)言面試題,解釋為何代碼輸出結(jié)果全部為“博客”。
以下代碼片段演示了這個(gè)問(wèn)題:
type student struct { name string age int } func main() { m := make(map[string]*student) stus := []student{ {name: "pprof.cn", age: 18}, {name: "測(cè)試", age: 23}, {name: "博客", age: 28}, } for _, stu := range stus { m[stu.name] = &stu } for k, v := range m { fmt.Println(k, "=>", v.name) } }
運(yùn)行結(jié)果令人意外:所有輸出均為“博客”。究其原因,在于for…range循環(huán)及指針的用法。
問(wèn)題根源:循環(huán)變量復(fù)用與指針指向
-
循環(huán)變量復(fù)用: Go語(yǔ)言的for…range循環(huán)會(huì)復(fù)用循環(huán)變量stu。這意味著stu始終指向同一內(nèi)存地址。
-
指針引用: 代碼中m[stu.name] = &stu使用指針,每次迭代都將stu的內(nèi)存地址賦值給map。由于stu的地址不變,所有map的值都指向同一地址。
-
最后一次賦值: 循環(huán)結(jié)束后,stu保存了最后一個(gè)學(xué)生的信息(“博客”)。因此,map中所有鍵值都指向這個(gè)最終的stu地址。
-
輸出結(jié)果: 打印v.name時(shí),所有指針都指向同一個(gè)“博客”數(shù)據(jù),故輸出結(jié)果全為“博客”。
解決方案:避免指針直接引用循環(huán)變量
為了避免此問(wèn)題,應(yīng)在循環(huán)內(nèi)創(chuàng)建新的student結(jié)構(gòu)體副本,而不是直接使用stu的指針:
for _, stu := range stus { s := stu // 創(chuàng)建副本 m[stu.name] = &s }
這樣,每個(gè)map的鍵值對(duì)都指向不同的內(nèi)存地址,從而正確輸出每個(gè)學(xué)生的名字。
總結(jié):謹(jǐn)慎使用指針和理解循環(huán)變量
此例警示我們?cè)贕o語(yǔ)言中使用指針時(shí)需格外謹(jǐn)慎,尤其在循環(huán)和切片操作中。理解for…range循環(huán)的變量復(fù)用機(jī)制是避免此類(lèi)問(wèn)題的關(guān)鍵。 避免直接將循環(huán)變量的地址賦值給map或其他數(shù)據(jù)結(jié)構(gòu),而是創(chuàng)建副本后再使用指針,是更安全的做法。