新聞中心
前言
為了讓大家更好的理解本期知識點(diǎn),先介紹以下幾個知識點(diǎn):線性結(jié)構(gòu)、非線性結(jié)構(gòu)、循環(huán)、迭代、遍歷、遞歸。

創(chuàng)新互聯(lián)公司成都網(wǎng)站建設(shè)按需定制設(shè)計,是成都網(wǎng)站制作公司,為成都LED顯示屏提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計、前端HTML5制作、后臺程序開發(fā)等。成都網(wǎng)站推廣熱線:028-86922220
- 線性結(jié)構(gòu):數(shù)組、隊列
- 非線性結(jié)構(gòu):樹、圖
- 循環(huán)(loop):最基礎(chǔ)的概念,所有重復(fù)的行為都是循環(huán)
- 遞歸(recursion):在函數(shù)內(nèi)調(diào)用自身,將復(fù)雜情況逐步轉(zhuǎn)化成基本情況
- (數(shù)學(xué))迭代(iterate):在多次循環(huán)中逐步接近結(jié)果
- (編程)迭代(iterate):按順序訪問線性結(jié)構(gòu)中的每一項
- 遍歷(traversal):按規(guī)則訪問非線性結(jié)構(gòu)中的每一項
下面會挑選幾個經(jīng)典的案例,一塊來探討下,看看如何避免掉坑,多積累積累采坑經(jīng)驗(yàn)。
1. for+傳值
先來到開胃菜,熱熱身~
type student struct {
name string
age int
}
func main() {
m := make(map[string]student)
stus := []student{
{name: "張三", age: 18},
{name: "李四", age: 23},
{name: "王五", age: 26},
}
for _, stu := range stus {
m[stu.name] = stu
}
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}不出意料,輸出結(jié)果為:
李四 => 李四
王五 => 王五
張三 => 張三
這題比較簡單,就是簡單的傳值操作,大家應(yīng)該都能答上來。下面加大難度,改為傳址操作
2. for+傳址
將案例一改為傳址操作
type student struct {
name string
age int
}
func main() {
m := make(map[string]*student)
stus := []student{
{name: "張三", age: 18},
{name: "李四", age: 23},
{name: "王五", age: 26},
}
for _, stu := range stus {
m[stu.name] = &stu
}
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}好好想想應(yīng)該輸出什么結(jié)果呢?還是跟案例一是一樣的結(jié)果嗎?難道會有坑?
不出意料,還是出了意外,輸出結(jié)果為:
張三 => 王五
李四 => 王五
王五 => 王五
為什么呢?
- 首先,關(guān)鍵點(diǎn)在于Go的for循環(huán),對循環(huán)變量stu?每次是循環(huán)并不是迭代(簡單的說,就是對循環(huán)變量stu只會做一次聲明和內(nèi)存地址的分配,后面循環(huán)就是不斷更新值);
- 所以,取址操作 &stu,其實(shí)都是取的同一個變量的地址,只是值被循環(huán)更新為最后一個元素的值;
- 最終,輸出的v.name,都是最后一個元素的name為王五。
解決方案:
在for循環(huán)中,做同名變量覆蓋stu:=stu(即重新聲明一個局部變量,做值拷貝,避免相互影響)
type student struct {
name string
age int
}
func main() {
m := make(map[string]*student)
stus := []student{
{name: "張三", age: 18},
{name: "李四", age: 23},
{name: "王五", age: 26},
}
for _, stu := range stus {
stu := stu //同名變量覆蓋
m[stu.name] = &stu
}
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}
輸出結(jié)果:
張三 => 張三
李四 => 李四
王五 => 王五
3.for+閉包
在for循環(huán)里,做閉包操作,也是很容易掉坑的??纯聪旅孑敵鍪裁??
var prints []func()
for _, v := range []int{1, 2, 3} {
prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
print()
}
一眼看過去,感覺是輸出1 2 3,但實(shí)際會輸出 3 3 3
為什么呢?
- 首先,在分析了案例二后,我們知道了Go的for循環(huán)對循環(huán)變量v,其實(shí)每次是循環(huán)并不是迭代;
- 然后,閉包=函數(shù)+引用環(huán)境,在同一個引用環(huán)境下,循環(huán)變量v的值會被不斷的覆蓋;
- 所以最終,在打印時,輸出的v,都是最后一個值3。
解決方案:
和案例二解決方案一樣,是在for循環(huán)中,做同名變量覆蓋v:=v
var prints []func()
for _, v := range []int{1, 2, 3} {
v := v //同名變量覆蓋
prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
print()
}
輸出結(jié)果:
1
2
3
4. for+goroutine
在for循環(huán)里,起goroutine協(xié)程,也是很迷惑很容易掉坑的??纯聪旅孑敵鍪裁??
var wg sync.WaitGroup
strs := []string{"1", "2", "3", "4", "5"}
for _, str := range strs {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(str)
}()
}
wg.Wait()
一眼看過去,感覺是會無序輸出1 2 3 4 5,但實(shí)際會輸出 5 5 5 5 5
為什么呢?
- 首先,要記得Go的for循環(huán)對循環(huán)變量str,其實(shí)每次是循環(huán)并不是迭代;
- 然后,main協(xié)程會和新起的協(xié)程做相互博弈,看誰執(zhí)行更快,按這個案例執(zhí)行情況來看,main協(xié)程執(zhí)行速度明顯比新起的協(xié)程會更快,所以str被更新為最后一個元素值5(備注:并非絕對);
- 最終,在新起的協(xié)程中,使用str時值都為5,作為結(jié)果去輸出;
- 拓展:如果在新起協(xié)程前,sleep個5s,輸出結(jié)果又會截然不同,感興趣的同學(xué)可以自行實(shí)驗(yàn)下,然后逐步深入地了解下GMP調(diào)度機(jī)制。
解決方案:
和前面兩個案例解決方案一樣,是在for循環(huán)中,做同名變量覆蓋str:=str
var wg sync.WaitGroup
strs := []string{"1", "2", "3", "4", "5"}
for _, str := range strs {
str := str //同名變量覆蓋
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(str)
}()
}
wg.Wait()
輸出結(jié)果:
5
4
2
1
3
注意是1~5無序輸出
總結(jié)
for循環(huán)中做傳址、閉包、goroutine相關(guān)操作,千萬要注意,一不小心就會很容易掉坑。
使用好同名變量覆蓋v:=v,這個解決大法,能很便捷的解決這一類問題。
本文轉(zhuǎn)載自微信公眾號「 程序員升級打怪之旅」,作者「王中陽Go」,可以通過以下二維碼關(guān)注。
轉(zhuǎn)載本文請聯(lián)系「 程序員升級打怪之旅」公眾號。
名稱欄目:Goforrange一不小心就掉坑里了
網(wǎng)站地址:http://www.dlmjj.cn/article/dpgoepo.html


咨詢
建站咨詢
