日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Golang开发新手常犯的50个错误

發布時間:2023/12/20 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Golang开发新手常犯的50个错误 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

《50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs》

原文地址:http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html

一、初級

1、不允許左大括號單獨一行

2、不允許出現未使用的變量

3、不允許出現未使用的import

解決方法:使用_作為引入包別名

package mainimport ( _ "fmt" // 指定別名為`_`"log""time" )var _ = log.Println // 變量名為`_`func main() { _ = time.Now }

4、短的變量聲明(Short Variable Declarations)只能在函數內部使用

package main// myvar := 1 // error var myvar = 1 // okfunc main() { }

5、不能使用短變量聲明(Short Variable Declarations)重復聲明

6、不能使用短變量聲明(Short Variable Declarations)這種方式來設置字段值

package mainfunc main() { one := 0// one := 1 //error: Redeclaring Variables Using Short Variable Declarations// data.result, err := work() //error:Can't Use Short Variable Declarations to Set Field Valuesdata.result, err = work() //ok }

7、意外的變量幽靈(Accidental Variable Shadowing)

短變量聲明語法,很好用,但是代碼塊中使用短變更聲明與外部相同的變量時,
沒有語法編譯錯誤,但是代碼塊中同名短變量聲明從聲明開始到代碼塊結束,對變量的修改將不會影響到外部變量!

package mainimport "fmt"func main() { x := 1fmt.Println(x) //prints 1{fmt.Println(x) //prints 1// x = 3x := 2 // 不會影響到外部x變量的值fmt.Println(x) //prints 2//x = 5 // 不會影響到外部x變量值}fmt.Println(x) //prints 3 }

這種現象稱之為幽靈變量,可以使用go tool vet -shadow you_file.go檢查幽靈變量。
使用go-ynet命令會執行更多幽靈變量的檢測。

8、不能使用nil初始化一個未指定類型的變量

// var x = nil //error var x interface{} = nil // OK _ = x

9、不能直接使用nil值的Slice和Map

10、map使用make分配內存時可指定capicity,但是不能對map使用cap函數

11、字符串不允許使用nil值

在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。

var x string = nil //error if x == nil { //errorx = "default" }//var x string //defaults to "" (zero value) if x == "" {x = "default" }

12、數組用于函數傳參時是值復制

注意:方法或函數調用時,傳入參數都是值復制(跟賦值一致),除非是map、slice、channel、指針類型這些特殊類型是引用傳遞。

x := [3]int{1,2,3}// 數組在函數中傳參是值復制 func(arr [3]int) {arr[0] = 7fmt.Println(arr) //prints [7 2 3] }(x) fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])// 使用數組指針實現引用傳參 func(arr *[3]int) {(*arr)[0] = 7fmt.Println(arr) //prints &[7 2 3] }(&x) fmt.Println(x) //prints [7 2 3]

13、range關鍵字返回是鍵值對,而不是值

x := []string{"a","b","c"}// for v := range x { // fmt.Println(v) //prints 0, 1, 2 // }for _, v := range x {fmt.Println(v) //prints a, b, c }

14、Slice和Array是一維的

Go表面上看起來像多維的,實際上只是一維的。但可以使用原始的一維數組、一維的切片來實現多維。

15、從不存在key的map中取值時,返回的總是”0值”

x := map[string] string {"one":"1", "two":"2"} if _,ok := x["two"]; !ok { // 判斷是否存在,x[key]始終有返回值}

16、字符串是不可變

x := "text" // x[0] = 'T' // errorxtytes := []byte(x) xbytes[0] = 'T' // okfmt.Println(string(xbytes)) //prints Text

17、字符串與[]byte之間的轉換是復制(有內存損耗),可以用map[string] []byte建立字符串與[]byte之間映射,也可range來避免內存分配來提高性能

//[]byte: for i,v := range []byte(str) { }

18、string的索引操作返回的是byte(或uint8),如想獲取字符可使用for range,也可使用unicode/utf8包和golang.org/x/exp/utf8string包的At()方法。

19、字符串并不總是UTF8的文本

20、len(str)返回的是字符串的字節數,獲取字符串的rune數是使用unicode/utf8.RuneCountInString()函數,但注意一些字符也可能是由多個rune組成,如e?是兩個rune組成。

21、在Slice、Array、Map的多行書寫最后的逗號不可省略

x := []int{ 1,// 2 //error 3, // ok }y := []int {1, 2, 3,} // ok z := []int {1, 2, 3} // 單行書寫,最后一個元素的逗號可省略

22、內置數據結構的操作并不同步,但可把Go提供了并發的特性使用起來:goroutines和channels。

23、使用for range迭代String,是以rune來迭代的。

一個字符,也可以有多個rune組成。需要處理字符,盡量使用golang.org/x/text/unicode/norm包。

for range總是嘗試將字符串解析成utf8的文本,對于它無法解析的字節,它會返回oxfffd的rune字符。
因此,任何包含非utf8的文本,一定要先將其轉換成字符切片([]byte)。

data := "A\xfe\x02\xff\x04" for _,v := range data {fmt.Printf("%#x ",v) } //prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)fmt.Println() for _,v := range []byte(data) {fmt.Printf("%#x ",v) } //prints: 0x41 0xfe 0x2 0xff 0x4 (good)

24、使用for range迭代map時每次迭代的順序可能不一樣,因為map的迭代是隨機的。

25、switch的case默認匹配規則不同于其它語言的是,匹配case條件后默認退出,除非使用fallthrough繼續匹配;而其它語言是默認繼續匹配,除非使用break退出匹配。

26、只有后置自增、后置自減,不存在前置自增、前置自減

27、位運算的非操作是^(跟異或位運算一樣),有別于其它語言的~。

28、位運算(與、或、異或、取反)優先級高于四則運算(加、減、乘、除、取余),有別于C語言。

29、結構體在序列化時非導出字段(以小寫字母開頭的字段名)不會被encode,因此在decode時這些非導出字段的值為”0值”

30、程序不等所有goroutine結束就會退出。可通過channel實現主協程(main goroutine)等待所有goroutine完成。

31、對于無緩存區的channel,寫入channel的goroutine會阻塞直到被讀取,讀取channel的goroutine會阻塞直到有數據寫入。

32、從一個closed狀態的channel讀取數據是安全的,可通過返回狀態(第二個返回參數)判斷是否關閉;而向一個closed狀態的channel寫數據會導致panic。

33、向一個nil值(未用make分配空間)的channel發送或讀取數據,會導致永遠阻塞。

package mainimport ( "fmt""time" )func main() { var ch chan intfor i := 0; i < 3; i++ {go func(idx int) {ch <- (idx + 1) * 2}(i)}//get first resultfmt.Println("result:",<-ch)//do other worktime.Sleep(2 * time.Second) }

34、方法接收者是類型(T),接收者只是原對象的值復制,在方法中修改接收者不會修改原始對象的值;如果方法接收者是指針類型(*T),是對原對象的引用,方法中對其修改當然是原對象修改。

type data struct { num intkey *stringitems map[string]bool }func (this *data) pmethod() { this.num = 7 }func (this data) vmethod() { this.num = 8*this.key = "v.key"this.items["vmethod"] = true }

35、log包中的log.Fatal和log.Panic不僅僅記錄日志,還會中止程序。它不同于Logging庫。

二、中級

1、關閉HTTP的Response.Body

使用defer語句關閉資源時要注意nil值,在defer語句之前要進行nill值處理

以下以http包的使用為例

package mainimport ( "fmt""net/http""io/ioutil" )func main() { resp, err := http.Get("https://api.ipify.org?format=json")if resp != nil {defer resp.Body.Close() // ok,即使不讀取Body中的數據,即使Body是空的,也要調用close方法}//defer resp.Body.Close() // (1)Error:在nil值判斷之前使用,resp為nil時defer中的語句執行會引發空引用的panicif err != nil {fmt.Println(err)return}//defer resp.Body.Close() // (2)Error:排除了nil隱患,但是出現重定向錯誤時,仍然需要調用closebody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println(string(body)) }

在Go 1.5之前resp.Body.Close()會讀取并丟失body中的數據,保證在啟用keepaliva的http時能夠在下一次請求時重用。
在Go 1.5之后,就需要在關閉前手動處理。

_, err = io.Copy(ioutil.Discard, resp.Body)

如果只是讀取Body的部分,就很有必要在關閉Body之前做這種手動處理。例如處理json api響應時json.NewDecoder(resp.Body).Decode(&data)就需要處理掉剩余的數據。

2、關閉HTTP連接:

(1) 可使用req.Close=true,表示在http請求完成時關閉連接

(2) 添加Connection: close的連接請求頭。http服務端也會發送Connection: close的響應頭,http庫處理響應時會關閉連接。

package mainimport ( "fmt""net/http""io/ioutil" )func main() { req, err := http.NewRequest("GET","http://golang.org",nil)if err != nil {fmt.Println(err)return}req.Close = true//or do this://req.Header.Add("Connection", "close")resp, err := http.DefaultClient.Do(req)if resp != nil {defer resp.Body.Close()}if err != nil {fmt.Println(err)return}body, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println(len(string(body))) }

(3)全局關閉http連接重用。

對于相同http server發送多個請求時,適合保持網絡連接;
但對于短時間內向多個HTTP服務器發送一個或兩個請求時,最好在每次接收到服務端響應后關閉網絡鏈接

package mainimport ( "fmt""net/http""io/ioutil" )func main() { tr := &http.Transport{DisableKeepAlives: true}client := &http.Client{Transport: tr}resp, err := client.Get("http://golang.org")if resp != nil {defer resp.Body.Close()}if err != nil {fmt.Println(err)return}fmt.Println(resp.StatusCode)body, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println(len(string(body))) }

3、Json反序列化數字到interface{}類型的值中,默認解析為float64類型,在使用時要注意。

var data = []byte(`{"status": 200}`)var result map[string]interface{}if err := json.Unmarshal(data, &result); err != nil {fmt.Println("error:", err)return}//var status = result["status"].(int) //error: panic: interface conversion: interface is float64, not int var status = uint64(result["status"].(float64)) //okfmt.Println("status value:",status)

(1) 使用Decoder類型解析JSON

var data = []byte(`{"status": 200}`)var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber()if err := decoder.Decode(&result); err != nil {fmt.Println("error:", err)return }var status,_ = result["status"].(json.Number).Int64() //ok fmt.Println("status value:",status)var status2 uint64 if err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status2); err != nil {fmt.Println("error:", err)return }

(2)使用struct結構體映射

var data = []byte(`{"status": 200}`) var result struct {Status uint64 `json:"status"` }if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil {fmt.Println("error:", err)return }fmt.Printf("result => %+v",result) //prints: result => {Status:200}

(3) 使用struct映射數字為json.RawMessage

records := [][]byte{[]byte(`{"status": 200, "tag":"one"}`),[]byte(`{"status":"ok", "tag":"two"}`),}for idx, record := range records {var result struct {StatusCode uint64StatusName stringStatus json.RawMessage `json:"status"`Tag string `json:"tag"`}if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil {fmt.Println("error:", err)return}var sstatus stringif err := json.Unmarshal(result.Status, &sstatus); err == nil {result.StatusName = sstatus}var nstatus uint64if err := json.Unmarshal(result.Status, &nstatus); err == nil {result.StatusCode = nstatus}fmt.Printf("[%v] result => %+v\n",idx,result)}

4、Struct、Array、Slice、Map的比較

如果struct結構體的所有字段都能夠使用==操作比較,那么結構體變量也能夠使用==比較。
但是,如果struct字段不能使用==比較,那么結構體變量使用==比較會導致編譯錯誤。

同樣,array只有在它的每個元素能夠使用==比較時,array變量才能夠比較。

Go提供了一些用于比較不能直接使用==比較的函數,其中最常用的是reflect.DeepEqual()函數。

DeepEqual()函數對于nil值的slice與空元素的slice是不相等的,這點不同于bytes.Equal()函數。

type data struct { num intfp float32complex complex64str stringchar runeyes boolevents <-chan stringhandler interface{}ref *byteraw [10]byte }v1 := data{} v2 := data{} fmt.Println("v1 == v2:",v1 == v2) //prints: v1 == v2: truetype data2 struct { num int //okchecks [10]func() bool //not comparabledoit func() bool //not comparablem map[string] string //not comparablebytes []byte //not comparable }v3 := data2{} v4 := data2{} fmt.Println("v3 == v4:",v3 == v4) // errorv5 := data2{} v6 := data2{} fmt.Println("v5 == v6:",reflect.DeepEqual(v5,v6)) //prints: v5 == v6: truem1 := map[string]string{"one": "a","two": "b"} m2 := map[string]string{"two": "b", "one": "a"} fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) //prints: m1 == m2: trues1 := []int{1, 2, 3} s2 := []int{1, 2, 3} fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) //prints: s1 == s2: truevar b1 []byte = nil b2 := []byte{} fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: falsevar b3 []byte = nil b4 := []byte{} fmt.Println("b3 == b4:",bytes.Equal(b3, b4)) //prints: b3 == b4: true

DeepEqual()函數并不總是能夠正確處理slice。

var str string = "one" var in interface{} = "one" fmt.Println("str == in:",str == in,reflect.DeepEqual(str, in)) //prints: str == in: true truev1 := []string{"one","two"} v2 := []interface{}{"one","two"} fmt.Println("v1 == v2:",reflect.DeepEqual(v1, v2)) //prints: v1 == v2: false (not ok)data := map[string]interface{}{"code": 200,"value": []string{"one","two"}, } encoded, _ := json.Marshal(data) var decoded map[string]interface{} json.Unmarshal(encoded, &decoded) fmt.Println("data == decoded:",reflect.DeepEqual(data, decoded)) //prints: data == decoded: false (not ok)

如果要忽略大小寫來比較包含文字數據的字節切片(byte slice),
不建議使用bytes包和strings包里的ToUpper()、ToLower()這些函數轉換后再用==、byte.Equal()、bytes.Compare()等比較,

ToUpper()、ToLower()只能處理英文文字,對其它語言無效。因此建議使用strings.EqualFold()和bytes.EqualFold()

如果要比較用于驗證用戶數據密鑰信息的字節切片時,使用reflact.DeepEqual()、bytes.Equal()、
bytes.Compare()會使應用程序遭受計時攻擊(Timing Attack),可使用crypto/subtle.ConstantTimeCompare()避免泄漏時間信息。

5、從panic中恢復

recover()函數能夠捕獲或攔截panic,但必須在defer函數或語句中直接調用,否則無效。

package mainimport "fmt"func doRecover() { fmt.Println("recovered =>",recover()) //prints: recovered => <nil> }func main() { defer func() {fmt.Println("recovered:",recover()) // ok}()defer func() {doRecover() //panic is not recovered}()// recover() //doesn't do anythingpanic("not good")// recover() //won't be executed :)fmt.Println("ok") }

6、在slice、array、map的for range子句中修改和引用數據項

使用range獲取的數據項是從集合元素的復制過來的,并非引用原始數據,但使用索引能訪問原始數據。

data := []int{1,2,3} for _,v := range data {v *= 10 // original item is not changed }data2 := []int{1,2,3} for i,v := range data2 {data2[i] *= 10 // change original item }// 元素是指針類型就不一樣了 data3 := []*struct{num int} {{1}, {2}, {3}} for _,v := range data {v.num *= 10 }fmt.Println("data:", data) //prints data: [1 2 3] fmt.Println("data:", data2) //prints data: [10 20 30] fmt.Println(data3[0],data3[1],data3[2]) //prints &{10} &{20} &{30}

7、Slice中的隱藏數據

從一個slice上再生成一個切片slice,新的slice將直接引用原始slice的那個數組,兩個slice對同一數組的操作,會相互影響。

可通過為新切片slice重新分配空間,從slice中copy部分的數據來避免相互之間的影響。

raw := make([]byte,10000) fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>data := raw[:3] fmt.Println(len(data),cap(data),&data[0]) //prints: 3 10000 <byte_addr_x>res := make([]byte,3) copy(res,raw[:3]) fmt.Println(len(res),cap(res),&res[0]) //prints: 3 3 <byte_addr_y>

8、Slice超范圍數據覆蓋

從已存在的切片slice中繼續切片時,新切片的capicity等于原capicity減去新切片之前部分的數量,新切片與原切片都指向同一數組空間。

新生成切片之間capicity區域是重疊的,因此在添加數據時易造成數據覆蓋問題。

slice使用append添加的內容時超出capicity時,會重新分配空間。
利用這一點,將要修改的切片指定capicity為切片當前length,可避免切片之間的超范圍覆蓋影響。

path := []byte("AAAA/BBBBBBBBB")sepIndex := bytes.IndexByte(path,'/')dir1 := path[:sepIndex]// 解決方法// dir1 := path[:sepIndex:sepIndex] //full slice expressiondir2 := path[sepIndex+1:]fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAfmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBBdir1 = append(dir1,"suffix"...)path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffixfmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)fmt.Println("new path =>",string(path))

9、Slice增加元素重新分配內存導致的怪事

slice在添加元素前,與其它切片共享同一數據區域,修改會相互影響;但添加元素導致內存重新分配之后,不再指向原來的數據區域,修改元素,不再影響其它切片。

s1 := []int{1,2,3}fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]s2 := s1[1:]fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]for i := range s2 { s2[i] += 20 }//still referencing the same arrayfmt.Println(s1) //prints [1 22 23]fmt.Println(s2) //prints [22 23]s2 = append(s2,4)for i := range s2 { s2[i] += 10 }//s1 is now "stale"fmt.Println(s1) //prints [1 22 23]fmt.Println(s2) //prints [32 33 14]

10、類型重定義與方法繼承

從一個已存在的(non-interface)非接口類型重新定義一個新類型時,不會繼承原類型的任何方法。
可以通過定義一個組合匿名變量的類型,來實現對此匿名變量類型的繼承。

但是從一個已存在接口重新定義一個新接口時,新接口會繼承原接口所有方法。

11、從”for switch”和”for select”代碼塊中跳出。

無label的break只會跳出最內層的switch/select代碼塊。
如需要從switch/select代碼塊中跳出外層的for循環,可以在for循環外部定義label,供break跳出。

return當然也是可以的,如果在這里可以用的話。

12、在for語句的閉包中使用迭代變量會有問題

在for迭代過程中,迭代變量會一直保留,只是每次迭代值不一樣。
因此在for循環中在閉包里直接引用迭代變量,在執行時直接取迭代變量的值,而不是閉包所在迭代的變量值。

如果閉包要取所在迭代變量的值,就需要for中定義一個變量來保存所在迭代的值,或者通過閉包函數傳參。

package mainimport ( "fmt""time" )func forState1(){data := []string{"one","two","three"}for _,v := range data {go func() {fmt.Println(v)}()}time.Sleep(3 * time.Second) //goroutines print: three, three, threefor _,v := range data {vcopy := v // 使用臨時變量go func() {fmt.Println(vcopy)}()}time.Sleep(3 * time.Second) //goroutines print: one, two, threefor _,v := range data {go func(in string) {fmt.Println(in)}(v)}time.Sleep(3 * time.Second) //goroutines print: one, two, three }func main() { forState1() }

再看一個坑埋得比較深的例子。

package mainimport ( "fmt""time" )type field struct { name string }func (p *field) print() { fmt.Println(p.name) }func main() { data := []field{{"one"},{"two"},{"three"}}for _,v := range data {// 解決辦法:添加如下語句// v := vgo v.print()}time.Sleep(3 * time.Second) //goroutines print: three, three, threedata2 := []*field{{"one"}, {"two"}, {"three"}} // 注意data2是指針數組for _, v := range data2 {go v.print() // go執行是函數,函數執行之前,函數的接受對象已經傳過來}time.Sleep(3 * time.Second) //goroutines print: one, two, three }

13、defer函數調用參數

defer后面必須是函數或方法的調用語句。defer后面不論是函數還是方法,輸入參數的值是在defer聲明時已計算好,
而不是調用開始計算。

要特別注意的是,defer后面是方法調用語句時,方法的接受者是在defer語句執行時傳遞的,而不是defer聲明時傳入的。

type field struct{num int } func(t *field) print(n int){fmt.println(t.num, n) } func main() { var i int = 1defer fmt.Println("result2 =>",func() int { return i * 2 }())i++v := field{1}defer v.print(func() int { return i * 2 }())v = field{2}i++// prints: // 2 4// result => 2 (not ok if you expected 4) }

14、defer語句調用是在當前函數結束之后調用,而不是變量的作用范圍。

for _,target := range targets {f, err := os.Open(target)if err != nil {fmt.Println("bad target:",target,"error:",err) //prints error: too many open filesbreak}defer f.Close() //will not be closed at the end of this code block, but closed at end of this function// 解決方法1:不用defer// f.Close()// 解決方法2:將for中的語句添加到匿名函數中執行。// func () {// }() }

15、失敗的類型斷言

var.(T)類型斷言失敗時會返回T類型的“0值”,而不是變量原始值。

var data interface{} = "great"if data, ok := data.(int); ok {fmt.Println("[is an int] value =>",data)} else {fmt.Println("[not an int] value =>",data) //prints: [not an int] value => 0 (not "great")}if res, ok := data.(int); ok {fmt.Println("[is an int] value =>",res)} else {fmt.Println("[not an int] value =>",data) //prints: [not an int] value => great (as expected)}

16、阻塞的goroutine與資源泄漏

func First(query string, replicas ...Search) Result { c := make(chan Result)// 解決1:使用緩沖的channel: c := make(chan Result,len(replicas))searchReplica := func(i int) { c <- replicas[i](query) }// 解決2:使用select-default,防止阻塞// searchReplica := func(i int) { // select {// case c <- replicas[i](query):// default:// }// }// 解決3:使用特殊的channel來中斷原有工作// done := make(chan struct{})// defer close(done)// searchReplica := func(i int) { // select {// case c <- replicas[i](query):// case <- done:// }// }for i := range replicas {go searchReplica(i)}return <-c }

三、高級

1、用值實例上調用接收者為指針的方法

對于可尋址(addressable)的值變量(而不是指針),可以直接調用接受對象為指針類型的方法。
換句話說,就不需要為可尋址值變量定義以接受對象為值類型的方法了。

但是,并不是所有變量都是可尋址的,像Map的元素就是不可尋址的。

package mainimport "fmt"type data struct { name string }func (p *data) print() { fmt.Println("name:",p.name) }type printer interface { print() }func main() { d1 := data{"one"}d1.print() //ok// var in printer = data{"two"} //errorvar in printer = &data{"two"}in.print()m := map[string]data {"x":data{"three"}}//m["x"].print() //errord2 = m["x"]d2.print() // ok }

2、更新map值的字段

如果map的值類型是結構體類型,那么不能更新從map中取出的結構體的字段值。
但是對于結構體類型的slice卻是可以的。

package maintype data struct { name string }func main() { m := map[string]data {"x":{"one"}}//m["x"].name = "two" //errorr := m["x"]r.name = "two"m["x"] = rfmt.Println(s) // prints: map[x:{two}]mp := map[string]*data {"x": {"one"}}mp["x"].name = "two" // oks := []data{{"one"}}s[0].name = "two" // okfmt.Println(s) // prints: [{two}] }

3、nil值的interface{}不等于nil

在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。

interface{}表示任意類型,可以接收任意類型的值。interface{}變量在底是由類型和值兩部分組成,表示為(T,V),interface{}變量比較特殊,判斷它是nil時,要求它的類型和值都是nil,即(nil, nil)。
其它類型變量,只要值是nil,那么此變量就是nil(為什么?變量類型不是nil,那當然只能用值來判斷了)

聲明變量interface{},它默認就是nil,底層類型與值表示是(nil, nil)。
當任何類型T的變量值V給interface{}變量賦值時,interface{}變量的底層表示是(T, V)。只要T非nil,即使V是nil,interface{}變量也不是nil。

因此,var a interface{}、var a interface{} = nil、“

var data *bytevar in interface{}fmt.Println(data,data == nil) //prints: <nil> truefmt.Println(in,in == nil) //prints: <nil> truein = datafmt.Println(in,in == nil) //prints: <nil> false//'data' is 'nil', but 'in' is not 'nil'doit := func(arg int) interface{} {var result *struct{} = nilif(arg > 0) {result = &struct{}{}}return result}if res := doit(-1); res != nil {fmt.Println("good result:",res) //prints: good result: <nil>//'res' is not 'nil', but its value is 'nil'}doit = func(arg int) interface{} {var result *struct{} = nilif(arg > 0) {result = &struct{}{}} else {return nil //return an explicit 'nil'}return result}if res := doit(-1); res != nil {fmt.Println("good result:",res)} else {fmt.Println("bad result (res is nil)") //here as expected}

4、變量內存的分配

在C++中使用new操作符總是在heap上分配變量。Go編譯器使用new()和make()分配內存的位置到底是stack還是heap,
取決于變量的大小(size)和逃逸分析的結果(result of “escape analysis”)。這意味著Go語言中,返回本地變量的引用也不會有問題。

要想知道變量內存分配的位置,可以在go build、go run命令指定-gcflags -m即可:
go run -gcflags -m app.go

5、GOMAXPROCS、Concurrency和Parallelism

Go 1.4及以下版本每個操作系統線程只使用一個執行上下文execution context)。這意味著每個時間片,只有一個goroutine執行。
從Go 1.5開始可以設置執行上下文的數量為CUP內核數量runtime.NumCPU(),也可以通過GOMAXPROCS環境變量來設置,
還可調用runtime.GOMAXPROCS()函數來設置。

注意,GOMAXPROCS并不代表Go運行時能夠使用的CPU數量,它是一個小256的數值,可以設置比實際的CPU數量更大的數字。

fmt.Println(runtime.GOMAXPROCS(-1)) //prints: X (1 on play.golang.org)fmt.Println(runtime.NumCPU()) //prints: X (1 on play.golang.org)runtime.GOMAXPROCS(20)fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 20runtime.GOMAXPROCS(300)fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 256

6、讀寫操作排序

Go可能會對一些操作排序,但它保證在goroutine的所有行為保持不變。
但是,它無法保證在跨多個goroutine時的執行順序。

package mainimport ( "runtime""time" )var _ = runtime.GOMAXPROCS(3)var a, b intfunc u1() { a = 1b = 2 }func u2() { a = 3b = 4 }func p() { println(a)println(b) }func main() { go u1()go u2()go p()time.Sleep(1 * time.Second)// 多次執行可顯示以下以幾種打印結果// 1 2// 3 4// 0 2 (奇怪嗎?)// 0 0 // 1 4 (奇怪嗎?) }

看到上面顯示的02,14這樣的值了嗎?這沒什么奇怪的。以02這樣的輸出結果,goroutine的執行應該是這樣的

-------------------------------------p函數 | u1函數 | u2函數 | ------------------------------------- println(a) | | | | a = 1 | | | b = 2 | | println(b) | | | | | a = 3 | | | b = 4 | ------------------------------------- (從上向下為時間執行順序)

7、優先調度

有一些比較流氓的goroutine會阻止其它goroutine的執行。
例如for循環可能就不允許調度器(scheduler)執行。

scheduler會在GC、go語句、阻塞channel的操作、阻塞系統調用、lock操作等語句執行之后立即執行。
也可以顯示地執行runtime.Gosched()(讓出時間片)使scheduler執行調度工作。

package mainimport ( "fmt""runtime" )func main() { done := falsego func(){done = true}()for !done {// ... //runtime.Gosched() // 讓scheduler執行調度,讓出執行時間片}fmt.Println("done!") }

總結

以上是生活随笔為你收集整理的Golang开发新手常犯的50个错误的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。