5funcmain(){

6varm map[ string] string

7ifm == nil{

8fmt.Println( "this map is a nil map")

9}

10}

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

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

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

1m := map[ string] string{}

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

1packagemain

2

3funcmain(){

4vars [] int

5s[ 0] = 1

6}

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

1packagemain

2

3funcmain(){

4//第二个参数是slice的len,make slice时必须提供,还可以传入第三个参数作为cap

5s := make([] int, 1)

6s[ 0] = 1

7}

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

1packagemain

2

3import"fmt"

4

5funcmain(){

6vars [] int

7s = append(s, 1)

8fmt.Println(s) // s => [1]

9}

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

1packagemain

2

3import"fmt"

4

5funcmain(){

6vars [] int//len(s)和cap(s)都是0

7s = append(s, 1)

8fmt.Println(s) // s => [1]

9}

10

11funcappend(s []int, arg int)[]int{

12newLen := len(s) + 1

13varnewS [] int

14ifnewLen > cap(s) {

15//创建新的slice,其底层数组扩容为原先的两倍多

16newS = make([] int, newLen, newLen* 2)

17copy(newS, s)

18} else{

19newS = s[:newLen] //直接在原数组上切一下就行

20}

21newS[ len(s)] = arg

22returnnewS

23}

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

2.误用:=赋值导致变量覆盖

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

1packagemain

2

3import(

4"errors"

5"fmt"

6)

7

8funcmain(){

9i := 2

10ifi > 1{

11i, err := doDivision(i, 2)

12iferr != nil{

13panic(err)

14}

15fmt.Println(i)

16}

17fmt.Println(i)

18}

19

20funcdoDivision(x, y int)(int, error){

21ify == 0{

22return0, errors.New( "input is invalid")

23}

24returnx / y, nil

25}

我估计有人会认为是:

11

实际执行一遍,结果是:

12

为什么会这样呢!?

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

在语言层面这也许并不是个错误,但是实际工作中如果误用,那么产生的bug会很隐秘。比如例子中的代码,因为err是之前未声明的,所以使用了:=赋值(图省事,少写了var err error),然后既不会在编译时报错,也不会在运行时报错,它会让你百思不得其解,觉得自己的逻辑明明走对了,为什么最后的结果却总是不对,直到你一点一点调试,才发现自己不小心多写了一个:。

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

3.将值传递当成引用传递

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

在golang中,array和struct都是值类型的,而slice、map、chan是引用类型,所以我们写代码的时候,基本不使用array,而是用slice代替它,对于struct则尽量使用指针,这样避免传递变量时复制数据的时间和空间消耗,也避免了无法修改原数据的情况。

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

考虑这段代码并运行一下:

1packagemain

2

3import"fmt"

4

5typeperson struct{

6name string

7age byte

8isDead bool

9}

10

11funcmain(){

12p1 := person{name: "zzy", age: 100}

13p2 := person{name: "dj", age: 99}

14p3 := person{name: "px", age: 20}

15people := []person{p1, p2, p3}

16whoIsDead(people)

17for_, p := rangepeople {

18ifp.isDead {

19fmt.Println( "who is dead?", p.name)

20}

21}

22}

23

24funcwhoIsDead(people []person){

25for_, p := rangepeople {

26ifp.age < 50{

27p.isDead = true

28}

29}

30}

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

1p := person

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

1packagemain

2

3import"fmt"

4

5typeperson struct{

6name string

7age byte

8isDead bool

9}

10

11funcmain(){

12p1 := person{name: "zzy", age: 100}

13p2 := p1

14p1.name = "changed"

15fmt.Println(p2.name)

16}

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

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

1packagemain

2

3import"fmt"

4

5typeperson struct{

6name string

7age byte

8isDead bool

9}

10

11funcmain(){

12p1 := &person{name: "zzy", age: 100}

13p2 := &person{name: "dj", age: 99}

14p3 := &person{name: "px", age: 20}

15people := []*person{p1, p2, p3}

16whoIsDead(people)

17for_, p := rangepeople {

18ifp.isDead {

19fmt.Println( "who is dead?", p.name)

20}

21}

22}

23

24funcwhoIsDead(people []*person){

25for_, p := rangepeople {

26ifp.age < 50{

27p.isDead = true

28}

29}

30}

运行一下:

who is dead? px

everything is ok,很棒棒的代码。

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

1funcwhoIsDead(people []person){

2fori := 0; i < len(people); i++ {

3ifpeople[i].age < 50{

4people[i].isDead = true

5}

6}

7}

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

这段代码将之前的people []person改成了map结构,大家觉得有错误吗,如果有错,错在哪:

1packagemain

2

3import"fmt"

4

5typeperson struct{

6name string

7age byte

8isDead bool

9}

10

11funcmain(){

12p1 := person{name: "zzy", age: 100}

13p2 := person{name: "dj", age: 99}

14p3 := person{name: "px", age: 20}

15people := map[ string]person{

16p1.name: p1,

17p2.name: p2,

18p3.name: p3,

19}

20whoIsDead(people)

21ifp3.isDead {

22fmt.Println( "who is dead?", p3.name)

23}

24}

25

26funcwhoIsDead(people map[string]person){

27forname, _ := rangepeople {

28ifpeople[name].age < 50{

29people[name].isDead = true

30}

31}

32}

go run一下,报错:

1cannot assignto structfield people[name].isDead inmap

这个报错有点迷,我估计很多人都看不懂了。我解答下,map底层使用了array存储数据,并且没有容量限制,随着map元素的增多,需要创建更大的array来存储数据,那么之前的地址就无效了,因为数据被复制到了新的更大的array中,所以map中元素是不可取址的,也是不可修改的。这个报错的意思其实就是不允许修改map中的元素。

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

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

1packagemain

2

3import"fmt"

4

5typeperson struct{

6name string

7age byte

8isDead bool

9}

10

11funcmain(){

12p1 := &person{name: "zzy", age: 100}

13p2 := &person{name: "dj", age: 99}

14p3 := &person{name: "px", age: 20}

15people := map[ string]*person{

16p1.name: p1,

17p2.name: p2,

18p3.name: p3,

19}

20whoIsDead(people)

21ifp3.isDead {

22fmt.Println( "who is dead?", p3.name)

23}

24}

25

26funcwhoIsDead(people map[string]*person){

27forname, _ := rangepeople {

28ifpeople[name].age < 50{

29people[name].isDead = true

30}

31}

32}

另外,在interface{}断言里试图直接修改struct属性而非通过指针修改时:

1packagemain

2

3typeperson struct{

4name string

5age byte

6isDead bool

7}

8

9funcmain(){

10p := person{name: "zzy", age: 100}

11isDead(p)

12}

13

14funcisDead(p interface{}){

15ifp.(person).age < 101{

16p.(person).isDead = true

17}

18}

会直接报一个编译错误:

1cannotassigntop.( person) .isDead

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

1packagemain

2

3import"fmt"

4

5typeperson struct{

6name string

7age byte

8isDead bool

9}

10

11funcmain(){

12p := &person{name: "zzy", age: 100}

13isDead(p)

14fmt.Println(p)

15}

16

17funcisDead(p interface{}){

18ifp.(*person).age < 101{

19p.(*person).isDead = true

20}

21}

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

ID:Golangweb