Golang 易錯點

1.go傳值還是傳引用

  • Go語言並不存在類似其他語言的引用類型(沒有開闢新的內存地址,則是引用,切片沒有擴容,指針沒有new 或者&{}
  • 無論參數是普通類型還是指針類型都會發生參數值拷貝(變量值,指針值都是新的)
  • 函數內對切片進行了append,需要將切片作爲返回值返回

2.結構地指針是傳值還是傳引用

  • 指針的值會改變,但是指向的內存空間還是相同的,若指針沒有開闢新的內存地址,則傳引用,開闢的話,開闢後改變值,不會影響外面

func TestAdd(tt *testing.T) {
	t := &T{
		FieldA: "a",
		FieldB: "b",
	}

    //Before changing: value-&{a b}, address-0xc000164088
	fmt.Printf("Before changing: value-%v, address-%p\n", t, &t)
	
	
	changeTypeField(t)
	//After changeTypeField: value-&{A B}, address-0xc000164088
	fmt.Printf("After changeTypeField: value-%v, address-%p\n", t, &t)
	
	changeTypeStruct(t)
	//After changeTypeStruct: value-&{A B}, address-0xc000164088
	fmt.Printf("After changeTypeStruct: value-%v, address-%p\n", t, &t)

}

//這裏p 是局部變量,值是新的,但指針指向同一片內存,所以外面發生變化
func changeTypeField(p *T) {
	p.FieldA = "A"
	p.FieldB = "B"
	fmt.Printf("In changeTypeField: value-%v, address-%p\n", p, &p)
	// value-&{A B}, address-0xc000164090
}

//p 是局部變量,p的值是新的,但是開闢新的內存地址,所以改變裏面內容,不會影響外面
func changeTypeStruct(p *T) {
	p = &T{
		FieldA: "Alpha",
		FieldB: "Beta",
	}
	fmt.Printf("In changeTypeStruct: value-%v, address-%p \n", p, &p)
}

Before changing: value-&{a b}, address-0xc000164088
In changeTypeField: value-&{A B}, address-0xc000164090
After changeTypeField: value-&{A B}, address-0xc000164088
In changeTypeStruct: value-&{Alpha Beta}, address-0xc000164098 
After changeTypeStruct: value-&{A B}, address-0xc000164088

3.切片傳指針還是傳引用

  • 切片底層由數組指針 + len(長度) +cap(容量) 組成
  • 需要切片、數組分開看,局部切片是len、cap傳值,若沒有擴容,cap還有容量,若元素改變,局部切片的長度會改變,外面的切片長度不會改變,但是數組內容改變
  • 若cap不夠,發生擴容,局部切片數組指針指向新的內存地址、len、cap 發生改變,局部切片是新的,外部切片不受影響

func TestTest(t *testing.T) {
	var s []int
	for i := 1; i <= 3; i++ {
		s = append(s, i)
	}
	//此時len=3 cap=4  [1 2 3] before
	fmt.Println(s, "before")
	
	//RRR  [1 2 3] 3 4 
	fmt.Printf("RRR \x1B[1;32;32m %+v %d %d \n", s, len(s), cap(s))
	reverse(s)
	
	//[1 2 3] after
	fmt.Println(s, "after")
	
	//RRR  [1 2 3] 3 4 
	fmt.Printf("RRR \x1B[1;32;32m %+v %d %d \n", s, len(s), cap(s))
	
}

func reverse(s []int) {
	s = append(s, 999,1000)
	for i, j := 0, len(s)-1; i < j; i++ {
		j = len(s) - (i + 1)
		s[i], s[j] = s[j], s[i]
	}
	
	//RRR  [1000 999 3 2 1] 5 8 
	fmt.Printf("RRR \x1B[1;32;32m %+v %d %d \n", s, len(s), cap(s))
}

4.發生錯誤時使用defer關閉一個文件

如果你在一個for循環內部處理一系列文件,你需要使用defer確保文件在處理完畢後被關閉:

  • defer僅在函數返回時纔會執行,在循環的結尾或其他一些有限範圍的代碼內不會執行。
for _, file := range files {
    if f, err = os.Open(file); err != nil {
        return
    }
    // 這是錯誤的方式,當循環結束時文件沒有關閉
    defer f.Close()
    // 對文件進行操作
    f.Process(data)
}
但是在循環結尾處的defer沒有執行,所以文件一直沒有關閉!垃圾回收機制可能會自動關閉文件,但是這會產生一個錯誤,更好的做法是:

for _, file := range files {
    if f, err = os.Open(file); err != nil {
        return
    }
    // 對文件進行操作
    f.Process(data)
    // 關閉文件
    f.Close()
 }

5.誤用短聲明導致變量覆蓋

  • if 語句內若有跟外部變量有關係的變量,不要使用 :=聲明
  • err 也是如此,if內 避免使用 if _,err:=SomeFunc(){}
var remember bool = false
if something {
    remember := true //錯誤
}
// 外面remember還是爲false,
//因爲 if語句內remember被重新聲明&&賦值了

//所以正確的寫法應該是:
if something {
    remember = true
}

參考資料:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章