在翻阅kubernetes的文档时,里面刚好谈到go一些注意事项。
结合以前遇过的坑爹API,汇成此文.
语言特性
数据切片
原则是取下标,不取上标
1
2
3
4
5
a:=[]int{0,1,2,3,4}
a=a[:]
a=a[2:4] //从第[2]位起取,直至[4-1]位,所以结果只有2个元素
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):2 ; cap(a):3; values:[2 3]
数组切片是一种指针,所以才会引申出len/cap/append的问题
len/cap/append的问题
关于len/cap的问题可以看下面2篇文章
简单地说,len就是当前数组/切片实际元素的计数,cap是底层数组的长度,砍头不砍尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a :=make([]int,2,3)
// a[2]=666 //panic: runtime error: index out of range
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):2 ; cap(a):3; values:[0 0]
a=a[1:]
// 第0位没了,但是后面的还在,所以cap=3-1
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):1 ; cap(a):2; values:[0]
a=append(a,2)
a=append(a,3)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):3 ; cap(a):4; values:[0 2 3]
b:=append(a,4)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):3 ; cap(a):4; values:[0 2 3]
fmt.Printf("len(b):%d ; cap(b):%d; values:%v \n",len(b),cap(b),b)
// len(b):4 ; cap(b):4; values:[0 2 3 4]
append
是在数组末尾追加元素,返回一个新的数组,原数组不会发生改变.
len=cap时,继续append
,cap会翻倍
不会用defer就别瞎装逼
defer
是倒序执行,而且后定义的defer
先执行(这个很好理解,先定义A变量,然后定义B变量,A的作用域比B长,先清理B是正确选择).
1
2
3
4
5
6
7
8
9
for i := 0; i < 10; i++ {
defer fmt.Println(i) // OK; prints 9 ... 0
defer func() { fmt.Println(i) }() // WRONG; prints "10" 10 times
defer fmt.Println(i) //跟下面一行结果一样
defer func(i int) { fmt.Println(i) }(i) // OK prints 0 ... 9 in
defer print(&i) // WRONG; prints "10" 10 times unpredictable order
go func() { fmt.Println(i) }() // WRONG; totally unpredictable.
}
这个例子是The Three Go Landmines.markdown提出来的。要理解其实不难,把for拆解出来就行了。注意for只是控制了循环的边界,循环结束后,i=10.
需要分清的传入参数的情况
defer func() { fmt.Println(i) }()
相当于
1
2
3
4
5
6
7
8
i:=0
defer func() { fmt.Println(i) }()
i=1
defer func() { fmt.Println(i) }()
......
i=9
defer func() { fmt.Println(i) }()
i=10
所以defer func() { fmt.Println(i) }()
的结果为打印10次10
而defer fmt.Println(i)
等价于defer func(i int) { fmt.Println(i) }(i)
把局部变量传入,defer内部获得的是值的复制,所以实际上是
1
2
3
4
5
6
7
8
i:=0
defer fmt.Println(0)
i=1
defer fmt.Println(1)
......
i=9
defer fmt.Println(9)
i=10
还有无法定义变量获取defer函数的返回值,defer函数带返回值根本毫无意义
1
2
3
4
5
6
7
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
if变量作用域
if 里面定义的变量,即便是跟已定义变量同名,在if中对其赋值,对已经被定义的变量不会有影响。
1
2
3
4
5
6
7
8
9
10
11
var ErrDidNotWork = errors.New("did not work")
func DoTheThing(reallyDoIt bool) (err error) {
if reallyDoIt {
result, err := tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
半新不旧变量
常见于err和多重返回值函数,针对这种情况,可以通过if作用域强制覆盖,或者命名一个新变量解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var e error
func main() {
s, e := a() //无法编译
s, err2 := a() //OK
if s, e := a(); e != nil { //OK
}
······
fmt.Print(s)
}
func a() (str string, err error) {
return "", nil
}
goroutine 机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"runtime"
)
func main() {
var i byte
go func() {
for i = 0; i < 255; i++ {
fmt.Println(i)
}
}()
fmt.Println("start")
runtime.Gosched()
runtime.GC()
fmt.Println("end")
}
- 问题1: 运行的结果
- 问题2: 去掉
runtime.GC()
之后的结果 - 问题3: 如果你是goruntime,去掉
fmt.Println(i)
之后要怎么优化编译
答案:
Gosched() 让出了CPU时间片,让 goroutine 有机会运行
Goroutine采用的是半抢占式的协作调度,只有在当前Goroutine发生阻塞时才会导致调度
GC()需要stop the world,所以会等待协程运行完
如果没有 GC()这个方法,则运行结果完全不可控
for 里面一堆废话,最合理的优化,应该是连协程都不创建,哈哈哈哈哈
坑爹API
获取字符串长度:
1
len([]rune("文件夹,子文件夹,"))
Split的坑
1
2
3
4
5
s := strings.Split("shit,", ",")
fmt.Printf("len(s):%d\n", len(s))//2
for _, v := range s {
fmt.Printf("%s", v)
}
strings.Split的字符串,如果用来分割的字符串恰好出现完整字符串的最后面,获得的数组长度会+1,这个数组的最后一个元素会是一个空白
时间转换函数
golang的字符串转日期函数非常不灵活,并且格式化的字符串是一个魔术变量,代表golang的面世时间…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ToTime 字符串转golang内置当地时间
func ToTime(str string) (time.Time, error) {
var err error
format1 := "2006-01-02 15:04:05"
loc, err := time.LoadLocation("Local");
if err != nil {
return time.Now(), err
}
date, err := time.ParseInLocation(format1, str, loc);
if err == nil {
return date, nil
}
format2 := "2006-01-02"
date, err = time.ParseInLocation(format2, str, loc);
if err == nil {
return date, nil
}
sqlserverFormat:= "2006-01-02T15:04:05"
date, err = time.ParseInLocation(sqlserverFormat, str, loc);
if err == nil {
return date, nil
}
return time.Now(), err
}
值类型不会溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s int32 = 5120
fmt.Print(s * 1024 * 1024)
fmt.Print("\n")
fmt.Print(int64(s) * 1024 * 1024)
fmt.Print("\n")
fmt.Print(math.MaxInt32)
fmt.Print("\n")
fmt.Print(math.MaxInt64)
/*
1073741824
5368709120
2147483647
9223372036854775807
*/
以前用C#的时候,如果定义一个值类型变量,赋予它一个超出范围的值的话是会出异常的,然而到了golang,直接变成这个类型无符号最大值
构建篇
启用了go module
之后,对依赖的拉取变得更加频繁。但是基于中国有中国特色的互联网,我们有时候很难get到我们需要的依赖源代码,进而导致项目编译失败,CI失败。于是,我们需要一个proxy。
1
export GOPROXY=https://goproxy.io
开发建议
其他语言的可看 Google Style Guides
论喷子,还是没王垠强
课后作业
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
func a() (str string) {
defer func() {
str = "a"
}()
defer func(str string) {
str = "b"
}(str)
defer func() {
if str2 := "c"; str2 == "c" {
str = str2
}
}()
defer func() {
if str := "d"; str == "d" {
str = "d"
}
}()
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
str = "f"
return str
}
注释掉里面的defer
,观察一下不同组合下的函数的结果,看懂了,就算理解defer了