golang新手们容易犯的3个错误总结
分类:计算机教程

这是因为golang中变量的作用域范围小到每个词法块(不理解的同学可以简单的当成 {} 包裹的部分)都是一个单独的作用域,大家都知道每个作用域的内部声明会屏蔽外部同名的声明,而每个 if 语句都是一个词法块,也就是说,如果在某个 if 语句中,不小心用 := 而不是 = 对某个 if 语句外的变量进行赋值,那么将产生一个新的局部变量,并仅仅在 if 语句中的这个赋值语句后有效,同名的外部变量会被屏蔽,将不会因为这个赋值语句之后的逻辑产生任何变化!

实际执行一遍,结果是:

所以 p.isDead = true 这个操作实际上更改的是新生成的 p 数据,而非 people 中原本的 person ,这里产生了一个bug。

package mainimport "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 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 } }}

我估计有人会认为是:

package maintype person struct { name string age byte isDead bool}func main() { p := person{name: "zzy", age: 100} isDead}func isDead(p interface{}) { if p..age < 101 { p..isDead = true }}

值类型数据和引用类型数据的区别我相信在座的各位都能分得清,否则不用往下看了,因为看不懂。

package mainimport "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 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 } }}

同样的,直接对nil slice添加数据也是不允许的,因为slice的底层也是数组,没有经过make函数初始化时,只是声明了slice类型,而底层数组是不存在的:

everything is ok,很棒棒的代码。还有另外的方法,使用索引访问people中的person,改动一下whoIsDead函数,也能达到同样的目的:

panic: assignment to entry in nil map

11

前言

对nil map、nil slice的错误使用并不是很可怕,毕竟编译的时候就能发觉,下面要说的一个错误则非常坑爹,一不小心中招的话,很难排查。

我因为这个被坑过好几回了,每次都查了好久,以为是自己逻辑有漏洞,最后发现是把 = 写成了 := ,唉,说起来都是泪。

package mainimport "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 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 } }}
m := map[string]string{}

可能有人发现对nil slice使用append函数而不经过make也是有效的:

如果对这点认识不清,导致的后果可能是代码有瑕疵,更严重的是产生bug。

package mainimport "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}

在 for range 内部只需读取数据而不需要修改的情况下,随便怎么写也无所谓,顶多就是代码不够完美,而需要修改数据时,则最好传递 struct 指针:

我估计有人会认为是:

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
  }
 }
}

所以p.isDead = true这个操作实际上更改的是新生成的p数据,而非people中原本的person,这里产生了一个bug。在for range内部只需读取数据而不需要修改的情况下,随便怎么写也无所谓,顶多就是代码不够完美,而需要修改数据时,则最好传递struct指针:

p := person

who is dead? px

实际执行一遍,结果是:

func whoIsDead(people []person) { for i := 0; i < len; i   { if people[i].age < 50 { people[i].isDead = true } }}
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
}

先看下这段代码,猜猜会打印出什么:

go run 一下,报错:

所以想要顺利的使用map,一定要使用内建函数make函数进行创建:

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
}
m := map[string]string{}
package main

func main() {
 var s []int
 s[0] = 1
}
package mainimport "fmt"func main() { var m map[string]string if m == nil { fmt.Println("this map is a nil map") }}
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
 }
}
cannot assign to struct field people[name].isDead in map

请考虑一下这段代码是否有错,然后运行一遍:

package mainimport ( "errors" "fmt")func main() { i := 2 if i > 1 { i, err := doDivision if err != nil { panic } fmt.Println } fmt.Println}func doDivision (int, error) { if y == 0 { return 0, errors.New("input is invalid") } return x / y, nil}

最后,不能不说golang中指针真是居家旅行、升职加薪的必备知识啊,希望同学们熟练掌握。

上面的代码将产生一个panicruntime error:index out of range,正确做法应该是使用make函数或者字面量:

who is dead? px

运行一下:

可能有人发现对nil slice使用append函数而不经过make也是有效的:

package mainimport "fmt"type person struct { name string age byte isDead bool}func main() { p := &person{name: "zzy", age: 100} isDead fmt.Println}func isDead(p interface{}) { if p..age < 101 { p..isDead = true }}
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
  }
 }
}

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

总结

12
package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}
package mainimport "fmt"func main() { var s []int s = append fmt.Println // s => [1]}

会直接报一个编译错误:

美洲杯赔率,这是因为代码中只是声明了map的类型,却没有为map创建底层数组,此时的map实际上在内存中还不存在,即nil map,可以运行下面的代码进行验证:

好, for range 部分讲到这里,接下来说一说 map 结构中值的传递和修改问题。

即便编译通过,代码也是错误的 ,始终要记住struct是值类型的数据,请使用指针去操作它, 正确做法是:

所以想要顺利的使用map,一定要使用内建函数make函数进行创建:

为什么会这样呢!?这是因为golang中变量的作用域范围小到每个词法块(不理解的同学可以简单的当成{} 包裹的部分)都是一个单独的作用域,大家都知道每个作用域的内部声明会屏蔽外部同名的声明,而每个if语句都是一个词法块,也就是说,如果在某个if语句中,不小心用:=而不是=对某个if语句外的变量进行赋值,那么将产生一个新的局部变量,并仅仅在if语句中的这个赋值语句后有效,同名的外部变量会被屏蔽,将不会因为这个赋值语句之后的逻辑产生任何变化!在语言层面这也许并不是个错误,但是实际工作中如果误用,那么产生的bug会很隐秘。比如例子中的代码,因为err是之前未声明的,所以使用了:=赋值(图省事,少写了var err error),然后既不会在编译时报错,也不会在运行时报错,它会让你百思不得其解,觉得自己的逻辑明明走对了,为什么最后的结果却总是不对,直到你一点一点调试,才发现自己不小心多写了一个。我因为这个被坑过好几回了,每次都查了好久,以为是自己逻辑有漏洞,最后发现是把=写成了:=,唉,说起来都是泪。

我相信很多人一看就看出问题在哪了,但肯定还有人不清楚 for range 语法的机制,我絮叨一下:golang中 for range 语法非常方便,可以轻松的遍历 array 、 slice 、 map 等结构,但是它有一个特点,就是会在遍历时把当前遍历到的元素,复制给内部变量,具体就是在 whoIsDead 函数中的 for range 里,会把 people 里的每个 person ,都复制给 p 这个变量,类似于这样的操作:

会直接报一个编译错误:

不出意外的话,这段代码将导致一个panic:

同样的,直接对nil slice添加数据也是不允许的,因为slice的底层也是数组,没有经过make函数初始化时,只是声明了slice类型,而底层数组是不存在的:

那是因为slice本身其实类似一个struct,它有一个len属性,是当前长度,还有个cap属性,是底层数组的长度,append函数会判断传入的slice的len和cap,如果len即将大于cap,会调用make函数生成一个更大的新数组并将原底层数组的数据复制过来(以上均为本人猜测,未经查证,有兴趣的同学可以去挑战一下源码),过程类似:

最后,不能不说golang中指针真是居家旅行、升职加薪的必备知识啊,希望同学们熟练掌握。

1
2

go run一下,报错:

即便 map 中元素没有以上限制,这段代码依然是错误的,想一想,为什么?答案之前已经说过了。

cannot assign to p..isDead
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)
}

上文说过,struct是值类型,所以在赋值给p的过程中,实际上需要重新生成一份person数据,便于for range内部使用,不信试试:

本文由美洲杯赔率发布于计算机教程,转载请注明出处:golang新手们容易犯的3个错误总结

上一篇:PyQt5美洲杯赔率 pyqt多线程操作入门 下一篇:没有了
猜你喜欢
热门排行
精彩图文