Go Cheat Sheet

defer

顺序

1
2
3
4
5
6
7
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)

fmt.Println("main")
}
1
2
3
4
main
3
2
1

闭包

1
2
3
4
5
6
7
8
func main() {
a := 1
b := 2

defer fmt.Println(a + b)

a = 2
}
1
3
1
2
3
4
5
6
7
8
9
10
func main() {
a := 1
b := 2

defer func() {
fmt.Println(a + b)
}()

a = 2
}
1
4

返回

1
2
3
4
5
6
7
func t1() int {
a := 1
defer func() {
a++
}()
return a
}
1
1
1
2
3
4
5
6
func t2() (a int) {
defer func() {
a++
}()
return 1
}
1
2

defer 中的 a 来自外部(需要返回的),所以内部自加会影响返回值。

1
2
3
4
5
6
func t4() (a int) {
defer func(a int) {
a++
}(a)
return 2
}
1
2

defer 中的 a 是外部的复制。

os.Exit

1
2
3
4
5
func main() {
defer fmt.Println("1")
fmt.Println("main")
os.Exit(0)
}
1
main

程序退出不会执行 defer

coroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
GoA()
time.Sleep(1 * time.Second)
fmt.Println("main")
}

func GoA() {
defer (func(){
if err := recover(); err != nil {
fmt.Println("panic:" + fmt.Sprintf("%s", err))
}
})()

go GoB()
}

func GoB() {
panic("error")
}

GoA 无法捕获 GoB 的错误。

for idx & range 性能对比

测试

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
package main

import "testing"

type Item struct {
id int
val [4096]byte
}

func BenchmarkForStruct(b *testing.B) {
var items [1024]Item
for i := 0; i < b.N; i++ {
length := len(items)
var tmp int
for k := 0; k < length; k++ {
tmp = items[k].id
}
_ = tmp
}
}

func BenchmarkRangeIndexStruct(b *testing.B) {
var items [1024]Item
for i := 0; i < b.N; i++ {
var tmp int
for k := range items {
tmp = items[k].id
}
_ = tmp
}
}

func BenchmarkRangeStruct(b *testing.B) {
var items [1024]Item
for i := 0; i < b.N; i++ {
var tmp int
for _, item := range items {
tmp = item.id
}
_ = tmp
}
}

结果

1
2
3
4
5
6
7
8
➜  go_test go test -benchmem -run=^$ -bench ^Benchmark range_test.go 
goos: darwin
goarch: arm64
BenchmarkForStruct-8 3486474 330.4 ns/op 0 B/op 0 allocs/op
BenchmarkRangeIndexStruct-8 3637329 329.9 ns/op 0 B/op 0 allocs/op
BenchmarkRangeStruct-8 6810 164832 ns/op 0 B/op 0 allocs/op
PASS
ok command-line-arguments 4.279s

差距大概50倍。
原因是for range的k,v中的v是复制的,需要申请内存(除非值为指针)。
for range尽量只使用下标,不使用值。

any 传入 []anymap[string]any

1
2
3
4
5
6
7
8
9
10
11
12
13
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Slice:
items := make([]any, v.Len())
for i := 0; i < v.Len(); i++ {
items[i] = v.Index(i).Interface()
}
case reflect.Map:
items := make(map[string]any)
for _, key := range v.MapKeys() {
items[key.String()] = v.MapIndex(key).Interface()
}
}

string

for idx & for range

1
2
3
4
5
6
7
8
9
10
11
12
a := "1万🔖"
for i := 0; i < len(a); i++ {
println(i, a[i])
}
println()
for i := range a {
fmt.Println(i, a[i])
}
println()
for i, v := range a {
fmt.Println(i, v)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0 49
1 228
2 184
3 135
4 240
5 159
6 148
7 150

0 49
1 228
4 240

0 49
1 19975
4 128278

拼接

多次拼接使用strings.Builder

1
2
3
4
5
6
7
8
9
10
11
func builderConcat(strs... string) string {
var builder strings.Builder
for _, str := range strs {
builder.Grow(len(str))
}

for _, str := range strs {
builder.WriteString(str)
}
return builder.String()
}

error

==

1
2
3
4
5
err1 := errors.New("error1")
err2 := errors.New("error1")
err1 == err2 // false
reflect.DeepEqual(err1, err2) // true
errors.Is(err1, err2) // false

即使error内部str相同,也不==
可以使用reflect.DeepEqual,但是linter不推荐

time

ticker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"time"
)

func main() {
ti := time.NewTicker(time.Second * 2)
times := 0
for range ti.C {
times++
if times % 2 == 0 {
println("tick")
continue
}
println(time.Now().Format("2006-01-02 15:04:05"))
}
}

输出:

1
2
3
4
5
6
7
8
9
2022-08-08 18:36:00
tick
2022-08-08 18:36:04
tick
2022-08-08 18:36:08
tick
2022-08-08 18:36:12
tick
2022-08-08 18:36:16

tickercontinue不会直接进入下一个循环,而是会等待ticker到达预定时间才运行

json

interface{}

1
2
3
4
5
6
7
8
9
10
var temp interface{}
userStr := `{"name":"zhangsan","age":18}`
err := json.Unmarshal([]byte(userStr), &temp)
if err != nil {
println(err.Error())
} else {
fmt.Printf("%#v\n", temp)
}
// 输出:
// map[string]interface {}{"age":18, "name":"zhangsan"}

interface{} -> map[string]interface{}

flag

bool

1
2
3
b := flag.Bool("b", false, "bool flag")
flag.Parse()
println(*b)
1
2
go run . --b # true
go run . # false

:=

1
2
3
4
5
6
a := 0
if a == 0 {
a, _:= strconv.Atoi("1")
fmt.Printf("%#v\n", a) // 1
}
fmt.Printf("%#v\n", a) // 0

:=表示为一个新对象,if外部a对象被覆盖了,只在if作用域内a为1

单、双、反引号

1
fmt.Printf("%#v\n", '\v') // 11

单引号,表示byte类型或rune类型,对应 uint8和int32类型,默认是 rune 类型。

1
``

用来强调数据是raw data;而rune用来表示Unicode的code point。

nil 详解

https://zhuanlan.zhihu.com/p/354498185

格式化输出(前置0)。

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
var num1 int = 5

// 使用 %0Nd,其中 N 是你想要的宽度
fmt.Printf("%05d\n", num1)
}

在这个例子中,%05d 会打印一个宽度为 5 的整数,如果整数的长度小于 5,那么它会在前面添加 0 以达到 5 的宽度。