Go程序执行顺序

按顺序导入所有被main包引用的包,并为每个包执行init函数。导入包完成后,为main执行init函数,并开始启动main

init函数

init函数无参数,无返回值,优先于main函数执行。初始化时总是单线程执行,并且严格按照包的依赖关系进行。每个源文件只能有一个init函数。

const类型

没有定义类型的const对象,可以在第一次被使用时确定类型。确定后该常量将无法再隐式转为其他类型。

1
2
3
4
5
6
const aa, bb, cc = "eat", 2, "ok"
const (
a = iota
b
c
)

默认值

golang中所有的变量都是初始化过的。

类型 初始值
int 0
float 0.0
bool false
string ""
pointer nil

变量赋值

1
2
3
var a, b, c int
a, b, c = 1, 2, 3
a, b = b, a

Variables can be redeclared in short multi-variable declarations where at least one new variable is introduced.
Redeclaration does not introduce a new variable; it just assigns a new value to the original.

1
2
3
4
5
func main() {
m := 0
m, n := 1, 2
fmt.Println(m, n)
}

fmt

fmt 意义 例子 输出
%d 整数 fmt.Printf("%d", 3) 3
%x 16进制 fmt.Printf("%x", 11) b
%X 16进制 fmt.Printf("%X",11) B
%.mg 保留几位有效数字 fmt.Printf("%.5g", 1234.5678) 1234.6
%f 浮点数 fmt.Printf("%f", 1234.5678) 1234.567800
%e 科学计数 fmt.Printf("%e", 1234.5678) 1.234568e+03

数字强转

浮点类型强转int时采用截断的方式:

1
2
3
4
var := 3.9
fmt.Printf("%d", int32(a))

> 3

字符串

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
unicode.IsLetter(ch) // 是否为字母
unicode.IsDigit(ch) // 是否为数字
unicode.IsSpace(ch) // 是否为空格

s3 := s1 + s2

strings.HasPrefix(s, prefix string) bool
strings.HasSuffix(s, suffix string) bool
strings.Contains(s, substr string) bool
strings.Index(s, str string) int
strings.LastIndex(s, str string) int
strings.Replace(str, old, new, n) string // (n == -1 replace all )
strings.Count(s, str string) int
strings.Repeat(s, count int) string
strings.ToLower(s) string
strings.ToUpper(s) string

strings.TrimSpace(s) string // 剔除开头和结尾的空白符号
strings.Trim(s, "cut") string // 剔除开头和结尾的指定字符
strings.TrimLeft(s, "cut") string
strings.TrimRight(s, "cut") string

strings.Fields(s) []string // split with space
strings.Split(s, sep) []string // split with sep
strings.Join(sl []string, sep string) string // join with sep
1
2
3
4
5
6
7
8
9
10
11
// gives only the bytes:
for i:=0; i < len(str); i++ {
c := str[i]
}
// gives the Unicode characters:
for ix, ch := range str {
unicode_c := str[i]
}

// unicode length
utf8.RuneCountInString(str)

strconv包含有所有和字符串相关的转换,任何类型车字符串总是成功的。

if条件语句

if语句中使用 := 申明的变量的作用域只有if...else if...else中。

1
2
3
4
5
6
7
if value, err := fo(); err != nil {
fmt.Println("%s", err.Error())
} else {
fmt.Println("%v", value)
}

fmt.Println("%v", value) // compile error: value undefine
1
2
3
if value, ok := readData(); ok {
// do something with value
}

switch语句

switch匹配到某个分支后,执行完相关代码会退出整个swtich控制块,不需要显示使用break。如果需要在执行完某分支后继续后续分支,可以使用fallthrough关键字。fallthrough只会继续一个分支,不会穿透整个匹配条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
switch condition {
case value_1:
foo()
case value_2:
bar()
default:
panic()
}

switch result := calc(); {
case result < 0:
// do something
case result > 0:
// do something
default:
// do something
}

for & for-range loop

1
2
3
4
index := 5
for index > 0 {
index = index - 1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
for pos, char := range str {
// do something
}

var dict map[string]string
for key, value := range dict {
// do something
}

var array []string
for index, value := range array {
// do something
}

循环体申明的变量无法被闭包捕获。需要按值传递进入下层闭包,否则会收到不预期的值。

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
// ERROR: loop variable will not go closure
package main

import (
"fmt"
"time"
)

func goroutineRun(values []int) {
for value := range values {
go func() {
time.Sleep(1 * time.Second)
fmt.Println(value) // ERROR: value is not what we want
}()
}
}

func main() {
goroutineRun([]int{1, 2, 3, 4, 5})
time.Sleep(100 * time.Second)
}

->
4
4
4
4
4
1
2
3
4
5
6
7
// OK
for value := range values {
go func(val interface{}) {
time.Sleep(1 * time.Second)
fmt.Println(value)
}(value)
}

函数(function)

  • named function
  • anonymous function & lambda function
  • method
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
type binOp func(int, int) int

func foo(int, int) int

func bar() any {
return func() {
fmt.Println("hello")
}
}

func foobar(op binOp) {
op("hello")
}

func foo(args ...any) {
for _, arg := range args {
// do something
}
}

func foo(args ...any) {
fmt.Println(...args) // flat args
}

func select2nd[T any](args ...any) T {
var ret T

if args[1] != nil {
ret = args[1].(T)
}

return ret
}

// Ternary return value as:
// condition ? left : right
// don't user condition as nil-check that value depends on condition, such as
// Ternary(ptr != nil, ptr.value, 0)
// will panic because ptr.value is param that need to be calculated when Ternary called.
func Ternary[T any](b bool, left T, right T) T {
if b {
return left
} else {
return right
}
}

在函数调用时,切片(slice)、字典(map)、接口(interface)和通道(channel)默认使用引用传递。

数组和切片

数据长度也是数组结构体的一部分,所有[5]int[10]int是不同的类型。数组长度最大为2Gbit
数组默认是按拷贝传递的,切片按引用传递。
切片会导致整个底层数组被锁定,底层数组无法释放内存。如果底层数组较大会对内存产生很大的压力。

1
2
3
4
var slice []any = array[start:end]
var slice []any = array[:]
slice2 := slice1[:len(slice1)-1] // pop_last
slice3 := slice1[1:] // pop

make vs new

1
2
make([]int, 50, 100)
new([100]int)[0:50]

new(T)为指定类型T分配一片内存,并将其初始化,然后返回此内存的起始地址,类型为*T。相当于调用&T{}
make(T)返回一个类型为T的初始值,只能用于slicemapchannel

1
2
slice := []byte(string)
copy(dst []byte, src string)

Golang中的字符串是不可变字符串。

1
2
3
4
5
6
s := "abcdef"
s[3] = 'z' // panic

c := []byte(s)
c[3] = 'z'
s2 := string(c) // s2 == "abczef"

Map

读取元素,直接使用 dict[key]即可 ,如果key不存在,也不报错,会返回其value-type的零值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var dict1 map[string]int

dict2:= map[string]int{
"one": 1,
"two": 2,
}

dict3 := make(map[string]int)

if value, ok := dict["key"]; ok {
fmt.Println(value)
}

delete(dict, "key") // always success

range map时,可以在循环中安全的删除key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main()  {
d := map[string]string{
"asong": "帅",
"song": "太帅了",
}
for k := range d{
if k == "asong"{
delete(d,k)
}
}
fmt.Println(d)
}

# 运行结果
map[song:太帅了]

Big Number

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
// big.go
package main

import (
"fmt"
"math"
"math/big"
)

func main() {
// Here are some calculations with bigInts:
im := big.NewInt(math.MaxInt64)
in := im
io := big.NewInt(1956)
ip := big.NewInt(1)
ip.Mul(im, in).Add(ip, im).Div(ip, io)
fmt.Printf("Big Int: %v\n", ip)
// Here are some calculations with bigInts:
rm := big.NewRat(math.MaxInt64, 1956)
rn := big.NewRat(-1956, math.MaxInt64)
ro := big.NewRat(19, 56)
rp := big.NewRat(1111, 2222)
rq := big.NewRat(1, 1)
rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)
fmt.Printf("Big Rat: %v\n", rq)
}

/* Output:
Big Int: 43492122561469640008497075573153004
Big Rat: -37/112
*/

package

1
2
3
4
import "github.com/spf13/cobra" // normal import
import mycobra "github.com/spf13/cobra" // import as mycobra
import _ "github.com/spf13/cobra" // init function of cobra called
import _ "image/png" // register PNG decode

模块替换,本地开发时使用:

1
replace example.com/modulename => ../localmodule

模块初始化:

  • Package-level variables are initialized in declaration order, but after any of the variables they depend on.
  • Initialization of variables declared in multiple files is done in lexical file name order. Variables declared in the first file are declared before any of the variables declared in the second file.
  • Initialization cycles are not allowed.
  • Dependency analysis is performed per package; only references referring to variables, functions, and methods declared in the current package are considered.

Directory and file names that begin with “.” or “_” are ignored by the go tool, as are directories named “testdata”.

struct

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 inner struct {
in1 int
in2 int
}

type outer struct {
inner // anonymous field
out1 int
out2 int
}

outerValue := outer{inner{1, 2}, 3, 4}

func (a *inner) ToString() string {
return "inner tostring"
}

func (a inner) ToString2() string {
return "inner tostring"
}

type typeAlias float64
func (t typeAlias) Foo() {
// do something
}

var value float64 = 0.0
value.Foo() // panic

var value2 typeAlias = 0.0
value2.Foo() // ok

interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Shaper interface {
Area() float32
}

type Square struct {
side float32
}

func (sq *Square) Area() float32 {
return sq.side * sq.side
}

if v, ok := value.(T); ok {
// value is type T
}

switch t := arg.(type) {
case *Square:
fmt.Println("is square")
case *Circle:
fmt.Println("is circle")
default:
fmt.Printf("unexpected type %T\n", t)
}

Golang方法集: (https://www.topgoer.com/%E6%96%B9%E6%B3%95/%E6%96%B9%E6%B3%95%E9%9B%86.html)

  • 类型 T 方法集包含全部 receiver T 方法。
  • 类型 *T 方法集包含全部 receiver T + *T 方法。
  • 如类型 S 包含匿名字段 T,则 S 和 *S 方法集包含 T 方法。
  • 如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法。
  • 不管嵌入 T 或 T,S 方法集总是包含 T + *T 方法。

Reflect

1
2
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

Read & Write

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

var (
firstName, lastName, s string
i int
f float32
input = "56.12 / 5212 / Go"
format = "%f / %d / %s"
)

func main() {
fmt.Println("Please enter your full name: ")
fmt.Scanln(&firstName, &lastName)
// fmt.Scanf("%s %s", &firstName, &lastName)
fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
fmt.Sscanf(input, format, &f, &i, &s)
fmt.Println("From the string we read: ", f, i, s)
// 输出结果: From the string we read: 56.12 5212 Go
}
1
outputFile, outputError := os.OpenFile(“output.dat”, os.O_WRONLY|os.O_CREATE, 0666)

panic

调用panic会立即中止当前函数的执行,并开始寻找注册的defer处理函数,按照FILO的顺序调用所有defer代码,结束后将冒泡执行上层的defer,一直到调用栈最顶层。

defer里可以捕获当前panic异常,处理将调用恢复到正常状态。
defer还可以修改named return value的值,并正常返回。

跨协程的defer是没有效果的,在子协程里触发panic,只能触发自己协程内的defer,不会触发main协程里的defer函数的。

主动调用os.Exit(int)退出进程时,defer将不再被执行。

1
2
3
4
5
func foo() {
session := createSession()
defer session.Close()
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
defer func() {
if err := recover(); err != nil {
log("unexpected error: %v", err)
}
}()

func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}

->
3
2
1

The deferred call’s arguments are evaluated immediately, even though the function call is not executed until the surrounding function returns.

Goroutine & Channel

Go协程(goroutine)是并发并行的,并不是一般是半开协程。协程使用通道(channel)来与其他协程或者宿主交换信息。

unbuffered chain,阻塞通道,是一种同步通信,无缓冲。发送方调用后会阻塞当前函数,直到消息被接收方取走。接收方调用时也会阻塞,直到成功取出数据。

1
2
ch := make(chan any)
ch := make(chan any, 0)

buffered chain,缓冲通道,是一种半开异步通信,发送方将数据放进缓冲区,如果缓冲区能成功放入数据,则发送方将立即返回并继续当前函数。如果缓冲区已满,发送方将阻塞直到有可用的缓冲区放入数据。

1
2
3
4
5
6
7
8
9
buffer_length := 100
ch := make(chan any, buffer_length)

select {
case resp := <-ch
// data pushed into ch, or ch closed.
case <-time.After(timeoutNs)
// timeout
}

Tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr,omitempty"`
}

// 三种获取 field
field := reflect.TypeOf(obj).FieldByName("Name")
field := reflect.ValueOf(obj).Type().Field(i) // i 表示第几个字段
field := reflect.ValueOf(&obj).Elem().Type().Field(i) // i 表示第几个字段

// 获取 Tag
tag := field.Tag

// 获取键值对
labelValue := tag.Get("label")
labelValue,ok := tag.Lookup("label")

Mod & Vendor

1
2
3
4
5
6
7
go mod vendor
go mod tidy
go mod get
go mod graph |grep whatever
go mod why |grep whatever

env GOSUMDB=off go get mypkg.in/my-package

vendor匹配顺序:

  • ./vendor
  • $GOROOT/src
  • $GOPATH/src

Context

Context是线程安全的,可以在多个goroutine中传递使用。并且Context是树状结构,一个父节点的ContextCancel时,其所有子节点的Context都会被Cancel。可以使用在同步控制一组协程的运行状态。比如多个长耗时的协程,可以共用一个Context,用来在一个条件不满足时取消所有其他协程的运行。

1
2
3
4
5
6
context.Background()
context.TODO()

WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithValue(parent Context, key, val any) Context

internal

导出路径包含internal关键字的包,只允许internal的父级目录及父级目录的子包导入,其它包无法导入。

杂项

The remainder operator can give negative answers if the dividend is negative: if n is an odd negative number, n % 2 equals -1. (golang only)

1
2
3
4
5
value := -9
fmt.Println(value % 2)

# output:
-1

There is no mixing of numeric types in Go. You can only multiply a time.Duration with

  • another time.Duration, or
  • an untyped integer constant.