一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - golang新手們容易犯的3個錯誤總結

golang新手們容易犯的3個錯誤總結

2020-05-17 12:11西二旗搬磚仔 Golang

這篇文章主要給大家介紹了關于golang新手們容易犯的3個錯誤,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

golang小白到成為golang工程師快兩個月了,我要分享一下新手在開發中常犯的錯誤,都是我親自踩過的坑。這些錯誤中有些會導致無法通過編譯,這種錯容易發現,而有些錯誤在編譯時不會拋出,甚至在運行時也不會panic,如果缺少相關的知識,撓破頭皮都搞不清楚bug出在哪。

1.對nil map、nil slice 添加數據

請考慮一下這段代碼是否有錯,然后運行一遍:

?
1
2
3
4
5
6
package main
 
func main() {
 var m map[string]string
 m["name"] = "zzy"
}

不出意外的話,這段代碼將導致一個panic:

panic: assignment to entry in nil map

這是因為代碼中只是聲明了map的類型,卻沒有為map創建底層數組,此時的map實際上在內存中還不存在,即nil map,可以運行下面的代碼進行驗證:

?
1
2
3
4
5
6
7
8
9
10
package main
 
import "fmt"
 
func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}

所以想要順利的使用map,一定要使用內建函數make函數進行創建:

?
1
m := make(map[string]string)

使用字面量的方式也是可以的,效果同make:

?
1
m := map[string]string{}

同樣的,直接對nil slice添加數據也是不允許的,因為slice的底層也是數組,沒有經過make函數初始化時,只是聲明了slice類型,而底層數組是不存在的:

?
1
2
3
4
5
6
package main
 
func main() {
 var s []int
 s[0] = 1
}

上面的代碼將產生一個panic runtime error:index out of range ,正確做法應該是使用make函數或者字面量:

?
1
2
3
4
5
6
7
package main
 
func main() {
 //第二個參數是slice的len,make slice時必須提供,還可以傳入第三個參數作為cap
 s := make([]int, 1)
 s[0] = 1
}

可能有人發現對nil slice使用append函數而不經過make也是有效的:

?
1
2
3
4
5
6
7
8
9
package main
 
import "fmt"
 
func main() {
 var s []int
 s = append(s, 1)
 fmt.Println(s) // s => [1]
}

那是因為slice本身其實類似一個struct,它有一個len屬性,是當前長度,還有個cap屬性,是底層數組的長度,append函數會判斷傳入的slice的len和cap,如果len即將大于cap,會調用make函數生成一個更大的新數組并將原底層數組的數據復制過來(以上均為本人猜測,未經查證,有興趣的同學可以去挑戰一下源碼),過程類似:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
 
import "fmt"
 
func main() {
 var s []int //len(s)和cap(s)都是0
 s = append(s, 1)
 fmt.Println(s) // s => [1]
}
 
func append(s []int, arg int) []int {
 newLen := len(s) + 1
 var newS []int
 if newLen > cap(s) {
  //創建新的slice,其底層數組擴容為原先的兩倍多
  newS = make([]int, newLen, newLen*2)
  copy(newS, s)
 } else {
  newS = s[:newLen] //直接在原數組上切一下就行
 }
 newS[len(s)] = arg
 return newS
}

對nil map、nil slice的錯誤使用并不是很可怕,畢竟編譯的時候就能發覺,下面要說的一個錯誤則非常坑爹,一不小心中招的話,很難排查。

2.誤用:=賦值導致變量覆蓋

先看下這段代碼,猜猜會打印出什么:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
 
import (
 "errors"
 "fmt"
)
 
func main() {
 i := 2
 if i > 1 {
  i, err := doDivision(i, 2)
  if err != nil {
   panic(err)
  }
  fmt.Println(i)
 }
 fmt.Println(i)
}
 
func doDivision(x, y int) (int, error) {
 if y == 0 {
  return 0, errors.New("input is invalid")
 }
 return x / y, nil
}

我估計有人會認為是:

1
1

實際執行一遍,結果是:

1
2

為什么會這樣呢!?

這是因為golang中變量的作用域范圍小到每個詞法塊(不理解的同學可以簡單的當成 {} 包裹的部分)都是一個單獨的作用域,大家都知道每個作用域的內部聲明會屏蔽外部同名的聲明,而每個 if 語句都是一個詞法塊,也就是說,如果在某個 if 語句中,不小心用 := 而不是 = 對某個 if 語句外的變量進行賦值,那么將產生一個新的局部變量,并僅僅在 if 語句中的這個賦值語句后有效,同名的外部變量會被屏蔽,將不會因為這個賦值語句之后的邏輯產生任何變化!

在語言層面這也許并不是個錯誤,但是實際工作中如果誤用,那么產生的bug會很隱秘。比如例子中的代碼,因為 err 是之前未聲明的,所以使用了 := 賦值(圖省事,少寫了 var err error ),然后既不會在編譯時報錯,也不會在運行時報錯,它會讓你百思不得其解,覺得自己的邏輯明明走對了,為什么最后的結果卻總是不對,直到你一點一點調試,才發現自己不小心多寫了一個 : 。

我因為這個被坑過好幾回了,每次都查了好久,以為是自己邏輯有漏洞,最后發現是把 = 寫成了 := ,唉,說起來都是淚。

3.將值傳遞當成引用傳遞

值類型數據和引用類型數據的區別我相信在座的各位都能分得清,否則不用往下看了,因為看不懂。

在golang中, array 和 struct 都是值類型的,而 slice 、 map 、 chan 是引用類型,所以我們寫代碼的時候,基本不使用 array ,而是用 slice 代替它,對于 struct 則盡量使用指針,這樣避免傳遞變量時復制數據的時間和空間消耗,也避免了無法修改原數據的情況。

如果對這點認識不清,導致的后果可能是代碼有瑕疵,更嚴重的是產生bug。

考慮這段代碼并運行一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main
 
import "fmt"
 
type person struct {
 name string
 age byte
 isDead bool
}
 
func main() {
 p1 := person{name: "zzy", age: 100}
 p2 := person{name: "dj", age: 99}
 p3 := person{name: "px", age: 20}
 people := []person{p1, p2, p3}
 whoIsDead(people)
 for _, p := range people {
  if p.isDead {
   fmt.Println("who is dead?", p.name)
  }
 }
}
 
func whoIsDead(people []person) {
 for _, p := range people {
  if p.age < 50 {
   p.isDead = true
  }
 }
}

我相信很多人一看就看出問題在哪了,但肯定還有人不清楚 for range 語法的機制,我絮叨一下:golang中 for range 語法非常方便,可以輕松的遍歷 array 、 slice 、 map 等結構,但是它有一個特點,就是會在遍歷時把當前遍歷到的元素,復制給內部變量,具體就是在 whoIsDead 函數中的 for range 里,會把 people 里的每個 person ,都復制給 p 這個變量,類似于這樣的操作:

p := person

上文說過, struct 是值類型,所以在賦值給 p 的過程中,實際上需要重新生成一份 person 數據,便于 for range 內部使用,不信試試:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
 
import "fmt"
 
type person struct {
 name string
 age byte
 isDead bool
}
 
func main() {
 p1 := person{name: "zzy", age: 100}
 p2 := p1
 p1.name = "changed"
 fmt.Println(p2.name)
}

所以 p.isDead = true 這個操作實際上更改的是新生成的 p 數據,而非 people 中原本的 person ,這里產生了一個bug。

在 for range 內部只需讀取數據而不需要修改的情況下,隨便怎么寫也無所謂,頂多就是代碼不夠完美,而需要修改數據時,則最好傳遞 struct 指針:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main
 
import "fmt"
 
type person struct {
 name string
 age byte
 isDead bool
}
 
func main() {
 p1 := &person{name: "zzy", age: 100}
 p2 := &person{name: "dj", age: 99}
 p3 := &person{name: "px", age: 20}
 people := []*person{p1, p2, p3}
 whoIsDead(people)
 for _, p := range people {
  if p.isDead {
   fmt.Println("who is dead?", p.name)
  }
 }
}
 
func whoIsDead(people []*person) {
 for _, p := range people {
  if p.age < 50 {
   p.isDead = true
  }
 }
}

運行一下:

who is dead? px

everything is ok,很棒棒的代碼。

還有另外的方法,使用索引訪問 people 中的 person ,改動一下 whoIsDead 函數,也能達到同樣的目的:

?
1
2
3
4
5
6
7
func whoIsDead(people []person) {
 for i := 0; i < len(people); i++ {
  if people[i].age < 50 {
   people[i].isDead = true
  }
 }
}

好, for range 部分講到這里,接下來說一說 map 結構中值的傳遞和修改問題。

這段代碼將之前的 people []person 改成了 map 結構,大家覺得有錯誤嗎,如果有錯,錯在哪:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
 
import "fmt"
 
type person struct {
 name string
 age byte
 isDead bool
}
 
func main() {
 p1 := person{name: "zzy", age: 100}
 p2 := person{name: "dj", age: 99}
 p3 := person{name: "px", age: 20}
 people := map[string]person{
  p1.name: p1,
  p2.name: p2,
  p3.name: p3,
 }
 whoIsDead(people)
 if p3.isDead {
  fmt.Println("who is dead?", p3.name)
 }
}
 
func whoIsDead(people map[string]person) {
 for name, _ := range people {
  if people[name].age < 50 {
   people[name].isDead = true
  }
 }
}

go run 一下,報錯:

cannot assign to struct field people[name].isDead in map

這個報錯有點迷,我估計很多人都看不懂了。我解答下, map 底層使用了 array 存儲數據,并且沒有容量限制,隨著 map 元素的增多,需要創建更大的 array 來存儲數據,那么之前的地址就無效了,因為數據被復制到了新的更大的 array 中,所以 map 中元素是不可取址的,也是不可修改的。這個報錯的意思其實就是不允許修改 map 中的元素。

即便 map 中元素沒有以上限制,這段代碼依然是錯誤的,想一想,為什么?答案之前已經說過了。

那么,怎么改才能正確呢,老套路,依然是使用指針:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
 
import "fmt"
 
type person struct {
 name string
 age byte
 isDead bool
}
 
func main() {
 p1 := &person{name: "zzy", age: 100}
 p2 := &person{name: "dj", age: 99}
 p3 := &person{name: "px", age: 20}
 people := map[string]*person{
  p1.name: p1,
  p2.name: p2,
  p3.name: p3,
 }
 whoIsDead(people)
 if p3.isDead {
  fmt.Println("who is dead?", p3.name)
 }
}
 
func whoIsDead(people map[string]*person) {
 for name, _ := range people {
  if people[name].age < 50 {
   people[name].isDead = true
  }
 }
}

另外,在 interface{} 斷言里試圖直接修改 struct 屬性而非通過指針修改時:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
 
type person struct {
 name string
 age byte
 isDead bool
}
 
func main() {
 p := person{name: "zzy", age: 100}
 isDead(p)
}
 
func isDead(p interface{}) {
 if p.(person).age < 101 {
  p.(person).isDead = true
 }
}

會直接報一個編譯錯誤:

cannot assign to p.(person).isDead

即便編譯通過,代碼也是錯誤的 ,始終要記住 struct 是值類型的數據,請使用指針去操作它, 正確做法是:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
 
import "fmt"
 
type person struct {
 name string
 age byte
 isDead bool
}
 
func main() {
 p := &person{name: "zzy", age: 100}
 isDead(p)
 fmt.Println(p)
}
 
func isDead(p interface{}) {
 if p.(*person).age < 101 {
  p.(*person).isDead = true
 }
}

最后,不能不說golang中指針真是居家旅行、升職加薪的必備知識啊,希望同學們熟練掌握。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://studygolang.com/articles/14144

延伸 · 閱讀

精彩推薦
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
主站蜘蛛池模板: 护士的小嫩嫩好紧好舒服 | 国产精品免费看香蕉 | 亚洲欧美在线观看首页 | 久久免费国产 | 性欧美4khdxxxx | 91精品国产高清久久久久久 | 国产午夜亚洲精品 | 国产亚洲精品自在线亚洲情侣 | 精品在线观看一区 | 红楼梦黄色小说 | 日韩欧美一区二区三区 | 欧美肥乳| 99久久免费看国产精品 | 午夜影院免费观看视频 | 高清一区高清二区视频 | 九九精品免费视频 | 日韩欧美亚洲一区二区综合 | 激情三级hd中文字幕 | 欧美视频精品一区二区三区 | 波多野结衣亚洲一区 | 国产不卡视频一区二区在线观看 | 国产亚洲一欧美一区二区三区 | acg火影忍者熟密姬纲手h | 日本天堂视频 | 精品在线免费播放 | 亚洲国产视频一区 | 精品一区二区三区色花堂 | 扒开大腿狠狠挺进视频 | 日本深夜视频 | 国产小视频免费看 | 办公室出轨秘书高h | 亚洲国产AV一区二区三区四区 | 日本xxxxx高清免费观看 | 亚洲大片免费看 | 国产精品视频一区二区三区经 | 午夜久久久久久亚洲国产精品 | 亚洲国产成人精品不卡青青草原 | 欧产日产国产精品专区 | 91精品啪在线观看国产91九色 | 日本伦理动漫在线观看 | 男人的j放进女人的p全黄 |