Go基础语法
Go特点
- 高性能, 高并发
- 丰富的标准库
- 完善的工具链
- 静态编译
- 快速编译
- 跨平台
- 垃圾回收
变量
与js类似,通过var,const声明
声明变量方式
var 变量名 = 值自动推导变量类型
var 变量名 变量类型 = 值规定类型
变量名 := 值
常量
将var改成const
常量没有确定的类型, 会根据使用的上下文自动确定类型
if else
与其他语言类似
for
// 死循环
for {
fmt.Println("123")
}
// 经典循环
for j := 7; j < 9; j++ {
fmt.Println(j)
}
continue, break 与c一样 for后不加条件是指死循环
switch case
不需要加break, 不会继续往下执行
// 不加变量直接写结构分支
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
数组
定义方式
var a [5]intb := [5]int{1, 2, 3, 4, 5}var twoD [2][3]int
切片
切片不同于数组, 是一个可变长度的数组 用make创建切片
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
s = append(s, "d")
// 第[l~r)个元素
fmt.Println(s[2:5]) // [c d e]
}
map
类似于java的hashmap
-
创建和初始化 map
m := make(map[string]int):创建一个空的 map,键为字符串类型,值为整数类型。m["one"] = 1和m["two"] = 2:向 map 中添加键值对。
-
打印 map
fmt.Println(m):输出整个 map 的内容,结果为map[one:1 two:2]。
-
获取 map 的长度
fmt.Println(len(m)):输出 map 的长度,结果为2。
-
获取指定键的值
fmt.Println(m["one"]):获取键为"one"的值,结果为1。fmt.Println(m["unknow"]):尝试获取不存在的键"unknow"的值,结果为0(默认值)。
-
检查键是否存在
r, ok := m["unknow"]:使用多重赋值检查键是否存在。r是键对应的值,ok是一个布尔值,表示键是否存在。fmt.Println(r, ok):输出0 false,表示键"unknow"不存在。
-
删除指定键的值
delete(m, "one"):从 map 中删除键为"one"的条目。
-
使用字面量创建 map
m2 := map[string]int{"one": 1, "two": 2}:使用字面量创建并初始化 map。var m3 = map[string]int{"one": 1, "two": 2}:使用var关键字和字面量创建并初始化 map。fmt.Println(m2, m3):输出两个 map 的内容,结果为map[one:1 two:2] map[one:1 two:2]。
range
-
定义和遍历切片
nums := []int{2, 3, 4}:定义一个包含三个整数的切片。sum := 0:初始化一个变量sum用于存储切片元素的总和。for i, num := range nums:使用range关键字遍历切片,i是索引,num是当前元素的值。sum += num:将当前元素的值累加到sum中。if num == 2:如果当前元素的值为2,则打印其索引和值。
fmt.Println(sum):输出切片元素的总和,结果为9。
-
定义和遍历 Map
m := map[string]string{"a": "A", "b": "B"}:定义一个字符串到字符串的 map。for k, v := range m:使用range关键字遍历 map,k是键,v是值。fmt.Println(k, v):打印每个键值对,输出顺序可能不固定,例如b B; a A。
for k := range m:使用range关键字遍历 map,只获取键。fmt.Println("key", k):打印每个键,输出顺序可能不固定,例如key a; key b。
注意事项
range关键字在遍历切片时返回索引和值,在遍历 map 时返回键和值。- 遍历 map 的顺序是不确定的,每次运行程序时输出的顺序可能会不同。
多线程
协程
协程: 用户态, 轻量级线程, 栈KB级别 协程: 内核态, 线程跑多个协程, 栈MB级别 使用go关键字来开启一个协程
通道
提倡通过通信共享内存, 而不是通过共享内存实现通信
make(chan 元素类型,[缓冲大小]
- 无缓冲通道(同步通道) make(chan int)
- 有缓冲通道 make(chan int,2)
func CalSquare() {
src := make(chan int)
dest := make(chan int, 1)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
time.Sleep(3000000000)
println(i)
}
}
并发安全 Lock
lock sync.Mutex 对于共享内存来说, 有可能出现并发安全问题, 所以要对其加锁
WaitGroup
使用time.sleep时并不能确定应当让线程暂停的时间, 从而实现线程同步
| Add(delta int) | 计数器+delta |
|---|---|
| Done() | 计数器-1 |
| Wait() | 阻塞知道计数器=0 |
| 计数器: 开启协程+1, 执行结束-1; 主协程阻塞知道计数器为0 |
func CalSquare() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
println(j)
}(i)
}
wg.Wait()
}
依赖管理
GOPATH->GO VENDOR -> GO Module
环境变量GOPATH
| |-- bin 项目编译的二进制文件 |-- pkg 项目编译的中间产物, 加速编译 |-- src 项目源码
- 项目源码直接依赖src下的代码
- go get 下载最新版本的包到src目录下 弊端
- 无法实现package的多版本控制
GO VENDOR
解决无法实现package的多版本控制的问题 | |-- Readme.md |-- dao |-- handler |-- mian.go |-- service |-- vendor
- 项目目录下增加vendor文件, 所有依赖包副本形式放在$ProjectRoot/vendor
- 依赖寻址方式: vendor -> GOPATH 通过每个项目引入一份依赖的副本, 解决了多个项目需要同一个package依赖冲突问题 弊端
- 无法控制依赖的版本
- 更新项目可能出现依赖冲突, 导致编译出错
GO Module
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod 指令工具管理依赖包 类比maven
module github.com/wangkechun/go-by-example 依赖管理基本单元
go 1.18 原生库
require ( 单元依赖
example/lib1 v1.0.2
)
version
语义化版本
${MAJOR(大版本)}${MINOR(新增函数/功能)}${PATCH(修改bug)}
V1.3.0
V2.3.0
基于commit的伪版本
vx.0.0-yyyymmddhhmmss-${12位hash码前缀}
- 主版本2+模块会在模块路径增加/vN 后缀
- 对于没用go.mod文件并且主版本2+的依赖, 会+incompatible go在遇到依赖了同一个模块的不同版本时, 会选择依赖最低的兼容版本
go get
go get example.org/pkg +
| @update | 默认 |
|---|---|
| @none | 删除依赖 |
| @v1.2.2 | tag版本,语义版本 |
| @23dfdd5 | 特定的commit |
| @master | 分支的最新commit |
go mod
go mod +
| init | 初始化, 创建go.mod |
|---|---|
| download | 下载模块到本地缓存 |
| tidy | 增加需要的依赖, 删除不需要的依赖 |
总结与思考
协程
用户态协程:轻量级线程,栈大小为KB级别。
内核态协程:运行在内核态的线程上,栈大小为MB级别。
Go中的协程:使用go关键字启动一个新的协程。
通道
通信方式:提倡通过通信共享内存,而不是通过共享内存实现通信。
通道创建:
无缓冲通道:make(chan int)
有缓冲通道:make(chan int, 2)