Code Reference

gotemplate.go:

package main

import (
    "fmt"
)

const c = "C"

var v int = 5

type T struct{}

func init() {
    // initialization of package
}

func main() {
    var a int
    Func1()
    // ...
    fmt.Println(a)
}

func (t T) Method1() {
    // ...
}

func Func1() { // exported function Func1
    // ...
}

变量与常量

Assignment

// declare and init
var s1 string = "hello"
var s2 = "world" // 简写,有初始值时可省略类型
s3 := "hello world" // 简写
var i1, i2 int = 1, 2 // 同时声明多个变量
var i3, s4 = 1, "2" // 有初始值时可省略类型
var i4 = 4
i5 := 5 // := 结构不能在函数外使用

// declare without initiation
var s0 string // s0 = ""
var i0 int // i0 = 0
var b0 bool // b0 = false

Constants

const s string = "constant"
const n = 500000000 // 数值常量可以是任意精度,没有类型
const d = 3e20 / n
// 常量不能用 := 语法声明

Enum

常量还可以用作枚举:

const (
	Unknown = 0
	Female = 1
	Male = 2
)

const (
	a = iota // 0
	b        // 1
	c        // 2
)

const (
	_           = iota             // 使用 _ 忽略不需要的 iota
	KB = 1 << (10 * iota)          // 1 << (10*1)
	MB                             // 1 << (10*2)
	GB                             // 1 << (10*3)
)

基本类型

  • bool
  • string
  • Integers
    • ints
      • int:在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽
      • int8
      • int16
      • int32/rune:表示一个 Unicode 码点
      • Int64
    • uints
      • uint:在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽
      • uint8/byte
      • uint16
      • uint32
      • uint64
      • uintptr:在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽
  • Floats
    • float32
    • float64
  • Complex
    • complex64
    • complex128

零值

  • 数值类型为 0
  • 布尔类型为 false
  • 字符串为 ""(空字符串)

类型转换

i := 42
f := float64(i)
u := uint(f)

// int to string
s := strconv.Itoa(i)

// string to int
i1 := strconv.Atoi(s)
i2 := strconv.Parseint(s, 10)

Value type v.s. reference types

Value types, use new(T) returns type *T

  • strings (immutable)
  • array (mutable)
  • struct (mutable)

Reference types, use make(T) returns type T

  • func
  • slice
  • map
  • channel

Branches

循环

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

// while
i := 1
for i <= 3 {
    fmt.Println(i)
    i = i + 1
}

// while true
for {
    fmt.Println("loop")
    break
}

If/Else/Switch

if num := 9; num < 0 {
    fmt.Println(num, "is negative")
} else if num < 10 {
    fmt.Println(num, "has 1 digit")
} else {
    fmt.Println(num, "has multiple digits")
}

// 带表达式的switch
switch i {
case 1:
    fmt.Println("one")
case 2:
    fmt.Println("two")
case 3:
    fmt.Println("three")
}

// 不带表达式的switch
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.")
}

// switch type
whatAmI := func(i interface{}) {
    switch t := i.(type) {
    case bool:
        fmt.Println("I'm a bool")
    case int:
        fmt.Println("I'm an int")
    default:
        fmt.Printf("Don't know type %T\n", t)
  	}
}

Array/Slice/Map

Array

var a [5]int
b := [5]int{1, 2, 3, 4, 5}

var twoD [2][3]int
for i := 0; i < 2; i++ {
    for j := 0; j < 3; j++ {
        twoD[i][j] = i + j
    }
}

Slice

s1 := make([]string, 3) // len(s1) = 3
s1 := make([]string, 3, 5) // cap(s1) = 5
s2 := []string{}
s3 := []string{"a", "b", "c"}

// declare
var s []int               // s = nil
// 大部分场景 nil slice 可以直接使用,例如 append
// 但是在 json marshal 时需要注意,nil slice 是 `null`,而 empty slice 是 `[]`

// 初始化长度为0的slice
var s = []int{}           // s != nil
var s = make([]int, 0)    // s != nil
s := make([]int, 0)       // s != nil
s := []int{}              // s != nil

// append
s = append(s, 1)  // nil切片可以直接append
s = append(s, 2, 3)
s2 := []int{4,5,6}
s = append(s, s2...)

// copy
c := make([]int, len(s))
copy(c, s)
c0 := []int{}
copy(c0, s) // 当len(c) < len(s)时会自动加长

// 2D slice
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
    innerLen := i + 1
    twoD[i] = make([]int, innerLen)
    for j := 0; j < innerLen; j++ {
        twoD[i][j] = i + j
    }
}

// 遍历
for index, value := range s {
    fmt.Println(index, ": ", value)
}

切片实际是一个指向潜在数组的指针,当切片作为参数传递时,不需要解引用切片

func findBiggest( listOfNumbers []int ) int {}

// DO NOT
func findBiggest( listOfNumbers *[]int ) int {}

Map

// declare
var m map[string]int      // m = nil,不能添加key

// 初始化空map
m := make(map[string]int) // m != nil
var m = map[string]int{}  // m != nil
m := map[string]int{}     // m != nil

// 添加item
m := map[string]int{
  "a": 1,
  "b": 2,
}
m["c"] = 3

// 删除item
delete(m, "b")

// 取值
value := m["a"]

// has key
_, hasKey := m["d"]
value := m["d"] // 不存在,返回0值

// 遍历
for key, value := range m {
    fmt.Println(key, ": ", value)
}

// it is safe to delete item in loop for map
for key := range m {
    delete(m, key)
}

Strings

Strings Package

import (
    "fmt"
    s "strings"
)

var p = fmt.Println

func main() 
    p("Contains:  ", s.Contains("test", "es"))
    p("Count:     ", s.Count("test", "t"))
    p("HasPrefix: ", s.HasPrefix("test", "te"))
    p("HasSuffix: ", s.HasSuffix("test", "st"))
    p("Index:     ", s.Index("test", "e"))
    p("Join:      ", s.Join([]string{"a", "b"}, "-"))
    p("Repeat:    ", s.Repeat("a", 5))
    p("Replace:   ", s.Replace("foo", "o", "0", -1))
    p("Replace:   ", s.Replace("foo", "o", "0", 1))
    p("Split:     ", s.Split("a-b-c-d-e", "-"))
    p("ToLower:   ", s.ToLower("TEST"))
    p("ToUpper:   ", s.ToUpper("test"))
    p("Len: ", len("hello"))
    p("Char:", "hello"[1])
}

String Iteration

s := "my世界!"
fmt.Printf("%v\n", len(s)) // 8, 计算的是bytes数,一个字母1个byte,一个汉字站3个bytes
for idxByte, codePoint := range s {
    fmt.Printf("%v\n", idxByte) // 各个字符的起始byte位置
    fmt.Printf("Type: %T, Value: %v\n", codePoint, codePoint) // int32/rune类型
}

String Formatting

type point struct {
    x, y int
}

p := point{1, 2}

// %v 打印值
fmt.Printf("struct1: %v\n", p)
// %+v 打印键、值
fmt.Printf("struct2: %+v\n", p)
// %#v 打印结构体名、键、值
fmt.Printf("struct2: %#v\n", p)


// %T 打印类型
fmt.Printf("type: %T\n", p)

// %d 打印整数
fmt.Printf("int: %d\n", 123)
// %b 打印整数的二进制表示
fmt.Printf("bin: %b\n", 14)
// %x 打印整数的十六进制表示
fmt.Printf("hex: %x\n", 456)
// %c 打印整数对应的字符
fmt.Printf("char: %c\n", 33)

// %f 打印浮点数
fmt.Printf("float1: %f\n", 78.9)
fmt.Printf("float1: %.2f\n", 78.9) // 显示两位小数
// %e 打印科学记数法
fmt.Printf("float2: %e\n", 123400000.0)

// %s 打印字符串
fmt.Printf("str1: %s\n", "\"string\"")
// %q 打印字符串,不转义
fmt.Printf("str1: %s\n", "\"string\"")
// %x 打印字符串,以十六进制表示
fmt.Printf("str1: %s\n", "hello世界")

// %p 打印指针
fmt.Printf("pointer: %p\n", &p)

// 在%后加数字指定宽度
fmt.Printf("width1: |%6d|%6d|\n", 12, 345)

// 在%后加.指定小数位数
fmt.Printf("width2: |%6.2f|%6.2f|\n", 1.2, 3.45)

// 在%后加-左对齐
fmt.Printf("width5: |%-6s|%-6s|\n", "foo", "b")

// 格式化但不打印到os.Stdout
s := fmt.Sprintf("sprintf: a %s", "string")

// 格式化并输出到其他输出流
fmt.Fprintf(os.Stderr, "io: an %s\n", "error")

Functions

func plus(a int, b int) int {
    return a + b
}

func sumAndAvg(a, b float64) (float64, float64) {
	  return a + b, (a + b) / 2.
}
sum, avg := sumAndAvg(1, 3)


// 不定参数的函数(Variadic Functions)
func printItems(items ...interface{}) {
	  for _, item := range items {
		    fmt.Printf("Type: %T, Value: %+v\n", item, item)
	  }
}
printItems(1, 3, 123., "dqwdqw")
params := []interface{}{1, 3, "hello"}
printItems(params...) // 将每个元素当成参数传入函数
printItems(params) // 将整个slice当成一个参数传入函数

// 不定长参数要放最后一个
func printItems(name string, items ...interface{}) {}

// 匿名函数
f := func(x int) int {
    return x + 1
}

// 闭包
func intSeq() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}
nextInt := intSeq()

Pointers

// 指针可以修改外部变量的值
func zeroptr(iptr *int) {
    *iptr = 0
}
i := 1
zeroptr(&i)

Structs

type Person struct {
    name string
    age  int
}

// two ways to init

// init with `new` keyword
// get a pointer to the struct, with all 
p1 := new(Person) // <==> p1 := &Person{}

// init with struct literal
p := Person{
    name: "Tom",
    age: 12,
}

Composite struct

type Address struct {
    Street string
    City string
    ZipCode string
}

type Person struct {
    Name string
    Age int
    Address
}

person := Person{
    Name: "John Doe",
    Age: 30,
    Address: Address{
        Street: "123 Main St",
        City: "New York",
        ZipCode: "10001",
    }
}

fmt.Printf("person: %+v\n", person)
fmt.Printf("person.City: %+v\n", person.City) // access nested field
fmt.Printf("person.Address.City: %+v\n", person.Address.City)

Methods

type rect struct {
    width int
    height int
}

// 方法的 receiver 可以是类型或类型的指针
func (r rect) perim() int {
    return 2*r.width + 2*r.height
}
// <=>
func (r *rect) area() int {
    return r.width * r.height
}

// 本质是将 receiver 作为函数的第一个参数传入
func (r *rect) area() int {}
// <=>
func area(r *rect) int {}

// 调用时,无论是值接受还是指针接受都能直接用值调用
r := rect{3, 5}
fmt.Println(r.perim())
fmt.Println(r.area())
pr := &r
fmt.Println(pr.perim())
fmt.Println(pr.area())

// 指针接收者的方法可以修改接收者指向的值
// 避免在每次调用方法时复制该值
// 因此,当需要修改 receiver 的属性,或者避免复制时,应该使用 *T 作为 receiver
func (r *rect) scale(s int) int {
    r.width, r.height = r.width * s, r.height * s
}

Interfaces

type geometry interface {
    area() float64
    perim() float64
}

type rect struct {
    width, height float64
}
type circle struct {
    radius float64
}

func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}
    measure(r)
    measure(c)
  
    var g geometry
    g = r
    fmt.Printf("(%v, %T)\n", g, g) // 会打印interface底层类型和值
}

底层值为 nil 的接口值

type geometry interface {
    area() float64
    perim() float64
}
type rect struct {
    width, height float64
}
func (r *rect) area() float64 {
    if r == nil {
        fmt.Println("nil")
		    return 0
	  }
    return r.width * r.height
}

func main() {
    var g geometry
    fmt.Println(g.area()) // error, 没有底层类型的nil接口不能调用方法
    var r *rect
    fmt.Println(r.area()) // interface底层值可以是nil,这时也能调用方法,因此一般需要在方法内部处理nil
}

空接口

// 空接口可保存任何类型的值
func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}

// 类型断言,断言接口i的底层类型为T,并赋值给t。如果i的底层类型不是T,会触发一个panic
var i interface{} = "hello"
s := i.(string)

// 如果让成功,ok为true,否则为false
s, ok := i.(string)

// 类型选择
switch v := i.(type) {
case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
		fmt.Printf("I don't know about type %T!\n", v)
}

常用内建接口

// Stringer 是一个可以用字符串描述自己的类型
type Stringer interface {
    String() string
}

// error内建接口
type error interface {
    Error() string
}

// io.Reader 接口

Embedding

type base struct {
    num int
}
func (b base) describe() string {
    return fmt.Sprintf("base with num=%v", b.num)
}

// A container embeds a base. An embedding looks like a field without a name.
type container struct {
    base
    str string
}

func main() {
    // When creating structs with literals, 
    // we have to initialize the embedding explicitly; 
    // where the embedded type serves as the field name.
    co := container{
        base: base{
            num: 1,
        },
        str: "some name",
    }
  
    // We can access the base’s fields directly on co, e.g. co.num.
    fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)
    // Alternatively, we can spell out the full path using the embedded type name.
    fmt.Println("also num:", co.base.num)
    // Since container embeds base, 
    // the methods of base also become methods of a container. 
    // Here we invoke a method that was embedded from base directly on co.
    fmt.Println("describe:", co.describe())
  
    type describer interface {
        describe() string
    }
    // Embedding structs with methods may be used to bestow interface implementations onto other structs. 
    // Here we see that a container now implements the describer interface because it embeds base.
    var d describer = co
    fmt.Println("describer:", d.describe())

Errors

在 go 中,错误是值,通过判断错误值来处理错误

// 定义新的错误值

import "errors"

var MyError = errors.New("permission denied")

函数一般把err放在最后一位返回

func divide(a, b int) (int, error) {
    res := 0
    if b == 0 {
        return res, errors.New("divide by zero") // errors.New创建错误
    } else {
        return a / b, nil
    }
}

透明错误处理模式:不关心具体错误值,只关心是否有错误

res, err := divide(3, 0)
if err != nil {
    fmt.Printf("Error: %v\n", err)
}
fmt.Printf("Answer: %v\n", res)

// or
if res, err := divide(3, 0); err != nil {
    fmt.Printf("Error: %v\n", err)
}

哨兵模式,根据具体错误值分别处理,注意这里的错误值是 API 的一部分,发布后不能随意变动

var (
    ErrInvalideUnreadByte = errors.New("bufio: invalide use of UnreadByte")
    ErrBufferFull = errors.New("bufio: buffer full")
    // ...
)

data, err := b.Peek(1)
if err != nil {
    switch err {
    case bufio.BufferFull:
        return
    case bufio.ErrInvalideUnreadByte:
        return
    // ...
    default:
        return
    }
}

包装错误,Go 1.13 之后可以用 “%w” 包装错误

Working with Errors in Go 1.13

if err != nil {
    // Return an error which unwraps to err.
    return fmt.Errorf("decompress %v: %w", name, err)
}

检查错误,可以分别用 errors.Is 检查错误的值和 errors.As 检查被包装后的错误

// Similar to:
//   if err == ErrNotFound { … }
if errors.Is(err, ErrNotFound) {
    // something wasn't found
}

// Similar to:
//   if e, ok := err.(*QueryError); ok { … }
// Note: *QueryError is the type of the error.
if errors.As(err, &e) {
    // err is a *QueryError, and e is set to the error's value
}

Concurrency

Goroutine

  • 通过 go 关键字创建 goroutine
  • 如果 goroutine 没有执行完,会在主程序退出时(而非调用函数返回时)退出
  • 主程序不会等待所有 goroutine 执行完成,如果需要等待需要用 sync.WaitGroup
func spinner(delay time.Duration) {
	for {
		for _, r := range `-\|/` {
			time.Sleep((delay))
			fmt.Printf("\r%c", r)
		}
	}
}

func fib(x int) int {
	if x < 2 {
		return x
	}
	return fib(x-1) + fib(x-2)
}

func Run() {
	go spinner(100 * time.Millisecond)
	const n = 45
	fibN := fib(n)
	fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

Channel

Go 101 - Channel Use Cases

Sending to channel

c := make(chan string)
c <- "ping"

Receiving from channel

msg := <- c

Closing channel

Go 101 - How to Gracefully Close Channels

close(c)

One general principle of using Go channels is don’t close a channel from the receiver side and don’t close a channel if the channel has multiple concurrent senders. In other words, we should only close a channel in a sender goroutine if the sender is the only sender of the channel.

sync.WaitGroup

const N = 5
var values [N]int32

var wg sync.WaitGroup
wg.Add(N)
for i := 0; i < N; i++ {
    i := i
    go func() {
        values[i] = 50 + rand.Int31n(50)
        log.Println("Done:", i)
        wg.Done() // <=> wg.Add(-1)
    }()
}

wg.Wait()
// All elements are guaranteed to be initialized now.
log.Println("values:", values)

sync.Once

log.SetFlags(0)

x := 0
doSomething := func() {
	x++
	log.Println("Hello")
}

var wg sync.WaitGroup
var once sync.Once
for i := 0; i < 5; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		once.Do(doSomething)
		log.Println("world!")
	}()
}

wg.Wait()
log.Println("x =", x) // x = 1

sync.Mutex

sync.Mutex: 在 Locked 状态调用 Lock() 会 block

type Counter struct {
	m sync.Mutex
	n uint64
}

func (c *Counter) Value() uint64 {
	c.m.Lock()
	defer c.m.Unlock()
	return c.n
}

func (c *Counter) Increase(delta uint64) {
	c.m.Lock()
	c.n += delta
	c.m.Unlock()
}

sync.RWMutex:用于多读少写的场景

  • 分别有 writerLock 和 readerLock 两把锁
  • Lock() 会 block 如果任何一把锁处于 Locked 状态
  • 如果 writerLock 处于 Locked 状态,RLock()Lock()都会 block
  • 如果 readerLock 处于 Locked 状态,不会 block RLock() ,只会 block Lock()
  • readerLock 可以被多次 RLock() ,解锁需要调用相同次数 RUnlock()
type Counter struct {
	//m sync.Mutex
	m sync.RWMutex
	n uint64
}

func (c *Counter) Value() uint64 {
	//c.m.Lock()
	//defer c.m.Unlock()
	c.m.RLock()
	defer c.m.RUnlock()
	return c.n
}

sync.Cond

用于保护用作条件的变量

  • 用一个 sync.Locker 初始化,通常为 *sync.Mutex
  • 调用 cond.Wait() 等待某个条件
  • 调用 cond.Signal() 通知一个等待中的 goroutine 条件成立
  • 调用 cond.Broadcast() 通知所有等待中的 goroutine 条件成立
  • 调用上述方法前后需要加锁解锁 cond.L.Lock()/cond.L.Unlock()
const N = 10
var values [N]string

cond := sync.NewCond(&sync.Mutex{})

for i := 0; i < N; i++ {
	d := time.Second * time.Duration(rand.Intn(10)) / 10
	go func(i int) {
		time.Sleep(d) // simulate a workload

		cond.L.Lock()
		values[i] = string('a' + i)
		// notifying
		cond.Broadcast()
		cond.L.Unlock()
        
	}(i)
}

checkCondition := func() bool {
	fmt.Println(values)
	for i := 0; i < N; i++ {
		if values[i] == "" {
			return false
		}
	}
	return true
}

cond.L.Lock()
defer cond.L.Unlock()
for !checkCondition() 
	cond.Wait()
}

Race condition

Data Race Detector

test and avoid race condition

go run -race main.go

Generic

类型约束:类型约束是一个或多个类型的组合,用于约束具体的实参类型

go 内置了常用的类型约束

  • any: 代表 go 里面所有的内置类型,等价于 interface {}
  • comparable: 代表go里面内置的可比较类型:int、uint、float、bool、struct、指针等一切可比较类型
  • ~: 表示衍生类型,例如 ~int 表示所有底层类型为 int 的类型,例如 type MyInt int

也可以自定义类型约束

type MyNumber interface {
	int | float32
}

// slice
type MySlice [T ~int | ~float32] []T

// ptr
type MyPtr[T int | int32] *T

// map
type MyMap[K comparable, V any] map[K]V

// chan
type MyChannel[T int | int32] chan T

// interface
type MyInterface[T int | int32] interface {
    Val() T
}

自定义类型约束时

// 可以使用类型组合
type MySlice[T int | float32] []T
// 也可以使用前面定义的类型约束的名称
type MySlice[T Number] []T
// 或者使用内置类型约束
type MySlice[T any] []T
type MySlice[T comparable] []T
 []T
  • T 表示类型形参,是个占位符,int | float32 类型实参表示具体的类型约束
  • [] 方括号中的为类型形参列表
  • 定义的泛型为 MySlice[T]

实例化泛型

var a MySlice[int] = []int{1, 2, 3}
var b MySlice[float32] = []int{1.0, 2.0}

定义泛型函数

func Add[T int | int32 | float64 | string] (a, b T) T {
    return a + b
}

// 可以传入满足类型约束的实参
Add(2, 3)
Add("hello", "world")

问题

// method 1
var (
    someMap      = map[string]string{}
    someMapMutex = sync.RWMutex{}
)

go func() {
    someMapMutex.Lock()
    someMap["key"] = "value"
    someMapMutex.Unlock()
}()

// method 2
var (
    someMap = syncmap.Map{}
)

go func() {
    someMap.Store("key", "value")
}()

v, ok := someMap.Load("key")
if !ok {
    fmt.Println("key missing")
    return
}
fmt.Println(v)

参考