這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在寫Go代碼時,多少會出一些錯誤,我把這些常見錯誤整理出來。一是再次讓自己重新認識Golang,進行不足的學習。二是分享出來讓更多人認識Golang。
短聲明使用
短聲明只能出現在函數內部。
func(){ a := 10}()
短聲明的重新聲明。
官方解釋:Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original.
翻譯:與常規變數聲明不同,短變數聲明可以重新聲明變數,前提是它們最初在相同塊中聲明的(如函數體)具有相同類型,並且至少有一個非空變數。因此,重新聲明只能出現在多變數短聲明中。重新聲明不引入新的變數; 它只是為原始值分配一個新值。
func main() { c:=10 c:=23 //----上面短聲明使用錯誤,重聲明只能出現在多變數短聲明中--- a := 10 fmt.Println(&a, a) a, b := 15, 39 fmt.Println(&a, a, b)}
列印結果。
0xc082002278 10 0xc082002278 15 39
短聲明範圍。
坑
func() (err error) { a, err := 1, errors.New(" a error") // a是新建立變數,err是被賦值 if err != nil { return // 正確返回err } // ------------------------------------------------ if b, err := 2, errors.New("b error"); err != nil { // 此刻if語句中err被重新建立 return // if語句中的err覆蓋外面的err,導致編譯 // 錯誤 (err is shadowed during return。) } else { fmt.Println(b) } return }()
上面的代碼,由於 := 導致的範圍的原因出現錯誤,需要注意一下如果 := 左邊的某個變數在外部的範圍已經定義,在裡面的賦值會導致屏蔽掉外部的變數,建立一個新的變數在當前的範圍使用。如果需要更改外部的變數進行賦值=。
字串效率
Go語言中字串是不可變的(類似java和c#)。使用諸如a += b形式連接字串效率低下,尤其在一個迴圈內部使用這種形式。這會導致大量的記憶體開銷和拷貝。應該使用一個字元數組代替字串,或將字串內容寫入一個緩衝中。如下:
str := func() string { var b bytes.Buffer for i := 0; i < 10; i++ { b.WriteString("wuxiao" + i) } return b.String() }()
defer使用
defer延時調用。
感覺很高大上
當一個函數調用有關鍵字 defer 的函數(變數)時, 那麼這個 defer 函數(變數)會延遲到當前函數即將返回前執行。例如:
func(){ a := 1 defer fmt.Println("deder print:", a) a++ fmt.Println("a add print:", a)}()
需要注意, 使用 defer 關鍵字的函數中如果有參數,此參數定義時就確定了。
列印結果。
a add print: 2 deder print: 1
因此 defer fmt.Println("deder print:", a)調用時, a 的值已經確定了, 所有a等於1。
有一個有意思的例子更能加深對defer的理解。
func add(x int) int { return x + x}func result(x int) (r int) { defer func() { r += x }() return add(x)}func main() { fmt.Println(result(3))}
強調一點: defer 關鍵字函數調用的執行順序是外層函數設定傳回值之後, 將要返回之前。
所有列印結果是9。
真年輕
之前寫代碼犯了一個錯誤如下:
for _, file := range files { if f, err = os.Open(file); err != nil { return } defer f.Close() // 假設處理檔案 f.doSomething(data) }
代碼沒什麼問題啊,沒犯錯啊!!!
no,會導致迴圈結尾的defer還沒有執行,檔案沒有及時關閉!
for _, file := range files { if f, err = os.Open(file); err != nil { return } // 假設處理檔案 f.doSomething(data) //正確做法 f.Close() }
介面類型使用
type tester interface { test()}func testFun(n *tester) { n.test() // 編譯錯誤:n.test未定義}
哎呀為什嗎???
查了很多資料,介面類型本是一個指標,所有不能使用一個指標指向一個介面類型。
不懂
為了能更好理解interface,我們用一個demo瞭解interface記憶體做了什麼。
type tester interface{ test()}func testing() tester { var t tester return t}func main(){ tester := testing() if tester == nil //執行結果true}
interface記憶體狀態
簡單查看interface源碼與之對應,初始化interface記憶體會建立tab與data。
type iface struct { tab *itab data unsafe.Pointer}
tab代表介面表,是一種儲存有關介面和底層類型的結構:
type itab struct { inter *interfacetype _type *_type link *itab bad int32 unused int32 fun [1]uintptr // variable sized}
從上面記憶體和源碼可以看出interface確實是指標類型
還有一個坑需要與上面的記憶體配置圖interface記憶體狀態對應學習,代碼與記憶體狀態圖如下:
type tester interface{ test()}func testing() tester { var t *testIml //實現tester介面 return t}func main(){ tester := testing() if tester == nil //執行結果false}
介面實現後的記憶體狀態圖
總結:
- 從這倆個介面的內部結構我們可出,如果對實現介面的變數進行非空判斷會出現自己不想要的結果,以後避免犯錯。