GO的一些小细节分享

小文blog小文 2019-07-30 19:40 97人围观

map

1.无序性

map存储的数据并不保证有序性,通常可以把key写入一个slice切片,依靠slice切片的有序性来读取map。

s := []int{0,1,2}
m := map[int]string{"第一名","第二名","第三名"}
for _,v := range s {
    print(m[v])
}

2.不能并发读写,会panic,一定要加锁。

time时区

windows下,time.Parse()和time.Format()时区默认是本地,但是在linux下,time.Parse()时区默认是UTC,而time.Format()时区默认是本地,这就会出现一些不符合预期的问题。

可以使用time.ParseInlocation()使用本地时区去解析时间。

timeFormat := "2006-01-02 15:04:05"
now := time.Now()
timeStr := now.Format(timeFormat)
//bug
bt, _ := time.Parse(timeFormat, timeStr)
bts := bt.Format(timeFormat)
fmt.Printf("bt(%v) bts(%s) diff(%d)", bt, bts, bt.Unix()-now.Unix())//bt(2019-07-25 12:19:52 +0000 UTC) bts(2019-07-25 12:19:52) diff(28800)
//fix
fbt, _ := time.ParseInLocation(timeFormat, timeStr, time.Local)
fts := fbt.Format(timeFormat)
fmt.Printf("fbt(%v) fts(%s) diff(%d)", fbt, fts, fbt.Unix()-now.Unix())//fbt(2019-07-25 12:19:52 +0800 CST) fts(2019-07-25 12:19:52) diff(0)

UTC和CST的区别,UTC是国际标准时间,CST指中国时间。UTC通常比CST多8小时。

变量作用域

1.注意变量的作用域,全局变量和局部变量的区别以及变量屏蔽现象,一般每个花括号都是一个作用域。

var a int
b := 1
{
    a, b := 10, 10
    fmt.Println(a, b) //10 10
}
fmt.Println(a, b) //0 1

2.闭包

for i:=0; i<10; i++ {
    go fmt.Println(i)
}

会全部打印10,以下是正确做法

for i:=0; i<10; i++ {
    i:=i
    go fmt.Println(i)
}
for i:=0; i<10; i++ {
    go func(i int){fmt.Println(i)}(i)
}

slice append

append()返回的可能是原切片地址,也可能是新生成的切片地址,主要是底层数组的容量够不够容纳新的元素,如果不够就会发生扩容行为。

s := []int{1}
s = append(s, 2) //发生扩容 cap为2
s = append(s, 3) //发生扩容 cap为4
x := append(s, 4)//不扩容
y := append(s, 5)//不扩容
fmt.Println(s, x, y) //[1 2 3] [1 2 3 5] [1 2 3 5]

因为x,y append的时候,没有发生扩容,和s共享底层数组,因此发生比较诡异的问题。

s := []int{1}
s = append(s, 2) //发生扩容 cap为2
s = append(s, 3) //发生扩容 cap为4
x := append(s, 4)//不扩容
x1 := make([]int, len(x)) //新建一个全新数组,切断x1与s的联系
copy(x1,x)
y := append(s, 5)//不扩容
fmt.Println(s, x, y, x1) //[1 2 3] [1 2 3 5] [1 2 3 5] [1 2 3 4]

或者利用一个语法小技巧,s[0:len(s):len(s)]将容量改为长度相等,强迫追加时复制到新数组。

s := []int{1}
s = append(s, 2) //发生扩容 cap为2
s = append(s, 3) //发生扩容 cap为4
x := append(s[0:len(s):len(s)], 4)//强制扩容
y := append(s[0:len(s):len(s)], 5)//强制扩容
fmt.Println(s, x, y) //[1 2 3] [1 2 3 5] [1 2 3 5]

同时注意make内置函数,如需直接利用索引去给切片赋值,需要声明第二个参数len的长度,注意越界情况,如果需要append赋值,则必须把第二个参数置0,第三个参数写为预估的容量,避免频繁扩容。

s := make([]int,5)
s[1] = 2 //[2,0,0,0,0]
s = append(s,3) //[2,0,0,0,0,3]
s1 := make([]int,0,5)
//s1[0] = 0 panic index of range
s1 := append(s1,1)//[1]
s1 := append(s1,2)//[1,2]

接口实现方面

type animal interface {
Echo()
}
type cat struct {
}
func (c *cat) Echo(){
    fmt.Println("猫叫")
}
type dog struct {
}
func (d dog) Echo() {
    fmt.Println("狗叫")
}
func main() {
    c := cat{}
    cp := &cat{}
    d := dog{}
    dp := &dog{}
    //cat类型方法集没有Echo,&cat类型才有。
    var _ animal = c //cannot use c (type cat) as type animal in assignment: cat does not implement animal (Echo method has pointer receiver) 
    var _ animal = cp
    var _ animal = d
    var _ animal = dp
    //下面其实是一种语法糖
    c.Echo()  //(&c).Echo()
    dp.Echo() //(*dp).Echo()
}

channel

1.永远保持在发送端关闭channel,如果多个发送者,需要recover机制,因为关闭已关闭的channel会panic

2.读写channel零值会永远阻塞

3.close channel会给接收方发送一个零值,需要根据val,ok <- ch ok来判断是关闭的信号还是发送的零值

4.缓冲channel的关闭,ok会延后,也就是只有当读完后,ok才为false

5.channel是引用类型,发送和接收都是值拷贝

defer

1.defer的执行类似于栈,现进后出

2.匿名与非匿名返回值区别

func defer1() int {
    var i int
    defer func() {
        i++
    }()
    return i
}
func defer2() (i int) {
    defer func() {
        i++
    }()
    return i
    }
func main() {
    fmt.Println(defer1(), defer2()) //0, 1
}

defer1在return时候会先创建一个中间变量接收i的值再返回,此时是0,在检测是否有defer,defer中操作的i不是中间变量。

defer2由于返回的不是匿名变量,所以不会创建中间变量,defer对i的修改对返回值造成了影响。

3.如果在一个函数中操作资源很多,请及时close,避免导致性能问题

for {
    f,_ := os.Open("text.txt")
    defer f.Close() //这里会在函数return后才会释放资源,去掉defer比较好。
}

4.os.Exit时defer不会被执行

5.链式调用

type Total struct {
    Num int
}
func (t *Total) Add() *Total {
    t.Num++
    return t
}
func main() {
    t := &Total{}
    defer t.Add().Add().Add()
    fmt.Println(t.Num) //2
}

会先执行t.Add().Add(),然后把最后一个Add()压入defer

值类型与引用类型

基础类型,数组和结构体是值类型,零值为类型的零值。

slice,map,channel,interface都是引用类型,零值为nil。

sync.WaitGroup由于是结构体,所以注意用引用传参。

json.RawMessage

在解析json时,有时某个字段其结构是根据其他字段(比如有个类型含义的字段)决定的,这个时候在解析时,需要先解析一部分,进行判断后,再解析出合适的类型。

json.RawMessage类型是[]byte的别名

type Something struct {
    Type   string      `json:"type"`
    Object interface{} `json:"object"`
}
type Cat struct {
    CatName string `json:"cat_name"`
}
type Dog struct {
    DogName string `json:"dog_name"`
}
func main() {
input := `
{
  "type": "Cat",
  "object": {
      "cat_name": "猫"
  }
}
`
var object json.RawMessage
ss := Something{
    Object: &object,
}
if err := json.Unmarshal([]byte(input), &ss); err != nil {
    panic(err)
}
switch ss.Type {
    case "Cat":
        var c Cat
        if err := json.Unmarshal(object, &c); err != nil {
            panic(err)
        }
        fmt.Printf("cat %+v", c)
    case "Dog":
        var d Dog
        if err := json.Unmarshal(object, &d); err != nil {
            panic(err)
        }
        fmt.Printf("dog %+v", d)
}
//cat {CatName:猫}
}

待续

转载请注明来自小文blog,本文标题:GO的一些小细节分享

发布评论
生活是一场戏,主角当累了,你亦可成为观众,停下脚步,歇一歇