golang 要点速记

用了半年 Rust 之后发现 go 的语法基础知识有点生疏了,于是把官方 tutorial 又翻看了一遍,把其中一些比较重要关键的点列举出来了方便以后回顾,后续有新的要点也会继续补充进来。

1. 闭包

// 闭包实现狄波拉切数列
package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	num1, num2 := 0, 1
	return func() int {
		defer func() {num1, num2 = num2, num1 + num2}()
		return num1
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

2. 方法

type P struct {
	x, y float64
}

// p called reciver(接收器)
func (p P) Print() {
	fmt.Println(p.x, p.y)
}
  • method 定义只能在 receiver 定义的 package 里面声明和定义
  • go 默认是值传递,所以 receiver 是 *TT 两种类型时是有区别的
  • method receiver 是 *T,go 解释器默认会自动转换成 pointer receiver 即 (&T).Method()
  • method receiver 是 T,go 解释器默认会自动转换成 value receiver 即 (*T).Method()
  • pointer receiver 有两个优势:1.修改 receiver 本身的值;2.避免 value copy 的开销
  • pointer receiver 和 value receiver 不要混用

3. interface

// interface is a set of method signatures
type ABC interface {
	Method_A() int
	Method_B() float64
	// ...
}

// any type which implement all of the methods can be the interface type
type A struct {
	A int
	B float64
}

func (A) Method_A() int {
	return A.A
}

func (A) Method_B() float64 {
	return A.B
}
  • interface{} 被称为 empty interface,可以指向任意类型
  • switch i.(type){} 做类型判断
  • t, ok := i.(T) 做某个类型判断

4. goroutine

func do_something() {
	time.Sleep(100 * time.Millisecond)
}

func main() {
	go do_something()

	do_something()
}

5. channel

package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}
  • chan 的 sender,receiver 默认会阻塞直到对端有值进出
  • make(chan int, 100) 称之为 buffer channel,只有 buffer 满的时候 sender 才会阻塞,同时只有 buffer 空的时候 receiver 才会阻塞
  • 只有 sender 可以 close
  • 发送内容给一个 close 的 channel 会触发 panic
  • v, ok := <-ch 可以检测 channel 是否 close
  • for i := range c 可以重复的从 channel c 中获取值直到 channel close
  • 一般而言 channel 不需要特意关闭(跟文件 open/close 不一样),除非有必要 receiver 要被告知 channel 关闭了

6. select

package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}
  • select 可以等待多个通信操作(channel)
  • select 一致阻塞直到某个 case 可以运行
  • 如果有多个 case 可以运行,select 会随机选择一个执行
  • default case 用于处理没有 case 可以运行的情况

7. Mutex

package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mu.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mu.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}
  • sync.Mutex 用来做资源访问互斥操作

8. 基本语法要点

  • go 代码都是由 package 构成,import package 进行依赖
  • go 通过首字母是否大写控制对外的可见性,称之为 exported names
  • func 参数简写:x int, y int --> x, y int
  • func 返回值可以有明明:func foo() (x,y int){...}
  • var 声明一个变量
  • := 也可以声明一个变量,但是 const 变量不可以用这种方式声明
  • go 类型强制转换方法:T(v)
  • go 循环控制只有一种 for
  • for 循环的除了判断条件其他都可以省略
  • while 循环实现:for sum < 100 // while loops
  • 死循环实现:for {}
  • if 条件判断可以在条件之前加一个简单的语句:if v := 0; v < 10 {}
  • switch-case 可以用来简化 if-else 语句:
// i ops
// simplify if-else statement with switch-case
switch {
case i == 1:
	// do something
case i == 2:
	// do something
case i > 3:
	// do something
default:
	// do somthing
}
  • go 的 switch 在执行完一个 case 之后会自动停止,所以不需要显示的添加 break
  • defer 会在函数 return 之后自动执行
  • defer 会按照 LIFO 的顺序执行(stack)
  • slice index 是一个左闭又开的区间(half-open range):
a[1:4] // 表示 a[1], a[2], a[3] 三个元素

References

  1. https://go.dev/tour/