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