1. 程式人生 > >通過golang小案例,瞭解golang程式常見機制

通過golang小案例,瞭解golang程式常見機制

[TOC] # 程式碼理解及糾錯 ## 1、defer和panic執行先後順序 ```golang package main import ( "fmt" ) func main() { defer_call() } func defer_call() { defer func() { fmt.Println("列印前") }() defer func() { fmt.Println("列印中") }() defer func() { fmt.Println("列印後") }() panic("觸發異常") } ``` ```sh 列印後 列印中 列印前 panic: 觸發異常 ``` defer 的執行順序是後進先出。當出現 panic 語句的時候,會先按照 defer 的後進先出的順序執行,最後才會執行panic ## 2、for迴圈元素副本問題 ```golang func main() { slice := []int{0,1,2,3} m := make(map[int]*int) for key,val := range slice { m[key] = &val // 正確寫法 // value := val // m[key] = &value } for k,v := range m { fmt.Println(k,"->",*v) } } ``` ```sh 0 -> 3 1 -> 3 2 -> 3 3 -> 3 ``` 這是新手常會犯的錯誤寫法,for range 迴圈的時候會建立每個元素的副本,而不是元素的引用,所以 m[key] = &val 取的都是變數 val 的地址,所以最後 map 中的所有元素的值都是變數 val 的地址,因為最後 val 被賦值為3,所有輸出都是3 ## 3、slice追加元素問題 ```golang // 1. func main() { s := make([]int, 5) s = append(s, 1, 2, 3) fmt.Println(s) } // 2. func main() { s := make([]int,0) s = append(s,1,2,3,4) fmt.Println(s) } ``` 兩段程式碼分別輸出: ```sh [0 0 0 0 0 1 2 3] [1 2 3 4] ``` append 向 slice 新增元素,第一段程式碼常見的錯誤是 [1 2 3],需要注意。 ## 4、返回值命名問題 ```golang // 第二個返回值未命名錯誤 func funcMui(x,y int)(sum int,error){ return x+y,nil } ``` 在函式有多個返回值時,只要有一個返回值有命名,其他的也必須命名。如果有多個返回值必須加上括號();如果只有一個返回值且命名也必須加上括號()。這裡的第一個返回值有命名 sum,第二個沒有命名,所以錯誤。 ## 5、用new初始化內建型別問題 ```golang func main() { list := new([]int) list = append(list, 1) fmt.Println(list) } ``` 不能通過編譯,new([]int) 之後的 list 是一個 *[]int 型別的指標,不能對指標執行 append 操作。可以使用 make() 初始化之後再用。同樣的,map 和 channel 建議使用 make() 或字面量的方式初始化,不要用 new() 。 ## 6、切片append另外一個切片問題 ```golang func main() { s1 := []int{1, 2, 3} s2 := []int{4, 5} s1 = append(s1, s2) fmt.Println(s1) } ``` 不能通過編譯。append() 的第二個引數不能直接使用 slice,需使用 … 操作符,將一個切片追加到另一個切片上:append(s1,s2…)。或者直接跟上元素,形如:append(s1,1,2,3)。 ## 7、全域性變數用:=宣告問題 ```golang var( size := 1024 max_size = size*2 ) func main() { fmt.Println(size,max_size) } ``` :=只能在函式內部使用 ## 8、結構體比較問題 ```golang func main() { sn1 := struct { age int name string }{age: 11, name: "qq"} sn2 := struct { age int name string }{age: 11, name: "qq"} if sn1 == sn2 { fmt.Println("sn1 == sn2") } sm1 := struct { age int m map[string]string }{age: 11, m: map[string]string{"a": "1"}} sm2 := struct { age int m map[string]string }{age: 11, m: map[string]string{"a": "1"}} if sm1 == sm2 { fmt.Println("sm1 == sm2") } } ``` 編譯不通過 invalid operation: sm1 == sm2; - 結構體只能比較是否相等,但是不能比較大小。 - 相同型別的結構體才能夠進行比較,結構體是否相同不但與屬性型別有關,還與屬性順序相關 - 如果 struct 的所有成員都可以比較,則該 struct 就可以通過 == 或 != 進行比較是否相等,比較時逐個項進行比較,如果每一項都相等,則兩個結構體才相等,否則不相等; 那什麼是可比較的呢,常見的有 bool、數值型、字元、指標、陣列等。像切片、map、函式等是不能比較的。 具體可以參考 Go 說明文件。 ## 9、iota的使用 ```golang const ( x = iota _ y z = "zz" k p = iota ) func main() { fmt.Println(x,y,z,k,p) } ``` 編譯通過,輸出:0 2 zz zz 5。知識點:iota 的使用. - 每次 const 出現時,都會讓 iota 初始化為0 - iota出現後,下面的變數無初始值,會按照iota自增長 - 下劃線_可以跳過該行iota的增長,iota還是按行增長的,知道遇到不為const和_的其他變數 ## 10、介面型別斷言使用 ```golang func GetValue() int { return 1 } func main() { i := GetValue() switch i.(type) { case int: println("int") case string: println("string") case interface{}: println("interface") default: println("unknown") } } ``` 編譯失敗。考點:型別選擇,型別選擇的語法形如:i.(type),其中 i 是介面,type 是固定關鍵字,需要注意的是,只有介面型別才可以使用型別選擇。 ## 11、不同型別相加問題 ```golang func main() { a := 5 b := 8.1 fmt.Println(a + b) } ``` a 的型別是 int,b 的型別是 float,兩個不同型別的數值不能相加,編譯報錯。可以使用型別強轉來相加 ## 12、陣列型別比較問題 ```golang func main() { a := [2]int{5, 6} b := [3]int{5, 6} if a == b { fmt.Println("equal") } else { fmt.Println("not equal") } } ``` Go 中的陣列是值型別,可比較,另外一方面,陣列的長度也是陣列型別的組成部分,所以 a 和 b 是不同的型別,是不能比較的,所以編譯錯誤。 ## 13、map刪除不存在的值和獲取不存在的值 ```golang func main() { s := make(map[string]int) delete(s, "h") fmt.Println(s["h"]) } ``` 刪除 map 不存在的鍵值對時,不會報錯,相當於沒有任何作用;獲取不存在的減值對時,返回值型別對應的零值,所以返回 0。 ## 14、格式化輸出問題 ```golang func main() { i := -5 j := +5 fmt.Printf("%+d %+d", i, j) } ``` %d表示輸出十進位制數字,+表示輸出數值的符號。 ## 15、結構體優先呼叫外層方法 ```golang type People struct{} func (p *People) ShowA() { fmt.Println("showA") p.ShowB() } func (p *People) ShowB() { fmt.Println("showB") } type Teacher struct { People } func (t *Teacher) ShowB() { fmt.Println("teacher showB") } func main() { t := Teacher{} t.ShowB() } ``` 外部型別通過巢狀可以繼承內部結構體的方法,外部型別還可以定義自己的屬性和方法,甚至可以定義與內部相同的方法,這樣內部型別的方法就會被“遮蔽”。這個例子中的 ShowB() 就是同名方法。 ## 16、defer引數傳遞副本 ```golang func hello(i int) { fmt.Println(i) } func main() { i := 5 defer hello(i) i = i + 10 } ``` 這個例子中,hello() 函式的引數在執行defer語句的時候會儲存一份副本,在實際呼叫 hello() 函式時用,所以是輸出5 ## 17、字串只讀 ```golang func main() { str := "hello" str[0] = 'x' fmt.Println(str) } ``` Go 語言中的字串是隻讀的。所以編譯錯誤compilation error ## 18、整數強轉字串 ```golang func main() { i := 65 fmt.Println(string(i)) } ``` UTF-8 編碼中,十進位制數字 65 對應的符號是 A。輸出A ## 19、切片長度問題 ```golang func main() { s := [3]int{1, 2, 3} a := s[:0] b := s[:2] c := s[1:2:cap(s)] } ``` a長度是0,容量是3; b長度是2,容量是3; c長度是1,容量是2;cap(s)雖然是3,但是子切片的容量**不能大於底層陣列的長度** 擷取操作有帶 2 個或者 3 個引數,形如:[i:j] 和 [i:j:k],假設擷取物件的底層陣列長度為 l。在操作符 [i:j] 中,如果 i 省略,預設 0,如果 j 省略,預設底層陣列的**長度**,擷取得到的切片長度和容量計算方法是 j-i、l-i。操作符 [i:j:k],k 主要是用來限制切片的容量,但是不能大於陣列的長度 l,擷取得到的切片長度和容量計算方法是 j-i、k-i。 ## 20、閉包引用和匿名函式問題 ```golang type Person struct { age int } func main() { person := &Person{28} // 1. defer fmt.Println(person.age) // 2. defer func(p *Person) { fmt.Println(p.age) }(person) // 3. defer func() { fmt.Println(person.age) }() person.age = 29 } ``` 1.person.age 此時是將 28 當做 defer 函式的引數,會把 28 快取在棧中,等到最後執行該 defer 語句的時候取出,即輸出 28; 2.defer 快取的是結構體 Person{28} 的地址,最終 Person{28} 的 age 被重新賦值為 29,所以 defer 語句最後執行的時候,依靠快取的地址取出的 age 便是 29,即輸出 29; 3.閉包引用,輸出 29; 又由於 defer 的執行順序為先進後出,即 3 2 1,所以輸出 29 29 28。 ## 21、錯吧字串和nil比較的問題 ```golang package main import ( "fmt" ) func main() { var x string = nil // 錯誤1 if x == nil { // 錯誤2 x = "default" } fmt.Println(x) } ``` golang 的字串型別是不能賦值 nil 的,也不能跟 nil 比較。 ## 22、return後的defer無效問題 ```golang var a bool = true func main() { defer func(){ fmt.Println("1") }() if a == true { fmt.Println("2") return } defer func(){ fmt.Println("3") }() } ``` 輸出2 1; defer 關鍵字後面的函式或者方法想要執行必須先註冊,return 之後的 defer 是不能註冊的, 也就不能執行後面的函式或方法 ## 23、切片共享底層陣列,append擴容後生成新的陣列 ```golang func main() { s1 := []int{1, 2, 3} s2 := s1[1:] s2[1] = 4 fmt.Println(s1) s2 = append(s2, 5, 6, 7) fmt.Println(s1) } ``` ```sh [1 2 4] [1 2 4] ``` 1、golang 中切片底層的資料結構是陣列。當使用 s1[1:] 獲得切片 s2,和 s1 共享同一個底層陣列,這會導致 s2[1] = 4 語句影響 s1。 2、append 操作會導致底層陣列擴容,生成新的陣列,因此追加資料後的 s2 不會影響 s1。 ## 24、map無序問題 ```golang func main() { m := map[int]string{0:"zero",1:"one"} for k,v := range m { fmt.Println(k,v) } } ``` ```sh # 由於map無序,所以輸出結果為 0 zero 1 one # 或者 1 one 0 zero ``` ## 25、defer巢狀其他函式問題 ```golang func main() { a := 1 b := 2 defer calc("1", a, calc("10", a, b)) a = 0 defer calc("2", a, calc("20", a, b)) b = 1 } func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } ``` ```sh 10 1 2 3 20 0 2 2 2 0 2 2 1 1 3 4 ``` defer會預先把需要的值存起來,如果該值是一個函式的返回,會先計算。之後再按照defer倒序輸出 ## 26、指標接收者實現介面,值無法呼叫問題 ```golang type People interface { Speak(string) string } type Student struct{} func (stu *Student) Speak(think string) (talk string) { if think == "speak" { talk = "speak" } else { talk = "hi" } return } func main() { var peo People = Student{} think := "speak" fmt.Println(peo.Speak(think)) } ``` 編譯失敗;原因是基礎面試題26點,注意事項第二點。如果是值接收者,實體型別的值和指標都可以實現對應的介面;如果是指標接收者,那麼只有型別的指標能夠實現對應的介面 ## 27、介面賦值為nil問題 ```golang type People interface { Show() } type Student struct{} func (stu *Student) Show() { } func main() { var s *Student if s == nil { fmt.Println("s is nil") } else { fmt.Println("s is not nil") } var p People = s if p == nil { fmt.Println("p is nil") } else { fmt.Println("p is not nil") } } ``` ```sh s is nil p is not nil ``` 這道題看似有點詫異,我們分配給變數 p 的值明明是 nil,然而 p 卻不是 nil。記住一點,當且僅當動態值和動態型別都為 nil 時,介面型別值才為 nil。上面的程式碼,給變數 p 賦值之後,p 的動態值是 nil,但是動態型別卻是 *Student,是一個 nil 指標,所以相等條件不成立 ## 28、go中不同型別不能比較 ```golang func main() { fmt.Println([...]int{1} == [2]int{1}) fmt.Println([]int{1} == []int{1}) } ``` 有兩處錯誤: 1、go 中不同型別是不能比較的,而陣列長度是陣列型別的一部分,所以 […]int{1} 和 [2]int{1} 是兩種不同的型別,不能比較; 2、切片是不能比較的; ## 29、迴圈且追加切片 ```golang func main() { v := []int{1, 2, 3} for i := range v { v = append(v, i) } } fmt.Println(v) ``` ```sh [1 2 3 0 1 2] ``` 不會出現死迴圈,能正常結束。迴圈次數在迴圈開始前就已經確定,迴圈內改變切片的長度,不影響迴圈次數。注意,這點跟java不一樣 ```java public class Test { public static void main(String[] args) {