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
int
s- int:在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽
- int8
- int16
- int32/rune:表示一个 Unicode 码点
- Int64
uint
s- 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” 包装错误
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
Sending to channel
c := make(chan string)
c <- "ping"
Receiving from channel
msg := <- c
Closing channel
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()
,只会 blockLock()
- 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
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")
问题
- slice的泛型
cannot use (type []int) as type []interface {}
- Map 并行写:Golang fatal error: concurrent map read and map write
- 在写入代码前后加锁
- 使用
syncmap.Map
// 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)