速通 Go 语言基础
马上就退役了,要为下一步做点准备了。先速通一下 GO 语言基础,本文章参考了 GO TOUR 和字节的青训营课程,通过列出一些代码来快速上手 GO 语言。
阅读可能要需要具有其他语言的编程经验
语法
hello,world
1 | package main |
程序总是从 main 包下的 main 函数开始执行
import 语句可以导入包,除了使用 import
语言单个单个导入外,还可以使用分组导入
1 | import ( |
函数
1 | func add(x int, y int) int { |
变量
基本类型有
bool
string
int
int8
int16
int32
int64
uint
uint8
uint16
uint32
uint64
uintptr
byte
(uint8
的别名)rune
(int32
的别名,表示一个 Unicode 码点)float32
float64
complex64
complex128
int
, uint
和 uintptr
在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽
1 | package mainjj |
常量
1 | const ( |
控制流
判断
1 | package main |
分支
1 | package main |
不带条件的 switch
1 | package main |
循环
1 | package main |
推迟
使用 defer
会将后面的语句放在当前作用域结束时执行
执行顺序如栈,总是先进后执行
1 | package main |
输出信息
3
2
1
指针
1 | package main |
结构体
1 | type Vertex struct { |
数组
1 | var a [10] int |
切片
1 | var a [10]int |
容量与长度
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s
的长度和容量可通过表达式 len(s)
和 cap(s)
来获取。
切片的零值是 nil
。
nil 切片的长度和容量为 0 且没有底层数组
用 make 创建切片
切片可以用内建函数 make
来创建,这也是你创建动态数组的方式。
make
函数会分配一个元素为零值的数组并返回一个引用了它的切片:
1 | a := make([]int, 5) // len(a)=5 |
要指定它的容量,需向 make
传入第三个参数:
1 | b := make([]int, 0, 5) // len(b)=0, cap(b)=5 |
操作
append
1 | func append(s []T, vs ...T) []T |
range
1 | for i, v := range pow { |
映射
1 | var m map[string]Vertex |
删除元素:
1 | delete(m, key) |
通过双赋值检测某个键是否存在:
若 key
在 m
中,ok
为 true
;否则,ok
为 false
。
1 | elem, ok = m[key] |
方法
1 | type Vertex struct { |
指针接收者的方法可以修改接收者指向的值(就像 Scale
在这做的)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。
1 | func (v *Vertex) Scale(f float64) { |
接口
1 | type Abser interface { |
类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。
隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。
空接口
指定了零个方法的接口值被称为 空接口:
1 | interface{} |
空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
空接口被用来处理未知类型的值。
类型断言
类型断言提供了访问接口值底层具体值的方式。
1 | t := i.(T) |
若 i
并未保存 T
类型的值,该语句就会触发一个恐慌。
为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。
1 | t, ok := i.(T) |
若 i
保存了一个 T
,那么 t
将会是其底层值,而 ok
为 true
。
类型选择
1 | switch v := i.(type) { |
Stringer
同 ToString()
1 | type Stringer interface { |
error
1 | type error interface { |
泛型
1 | type Slice[T int|float32|float64 ] []T |
1 | var a Slice[int] = []int{1, 2, 3} |
匿名结构体不支持泛型
接口组合
1 | type Int interface { |
底层类型
使用 ~
标明
1 | type Int interface { |
并发
启动协程
1 | package main |
Channel
Channel 本身是并发安全的
1 | package main |
带缓冲
如果 Channel 没用缓冲,那么在 Channel 放入数据后,下一次读取线,放入数据是堵塞的
1 | package main |
close
发送者可通过 close
关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完
1 | v, ok := <-ch |
之后 ok
会被设置为 false
。
只有发送者才能关闭 Channel,关闭后将不可再发送数据。Channel 一般情况下不需要关闭
range
循环 for i := range c
会不断从信道接收值,直到它被关闭。
select
select
语句使一个 Go 程可以等待多个通信操作。
select
会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
当 select
中的其它分支都没有准备好时,default
分支就会执行。为了在尝试发送或者接收时不发生阻塞,可使用 default
分支:
1 | func fibonacci(c, quit chan int) { |
Mutex
Go 标准库中提供了 sync.Mutex
互斥锁类型及其两个方法:
Lock
Unlock
WaitGroup
WaitGroup 可以通过Add(int)
的方式添加任务,在所有任务调用 Done()
之前,调用 Wait()
函数都会导致堵塞
依赖管理
目标:不同环境项目的依赖库版本不同,需要控制依赖库的版本
$GOPATH (过时)
即环境变量 $GOPATH
。 在该情况下项目将直接依赖 $GOPATH/src
下的代码
在该 PATH
下的文件结构如下:
其中项目代码和项目依赖代代码都将存放在 $GOPATH/src
目录下
通过 go get
可以将最新版本的依赖代码下载到 src
下
缺点
无法实现多版本控制,即出现项目A和项目B分别依赖Pkg的不同版本时无法进行控制,即使这两个项目毫无关系
Vendor (过时)
在项目目录下增加 vendor
文件夹,所有的依赖包以源码
副本的形式存储在 vendor
下。通过为每个项目引入一份依赖的副本,解决了多个项目需要同一个 package 不同版本依赖的冲突问题
编译器在 solve deps 时会首先寻找 vendor
目录,在不存在的情况下再去查找 $GOPATH
缺点
由于本质上仍是依赖源码,在存储源码副本时如果项目依赖同一个包的不同的版本号,仍然可能会导致编译出错
Go Module
自 1.16 版本开始默认开启
通过
go.mod
文件管理依赖包通过
go get/go mod
指令工具管理依赖包
go.mod
文件并不是在项目中唯一。如果一个包需要被单独引用,它就应该创建一个 go.mod
文件。其内容如下
1 | module example/project/app |
对于 require 中的内容应该包含包路径和版本号
如果版本号的 MARJOR 大于等于2,那么路径中应该增加/vN
后缀
如果没有 go.mod
文件且 MARJOR 大于等于2,则需要标记为 +incompatible
版本号
版本号可以有两种填写方式
语义化版本: ${MARJOR}.${MINOR}.${PATCH}
基于 commit :vx.0.0-yyyymmddhhmmss-c38fb59326b7
基与 commit 的版本号在每次提交时都会自动生成版本号
直接依赖/间接依赖
如果不是直接依赖的包,GO 语言会通过 // indirect
标记为间接依赖
依赖选择规则
GO 语言会选择满足项目要求的最低的兼容版本
如图,最终编译时选择的 C 版本将为 1.4。以为 C1.3和C1.4的MARJOR版本相同,认为这两个版本兼容
依赖分发
GO 语言的依赖并不是从 GitHub 等站点直接下载,而是采用了一种类似适配器模式的设计
对 Proxy 的选择可以设置 GOPROXY
环境变量,本质是URL列表,以逗号分隔。其中 direct
表示源站(Github,SVN 等)
1 | export GOPROXY="https://proxy1.cn, https://proxy2.cn, proxy" |
在查找时会依次从第一项开始查询
go mod 工具
单元测试
测试文件以 _test.go
结尾,函数声明类似 func TestABC(*testing.T)
。对于所有测试的资源初始化和释放放在 TestMain
中
1 | import "githhub.com/stretchr/testify/assert" |
通过 go test [flag] [package]
可以执行所有单元测试
覆盖率可通过提供 --cover
flag 计算
1 | $ go test |
基准测试
1 | func BenchmarkSelect(b *testing.B){ |
运行方式
1 | $ go test -bench=. |