Preface
Let’s GO
Learning go basics follow steps offered by: https://golangbot.com/learn-golang-series/
Wish me good luck
CheckList
Introduction
✅1 - Introduction and Installation
✅2 - Hello World
Variables, Types and Constants
✅3 - Variables
✅4 - Types
✅5 - Constants
Functions and Packages
✅6 - Functions
✅7 - Packages
Conditional Statements and Loops
✅8 - if else statement
✅9 - Loops
✅10 - Switch Statement
Arrays, Slices and Variadic Functions
✅11 - Arrays and Slices
✅12 - Variadic Functions
More types
✅13 - Maps
⛷[skip]14 - Strings
Pointers, Structures and Methods
✅15 - Pointers
✅16 - Structures
✅17 - Methods
Interfaces
✅18 - Interfaces - I
✅19 - Interfaces - II
Concurrency
✅20 - Introduction to Concurrency
✅21 - Goroutines
✅22 - Channels
✅23 - Buffered Channels and Worker Pools
✅24 - Select
✅25 - Mutex
Object Oriented Programming
✅26 - Structs Instead of Classes
✅27 - Composition Instead of Inheritance
✅28 - Polymorphism
Defer and Error Handling
✅29 - Defer
✅30 - Error Handling
✅31 - Custom Errors
32 - Panic and Recover
First Class Functions
33 - First Class Functions
Reflection
34 - Reflection
Filehandling
35 - Reading Files
36 - Writing Files
变量
变量声明
常规式
1 | var ( |
Ninja 式
1 | myVar1, myVar2 := "value1", "value2" |
* =
是赋值 / :=
是声明变量并赋值, 所以使用 :=
要求左边变量至少有一个是新声明的
类型
类型概览
bool
Numeric Types
- int8, int16, int32, int64, int
- uint8, uint16, uint32, uint64, uint
- float32, float64
- complex64, complex128
- byte // alias to uint8
- rune // alias to int32
string
类型要点
* int 表示 32bit / 64bit integer, 取决于操作系统是 32bit / 64bit, 大多数时候应该使用它, 而不直接指定int32/int64
* 复数声明方式
- var cplx = complex(10, 20)
- var cplx = 10 + 20i
实部和虚部是32bit, 结果是64bit
实部和虚部是64bit, 结果是128bit
* different types are not allowed to compute together (this part is not like shitty ‘typed’ js)
* const 声明的变量不能被重新赋值,而且类型需要在编译器可确定,如 const a = math.Sqrt(2) 是不允许的, 因为 math.Sqrt在运行时才会确定
函数
函数声明起手式
1 | func fnName (p1, p2 int) int { |
go的函数和很多语言不一样,可以返回多个值
1 | func fnName (p1, p2 int) (int, int) { |
也可以不显式return, 而是在返回值类型定义的地方指明返回的变量名(相当于已经在返回值类型定义的地方声明了变量)
1 | func fnName (p1, p2 int) (sq1, sq2 int) { |
Package
* package 里需要暴露出去的变量都以大写开头, 不以大写开头的从外部无法访问
初始化
Package允许带一个 init 函数
1 | func init () { |
这个函数在 Package 被初始化的时候调用,即便多次 import, 也只会初始化一次
初始化函数不带任何参数,也不会有返回类型(当然了! 模块得保持中立,自然不存在参数化)
if-statement
other language:
1 |
|
golang has another extra statement
to go, which u can do some init work, or preparation (u can do similar thing outside of if-statement, but its kinda inelegant)
1 | if statement; condition { |
else-statement has to stay on same line with last close-braces and next start-braces
1 | // good to go |
Loop
* similar to most other languages …
* label: like go-to statement of loop, get out of inner-most loop
* infinite loop:
1 | for { |
Switch
* similar to most other languages …
switch expression is OPTIONAL
1 | switch statement { |
* fallthrough, like a pass … (传球), it MUST be last statement of a CASE
1 | switch age { |
Array
Declaration
1 | // ↓ length of arr |
* [3]int and [5]int are distinct types
value-type
* Array in GO is 🚨 VALUE-TYPE, assignment of array will result in value copy
so *swap* shit wont happen ..
1 |
|
iteration
go provide range
to make iterating more concise
1 | for idx, value := range arr { |
Slices
Slice is a data interface of Array, any change to slice will reflected on origin arrary
slice / array desctructing -> array...
( a bit weird for javascript developer …)
* 🤩 change to slice will reflect on original array, so if u pass a slice as parameter to a func, any change to this slice inside func will also reflect on original array
* use copy
to make a copy of slice, so underlying array can be GC
Map
Basic syntax / usage
1 | salary := map[string]float64 { |
access / set data:
just like javascript …
1 | v := salary["Jobs"] |
EXT REF: zero-value for types:
type | zero-value |
---|---|
numeric | 0 |
bool | false |
string | “” |
*pointer | nil |
iteration
pretty much like iteration of Array (use range func)
with FOR RANGE flavor iteration, access order is NOT guaranteed
1 | for key, value := range(salary) { |
delete
just use delete(map, key)
func
1 | delete(salary, "Boss") // how DARE you to delete boss from salary table! |
facts about Map
* maps are ref-type instead of value-type
* == can only used to compare a map and nil
Pointer
Why and when need pointer
Why
Everything in go is passed by value, use pointer
- to make sure changes happened on original data object, not replica
- to reduce memory consumption when interact with complex data structure
When
reference a variable (get pointer)
1 | aNum := "123" |
deference variable (get value of pointed)
1 | aNum := "123" |
facts about Pointer
* avoid passing pointer of array into function, use slice instead (but WHY?)
* zero value of pointer
is nil
Structure
Structure just like interface in TS …
declaring
1 | // named structure |
creating
Similar to
initialize a instance
…
zero value
zero value of structure is a instance of zero-value of all members
1 | type Emp struct { |
access property
1 | emp3 := struct { |
pointer of structure
1 | pEmp3 := &emp3 |
both way to access field are supported, compiler thing ..
nested structure
ofc, like a json
promoted fields
1 | type Address struct { |
fields of Address can be access through Person, it’s like some kind of MERGE
Methods
method 也是 func, 但类似于JS中向原型添加方法
1 | func (r Rectangle) Area() int { |
(r Regtangle)
(在Go中成为接收器)标明Area方法可以定义在一个Rectangle结构上,r即Rectangle实例
WHY Method
- Go 没有真正意义上的类,通过实现 type 上的 method 来实现像类一样的行为归并
- 相同名字的 method 可以定义在不同的类型上,但如果通过 func 来做,没有办法实现相同签名、不同参数下的不同逻辑
Pointer Receiver
除了对可以对值类型定义 method 以外,还可以对指针定义 method
1 | func (r *Rectangle) changeArea() int { |
与定义在值类型上的 method 不同,定义在指针上的 method 对实例做的改变会反应回实例上
有时出于性能考虑的原因,为了避免包含大量属性的实例被拷贝传入 method, 我们会偏向于使用定义在指针类型上的 method 来避免参数拷贝
method for non-local type
method receiver 只能定义在当前包内的类型
例如为 int 定义 method 则会收到报错
解决方法为:为 int 类型创建当前包的 alias
1 | type myInt int |
接下来在使用 int 的地方都使用 myInt 代替,有点像在int之上用管道过滤器封了一层,myInt 接管了 int 的行为,并提供新的方法定义
Interface
在 go 中,使用接口本质上并不是像Java一样显式地定义和声明类实现了接口,而是通过定义类型的方式达到定义接口的目的
基本操作
1 | // interface |
除了 Cat / Dog 之外,还可以继续扩充新的实现 Animal 接口的动物类,而不需要改动 allMakeNoise 的业务代码
* 空接口: interface {}
类型断言
1 | func assert(i interface{}) { |
以上方法如果传入的不是显式调用的int值, 会导致 panic, 读取额外的状态2参数可以避免 panic
1 | func assert(i interface{}) { |
i.(int)
/ i.(string)
这样的用法还可以将实际类型参数化放到 switch 中使用
1 | func findType(i interface{}) { |
实现多个接口
对同一个 type 定义多个 method 来实现各自 interface 要求的 Method 即可
嵌套接口
1 | type SalaryCalculator interface { |
零值
interface 的零值是 nil.
协程 (Goroutines)
协程可以看做一种轻量级的线程
协程有点像js的异步时分复用,但不一样的是,如果有类似等待用户输入的事情发生,不会像js一样移交控制权等待回调,而是开启新线程执行别的 goroutines
上千个协程可能只会复用几个线程,有时候甚至只会用一个
基本操作
1 | func hello () { |
在方法前加 go 关键字来使用协程调用方法,hello 在被调起来之后,main 方法中接下来一行代码继续获得自己独立的控制权,hello 的返回值会被无视
* 主程(main)退出会导致其他所有协程都退出
Channel
零值为 nil
Channel 的类型定下来之后,只能在 Channel 内传输符合类型定义的数据类型,不能传输其他类型
init
1 | channelX = make(chan bool) |
初始化一个只能传输 bool 类型的 Channel
死锁 DeadLock
如果
- 一个协程向 channel 写入,但没有任何一个地方读取
- 一个地方读取了 channel, 但协程没有写入
会导致死锁
从 channel 不停读取数据
每次 channel 赋值都只会读取一次赋值的数据,如果 routine 不停在写数据,读取方需要循环来读取
1 | func floodData(dataChan chan<- int) { |
循环读取的for过程可以用 range 简化:
1 | for v := range sendch { |
channel 方向
channel 有方向性,通常我们使用 make(chan int) 构建一个双向 channel, 可以向这个 channel 写入数据,也可以用来读取数据
使用 make(chan<- int) 声明一个只能写入的 channel
*注: 通常应该是需要搭配双向 channel 类型转换使用,不然只能写入有什么用呢
双向 channel 在传入 goroutine 的时候可以转为单向 channel
关闭 channel
使用 close(xxChannel) 来关闭一个 channel
关闭后读取方会收到一个状态更新,状态使用第二个参数读取
1 | v, ok := <-someCh |
WaitGroup
有点类似JS里的 Promise.all()
Select
有点类似 JS 里的 Primise.race() …
Mutex (临界区)
共享变量在存在竞态的时候成为临界区,在操作临界区的时候需要用 Mutex 先锁上,保证只有一个协程在访问, 操作完毕后又解锁
1 | var mu sync.Mutex |
OOP
GO 没有真正意义上的Class,只有type
Go 通过一些其他手段实现类似 Class 的功能
如通过定义并暴露 func New(params)
来模拟 constructor
* Go 没有继承,只有组合
多态
类型通过实现 Interface 中声明的所有方法,来隐式声明实现 Interface
纵然 Go 没有父子类,但也可以像 Java 一样 (参考List接口),通过使用声明的接口来调用通用方法,达到多态的效果
Defer
defer 用来将某个函数调用推迟到 当前上下文 return 之前
如 WaitGroup,如果存在多个 return 语句,需要保证每次return之前都调用 WaitGroup的 Done 方法,否则会可能导致死锁,直接 defer Done 方法就可以只写一次该方法,并且能保证无论当前上下文以什么姿势 return, 该方法都会被调用
defer 的执行顺序是后进先出(栈结构)
Panic
runtime 遇到 panic 的时候,会立即停止执行接下来的代码,将所有 defer 全部执行出栈,然后返回e
从这个角度,panic 机制有点像 nodejs 的 process.exit(1)
recover
recover 只能恢复当前协程里的 panic
函数第一公民
Go 中函数也是第一公民,可以:
- 直接定义匿名函数并赋值给变量
- 将函数作为参数传递
- 函数声明完成后直接调用(JS 里的立即执行函数表达式IIFE)
- 定义高阶函数