初识GO语言

记录下踩到的坑

一 接口

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package main                                                                       

import (
"fmt"
"bytes"
)

type Stringer interface{
String() string
}

type Point struct{X,Y float64}

func (p *Point) String() string{
var buf bytes.Buffer
fmt.Fprintf(&buf, "%f-%f",p.X,p.Y)
return buf.String()
}

func main(){
r1 := Point{1,2}
var r2 Stringer
r2 = &r1
fmt.Println(&r1) //1.000000-2.000000
fmt.Println(r1) //{1 2}
fmt.Println(r1.String()) //1.000000-2.000000
fmt.Println(r2) //1.000000-2.000000
}
```

第一个语句是调用了带指针的String()结果

第二个Print语句输出r1,是Point结构体的,因为没有声明带指针的String方法,因此fmt输出了默认String方法,也就是{1,2}

第三个语句r1.String()为何能正确执行呢,这是因为编译器自动加了&r1.String()

第四个语句r2是接口类型,赋值r1以后检查类型Stringer实现的接口都被Point实现了,因此可以赋值,并且在调用时调用了r2带指针的String方法,而不是默认的那个

这里要注意的是,r2 = &r1是必须的,因为Point没有声明不带指针的String方法,如果要调用带指针的方法,必须对这个结构体取地址

**二 切片**

```go
package main

import "fmt"

func main() {
array1 := [3]int{1,2}
array2 := [5]int{}
array3 := [...]int{}
array4 := []int{1,2,3,4}
array5 := []int{}
fmt.Println(array1) //[1 2 0]
fmt.Println(array2) //[0 0 0 0 0]
fmt.Println(array3) //[]
fmt.Println(array4) //[1 2 3 4]
fmt.Println(array5) //[]
}
```

切片和数组的声明很类似,困扰了我好久

array1,2,3都是数组类型,4,5是切片类型

在类型的[]里,有数字或者...的都是数组,如果什么都没有则是切片

在我的理解看来,切片是对数组的封装,保存了一个数组的指针

1 切片赋值为数组或者切片并且append小于原来的尺寸

```go
//demo1
array1 := [3]int{1,2}
array2 := []int{}
array2 = array1[0:1]
array2 = append(array2, 3, 4)

//demo2
array3 := []int{1,2}
array4 := []int{}
array4 = array3[0:1]
array4 = append(array4, 3)

fmt.Println(array1) //[1 3 4]
fmt.Println(array2) //[1 3 4]
fmt.Println(array3) //[1 3]
fmt.Println(array4) //[1 3]

对于demo1来说数组能够容纳下这一块数据,因此array2依然指向array1,array1被改为[1 3 4]

对于demo2来说array3底层的数组能够容纳下这一块数据,因此array4依然指向array3底层的数组,array3被改为[1,3]

2 切片赋值为数组或者切片并且append大于原来的尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//demo1
array1 := [3]int{1,2}
array2 := []int{}
array2 = array1[0:1]
array2 = append(array2, 3, 4, 5, 6)

//demo2
array3 := []int{1,2}
array4 := []int{}
array4 = array3[0:1]
array4 = append(array4, 3, 4)

fmt.Println(array1) //[1 2 0]
fmt.Println(array2) //[1 3 4 5 6]
fmt.Println(array3) //[1 2]
fmt.Println(array4) //[1 3 4]

对于demo1来说由于数组是不可变长度的,因此使用append扩容以后,array2指向了一个匿名的数组[1 3 4 5 6],此时array1并没有被影响

对于demo2来说由于array3底层的数组不可变长度的,因此使用append扩容以后,array4指向了一个匿名的数组[1 3 4],此时array3并没有被影响

这样的坑让我意识到,对赋值得到的切片最好慎用append,因为你一不小心就把原来的给改了

三 协程

1
2
3
4
5
6
7
8
9
10
var wg sync.WaitGroup                                                          
indexs := []int{1,2,3,4,5,6}
for _, i := range indexs {
wg.Add(1)
go func(i int){
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()

这里会乱序输出一段数字,但是第一个数字一般都是6,因为在协程实际调度的时候,for循环应该已经完成了

如果这里把协程的匿名函数改为无参数的,会输出6个6来。这个坑相当低级,我却踩到了。。

四 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
type cardInfo struct {                                                             
CardType int
Id int
}

func (c *cardInfo) GetId() int{
return c.Id
}

type personInfo struct {
Name string
Card map[string]cardInfo
}

func main(){
p := &personInfo{
Name:"Tom",
}
p.Card = make(map[string]cardInfo)
p.Card["1"] = cardInfo {
CardType:100,
Id:10086,
}
p.Card["1"].GetId()
}

这个编译会报错

1
2
cannot call pointer method on p.Card["1"]
cannot take the address of p.Card["1"]

map的value值是不能取地址的,因为map会因为扩容等使得value值的地址改变,也就不支持对地址取值了,因此无法调用结构体指针的方法

而结构体字面值的方法用的很少,因为不能修改调用方的值,大都是结构体指针的方法

所以一般来说map存储的value如果是结构体的话,想要调用他的方法,那只能存指针

五 json序列化

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
type cardInfo struct {                                                             
CardType int
Id int
}

type personInfo struct {
Name string
Card map[string]*cardInfo
}

func main() {
p := &personInfo{
Name:"Tom",
}
p.Card = make(map[string]*cardInfo)
p.Card["1"] = &cardInfo {
CardType:100,
Id:10086,
}
data,err := json.Marshal(p)
fmt.Println(p)
fmt.Println(string(data))
if (err != nil) {
fmt.Println("Marshal",err)
}
var OtherP personInfo
if err := json.Unmarshal(data,&OtherP); err != nil {
fmt.Println("Unmarshal",err)
}
fmt.Println(OtherP)
}

输出

1
2
3
&{Tom map[1:0xc820072260]}
{"Name":"Tom","Card":{"1":{"CardType":100,"Id":10086}}}
{Tom map[1:0xc820072510]}

即使是指针类型的cardInfo,一样可以序列化和反序列化,反序列化后该指针将指向另一块地址

这里有三个小坑:

1 如果被序列化的成员名字首字母小写,将不会被序列化

2 如果要修改成员的输出结果,例如Name成员输出name,在后面加上json:"name"即可

3 如果这里Card成员是map[int]*cardInfo,将不能被序列化,这是json的要求,因为json的key不能为数字只能为字符串

六 数组转化为切片

1
2
3
x := [2]int{1,2}                                                               
y := []int{}
y = x

报错

1
cannot use x (type [2]int) as type []int in assignment

数组是不能直接转换为切片的,要这么写才行

1
y = x[:]

参考: why-can-not-convert-sizebyte-to-string-in-go

如果byte数组要转换为string,必须写成string(byte[:]),也就是先转换成byte切片才能转换为string