Matthew Note

Golang Note

TIP

  • Go command executables are statically linked; the package objects need not be present to run Go programs.
  • package name 最好和folder名字一致
  • Executable commands must always use package main.
  • You write a test by creating a file with a name ending in _test.go that contains functions named TestXXX with signature func (t *testing.T). The test framework runs each such function; if the function calls a failure function such as t.Error or t.Fail, the test is considered to have failed.
  • 在 Go 中,首字母大写的名称是被导出的。在导入包之后,你只能访问包所导出的名字,任何未导出的名字是不能被包外的代码访问的。Foo 和 FOO 都是被导出的名称。名称 foo 是不会被导出的。
  • 函数可以返回任意数量的返回值
  • 没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。
  • 变量定义可以包含初始值,每个变量对应一个。如果初始化是使用表达式,则可以省略类型;变量从初始值中获得类型。
  • 在函数中, := 简洁赋值语句在明确类型的地方,可以用于替代 var 定义。函数外的每个语句都必须以关键字开始( var 、 func 、等等), := 结构不能使用在函数外。
  • 变量在定义时没有明确的初始化时会赋值为 零值 。
  • 与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换。 试着移除例子中 float64 或 int 的转换看看会发生什么。
  • 如果函数参数是指针那么go可以自动转指针即便传入的是值
  • struct{}通常被用作占位符,就是一个空对象
  • break labalgoto不同,前者不会再进入循环/switch
  • 内嵌函数不能以func func_name(){}来声明,只能func_name := func() {}
  • duck type?
  • shadow value: 实际上就是短声明时候的覆盖问题,你可以使用 vet 命令来发现一些这样的问题。 默认情况下,vet不会执行这样的检查,你需要设置-shadow参数:go tool vet -shadow your_file.go
  • 你不能在一个单独的声明中重复声明一个变量,但在多变量声明中这是允许的,其中至少要有一个新的声明变量。
  • 字符串不会为nil
  • x [3]int是数组, x []int是slice,slice是按照传引用?如同python里面的list/dist
  • 以小写字母开头的结构体将不会被(json、xml、gob等)编码,因此当你编码这些未导出的结构体时,你将会得到零值。
  • 在一个nil的channel上发送和接收操作会被永久阻塞。这个行为有详细的文档解释,但它对于新的Go开发者而言是个惊喜。
  • 如果结构体中的各个元素都可以用你可以使用等号来比较的话,那就可以使用相号, ==,来比较结构体变量。
  • DeepEqual()不会认为空的slice与“nil”的slice相等。这个行为与你使用bytes.Equal()函数的行为不同。bytes.Equal()认为“nil”和空的slice是相等的。
  • 在“range”语句中生成的数据的值是真实集合元素的拷贝。它们不是原有元素的引用。这意味着更新这些值将不会修改原来的数据。同时也意味着使用这些值的地址将不会得到原有数据的指针。
  • 当你重新划分一个slice时,新的slice将引用原有slice的数组。如果你忘了这个行为的话,在你的应用分配大量临时的slice用于创建新的slice来引用原有数据的一小部分时,会导致难以预期的内存使用(因为重新划分的slice占用大小还是原来的大小, 尽管只取了一小部分)。为了避免这个陷阱,你需要从临时的slice中拷贝数据(而不是重新划分slice)。
  • for语句中的迭代变量在每次迭代时被重新使用。这就意味着你在for循环中创建的闭包(即函数字面量)将会引用同一个变量(而在那些goroutine开始执行时就会得到那个变量的值)。
  • 被defer的函数的参数会在defer声明时求值(而不是在函数实际执行时)
  • 然而并不是所有的变量是可取址的。Map的元素就不是。通过interface引用的变量也不是, 如果你有一个struct值的map,你无法更新单个的struct值,原因就是因为他不可取指,解决方法:第一个有效的方法是使用一个临时变量,另一个有效的方法是使用指针的map。
  • 如果你想知道变量分配的位置,在“go build”或“go run”上传入“-m“ gc标志(即,go run -gcflags -m app.go)
  • 可以显式的唤醒调度器runtime.Gosched()
  • iota
  • bytes.Buffer => StringBuilder 用来拼接字符串
  • 返回值可以返回局部变量的指针,因为go的变量不是组织在栈中的,所以只要有引用他就不会再内存中被回收
  • new(TYPE) === &TYPE{}
  • 引用类型: map,slice,channel, func, 方法
  • append(s, t...) 把t切片中所有值拼放到s里
  • switch x.(type) 用于判断类型
  • Memorize 函数 类似于python里的LRU
  • []Printable{&b, d} 声明一个接口slice,包含两个接口实现
  • append一个slice会可能导致cap自动变长,变长之后slice的索引会改变,所以之前生成的subslice不会得到后续的更改
  • type FakeString string FakeString不会自动转string,但是string可以自动转FakeString
  • type assert 只适用于inferface,其他的还是要用type cast
  • nil可以赋值给任何指针或引用类型的变量
  • new返回的是指针,不初始化属性,make是返回值
  • context 如果不cancel 父context是不会捕获到done的
  • go run file.go可以运行go,但是如果目录下有unittest,那么多说情况下还是会出错,所以go通常不适合单独运行,shebang可以让你以近似解析式语言的方式来运行go

  • 同一个目录不能有多个package,目录名字和pacakge可以不一样

  • When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for integers, 0.0 for floats, “” for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
  • json.Marshal 可以用过设置tag json:"my_name, omitempty" 来跳过某个空字段
  • 使用string(120) 是把整型换成他对应的ascii码,应该用strconv.Itoa(120)
  • When you have a struct implementing an interface, a pointer to that struct implements automatically that interface too. That’s why you never have *SomeInterface in the prototype of functions, as this wouldn’t add anything to SomeInterface, and you don’t need such a type in variable declaration

TODO

  • slice复制,赋值等

struct

struct的tag使用\``和“` 是一个意思

A field declared with a type but no explicit field name is an anonymous field, also called an embedded field or an embedding of the type in the struct. An embedded type must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type. The unqualified type name acts as the field name.

1
2
3
4
5
6
7
8
// A struct with four anonymous fields of type T1, *T2, P.T3 and *P.T4
struct {
T1 // field name is T1
*T2 // field name is T2
P.T3 // field name is T3
*P.T4 // field name is T4
x, y int // field names are x and y
}

A field or method f of an anonymous field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.

Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in composite literals of the struct.

Given a struct type S and a type named T, promoted methods are included in the method set of the struct as follows:

  • If S contains an anonymous field T, the method sets of S and S both include promoted methods with receiver T. The method set of S also includes promoted methods with receiver *T.
  • If S contains an anonymous field T, the method sets of S and S both include promoted methods with receiver T or *T.
    A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.
1
2
3
4
5
6
7
8
9
10
11
12
13
struct {
x, y float64 "" // an empty tag string is like an absent tag
name string "any string is permitted as a tag"
_ [4]byte "ceci n'est pas un champ de structure"
}
// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
microsec uint64 `protobuf:"1"`
serverIP6 uint64 `protobuf:"2"`
}

Conclude

interface是描述一个类型的关键因素,根据duck type,一个对象的方法决定了他的类型, 可以用接口的变量来描述结构(struct),这就好像是CPP里多态的基类指针

interface

  • func (t T)Printer() {} 表示T实现了Printer接口
  • func (t *T)Printer() {} 表示T指针实现了Printer接口
  • The value of an uninitialized variable of interface type is nil.

select

  • 如果不带default那么如果channal没有内容他会阻塞,然后等有内容后恢复,之后select整个语句执行完成,如果带default那么如果发现阻塞会直接到default执行default,然后结束select整个过程
  • select 读取chan的时候,在一个case之间他会一直等待返回,chan才有机会读取下一个,也就是说,这个时候写会导致阻塞,小心死锁

constant

常量的定义与变量类似,只不过使用 const 关键字。
常量可以是字符、字符串、布尔或数字类型的值。
常量不能使用 := 语法定义。

switch

除非以 fallthrough 语句结束,否则分支会自动终止。
没有条件的 switch 同 switch true 一样。
这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。

1
2
3
4
5
6
7
8
9
10
11
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}

array和slice

slice 可以包含任意的类型,包括另一个 slice。
slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
slice 的零值是 nil 。

1
2
3
4
5
a := make([]int, 5) // len(a)=5 这会分配一个全是零值的数组并且返回一个 slice 指向这个数组:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4

map

通过双赋值检测某个键存在:elem, ok = m[key] 如果 key 在 m 中, ok 为 true。否则, ok 为 false,并且 elem 是 map 的元素类型的零值。同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。

  • 嵌套的map slice要一次初始化,不然会entry nil

闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}

interface

类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

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
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了 Abser
// 下面一行,v 是一个 Vertex(而不是 *Vertex)
// 所以没有实现 Abser。
a = v
fmt.Println(a.Abs())
}

defer

defer 语句会延迟函数的执行直到上层函数返回。
延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。
延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。

1
2
3
4
5
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}

if

在 if 的便捷语句定义的变量同样可以在任何对应的 else 块中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 这里开始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}

native type

bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 的别名

rune // int32 的别名
// 代表一个Unicode码

float32 float64

complex64 complex128
这个例子演示了具有不同类型的变量。 同时与导入语句一样,变量的定义“打包”在一个语法块中。
int,uint 和 uintptr 类型在32位的系统上一般是32位,而在64位系统上是64位。当你需要使用一个整数类型时,你应该首选 int,仅当有特别的理由才使用定长整数类型或者无符号整数类型。

package

Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。
import 下划线(如:import hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。

内嵌

内嵌是不用显式的指出内嵌类型再去调用内嵌结构的方法,而聚合需要
子结构中是不能覆盖内嵌结构中的变量的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type base struct {
a int
b string
}
type derive struct {
base //内嵌,这里就可以直接找得到print, 自动转换?还是调用的func (b *base) xfunc(){}
Base base //这个就不是内嵌,是聚合所以不算是继承关系, 所以他的对象就不能调用到print, 要显式的指出
name string
}
func (b *base) print(s string) (ret string) {
ret = "aaa"
fmt.Printf("base\n")
return
}

Reference

slice and append gotcha
How to avoid Go gotchas

编码规则

  • 全局变量:驼峰式,结合是否可导出确定首字母大小写
  • 参数传递:驼峰式,小写字母开头
  • 局部变量:下划线形式
  • 包名应该为小写单词,不要使用下划线或者混合大小写。
  • 单个函数的接口名以”er”作为后缀,如Reader,Writer,接口的实现则去掉“er”
  • 两个函数的接口名综合两个函数名
  • 三个以上函数的接口名,类似于结构体名
  • 采用全部大写或者全部小写来表示缩写单词
  • 对于少量数据,不要传递指针
  • 对于大量数据的struct可以考虑使用指针
  • 传入参数是map,slice,chan不要传递指针
  • 因为map,slice,chan是引用类型,不需要传递指针的指针

返回值

1
2
3
4
5
6
7
8
9
10
11
v, ok = m[key] // map lookup
v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive
v = m[key] // map查找,失败时返回零值
v = x.(T) // type断言,失败时panic异常
v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败)
_, ok = m[key] // map返回2个值
_, ok = mm[""], false // map返回1个值
_ = mm[""] // map返回1个值

var

1
2
3
4
5
var (
_ StdLogger = &log.Logger{}
_ StdLogger = &Entry{}
_ StdLogger = &Logger{}
)

数组

  • 数组是值。将一个数组赋予另一个数组会复制其所有元素。
  • 特别地,若将某个数组传入某个函数,它将接收到该数组的一份副本而非指针。
  • 数组的大小是其类型的一部分。类型 [10]int 和 [20]int 是不同的。

append

但如果我们要像 Append 那样将一个切片追加到另一个切片中呢? 很简单:在调用的地方使用 …,就像我们在上面调用 Output 那样。以下代码片段的输出与上一个相同。

1
2
3
4
x := []int{1,2,3}
y := []int{4,5,6}
x = append(x, y...)
fmt.Println(x)

runtime.keepAlive

The purpose of keep alive is to ensure that the garbage collector does not collect a value which is no longer referenced inside a function. The usual cases where this is a problem is if you have obtained the file descriptor to an underlying os.File, or you have passed memory to cgo which was allocated inside the same function.

1
2
3
4
5
6
7
func g() {
x := make([]byte, 2^24)
y := x[0]
if y > 0 {
panic("wat")
}
}

Most people would expect that x would be garbage collected before the end of the function because it is no longer referenced. But consider this situation

1
2
3
4
5
func h() {
x, _ := os.Open("somefile")
fd := x.Fd()
// use fd in some kind of select or poll operation inside this function.
}

If you’ve follow the logic of what I’ve said up to this point, you would expect x to be dead after the assignment to fd, but we know that *os.File values have a finaliser attached to them, which will be invoked soon after x goes out of scope at the end of the second line.

When that happens, the finalizer will close the file descriptor that fd references. If you’re lucky you’ll get an error about writing to a closed file. If you’re unlucky, another goroutine will open a different file, and receive the same file descriptor number causing file corruption.

The workaround is to use runtime.KeepAlive to keep the reference in x live for the duration of the function.

The moral of the story is finalisers are terrible, and adding one to *os.File was a mistake.

编译

  • go build -x 打印出编译相关的信息

cgo编译

Linking golang statically

交叉编译

1
env GOOS=linux GOARCH=amd64 go build

接口类型作为参数

  • 接口类型作为参数是否是copy取决于传入参数是指针类型还是struct类型,如果是一个指针类型那么就不会copy
  • 接口传给接口也不会复制
  • struct赋值给接口会复制
  • struct指针复制给接口不会复制

实际上可以认为所有的类型都是穿值的,只不过有些是指针有些是值
Copying Interface Values In Go

微服务设计

  • 同步是一个比较大的问题,尽量需要同步的由单独的模块来做
  • Json token在某种情况下可以解决无状态的问题
  • 内部服务消息化更有助于解耦和和去同步

Test

  • go test -v -run TESTNAME 可以指定运行单个UT
  • 或者用go test -v xxx_test.go 这里有个问题:
    But there’s a catch. This works well if
  1. foo.go is package foo
  2. foo_test.go is package foo_test and imports ‘foo’.

If foo_test.go and foo.go are the same package (a common case), then you must name all other files required to build ‘foo_test’. In this example it would be:

1
$ go test foo_test.go foo.go

单独编译成一个binary

1
2
go test -c stream_test.go -o stream.test 单独编译一个UT
./stream.test -test.v 单独运行

Reflect

  • reflect.ValueOf会成成一个含有复制这个变量的Value对象,所以Set*方法都不能用,因为unaccessable, 如果需要修改,传入的应该是一个指针
  • interface会复制原始对象