02 设计哲学
设计哲学之于编程语言,就好比一个人的价值观之于这个人的行为。
- 简单:Go 生产力的源泉。
- 显式:Go 希望开发人员 明确知道自己在做什么;显式的基于值比较的错误处理方案。
- 组合:类型嵌入(Type Embedding)。
- 并发:面向多核、原生支持并发、用户层轻量级线程 goroutine。
- 面向工程:将解决工程问题作为 Go 的 设计原则之一,这些问题包括:程序构建慢、依赖管理失控、代码难于理 解、跨语言构建难等。
03 配好环境
- https://go.dev/doc/devel/release
- https://golang.google.cn/dl/
安装多个 Go 版本
代码语言:javascript复制go get golang.org/dl/go1.15.13
go1.15.13 download
go1.15.13 version
配置 Go
代码语言:javascript复制go env
go help environment
04 Go 程序的结构
import "fmt"
一行中fmt
代表的是包的导入路径(Import),它表示的是标准库下的 fmt 目录,整个 import 声明语句的含义是导入标准库 fmt 目录下的包fmt.Println
函数调用一行中的fmt
代表的则是包名。- 通常导入路径的最后一个分段名与包名是相同的,这也很容易让人误解 import 声明语句中的
fmt
指的是包名,其实并不是这样的。
gofmt main.go
Go module
代码语言:javascript复制go mod init
go mod tidy
05 Go 项目的布局标准
loccount 工具
https://github.com/golang/go
代码语言:javascript复制tree -LF 1 .
06 解决包依赖管理
GOPATH -> Vendor -> Go Module
GOPATH
代码语言:javascript复制go env
GOPATH="/Users/v_yfanzhao/go"
go get github.com/sirupsen/logrus
vendor
- Go 项目必须放在 GOPATH 环境变量配置的路径下,庞大的 vendor 目录需要提交到代码仓库,不仅占用代码仓库空间,减慢仓库下载和更新的速度, 而且还会干扰代码评审,对实施代码统计等开发者效能工具也有比较大影响。
- 你还需要手工管理 vendor 下面的 Go 依赖包,包括项目依赖包的分析、版本的记 录、依赖包获取和存放,等等,最让开发者头疼的就是这一点。
Go Module
Go Module 与 go.mod 是一一对应的。go.mod 文件所在的顶层目录也被称为 module 的根目录,module 根目录以及它子目录 下的所有 Go 包均归属于这个 Go Module,这个 module 也被称为 main module。
代码语言:javascript复制package main
import "github.com/sirupsen/logrus"
func main() {
logrus.Println("hello, go module mode")
}
代码语言:javascript复制go mod init
go mod tidy
代码语言:javascript复制module go-lesson-one
go 1.17
require github.com/sirupsen/logrus v1.9.0
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
major.minor.patch
Go 的语义导入版本机制:将包主版本号引入到包导入路径中。v0、v1 时不加入路径。
因此甚至可以同时依赖一个包的两个不兼容版本:
代码语言:javascript复制import (
"github.com/sirupsen/logrus"
logv2 "github.com/sirupsen/logrus/v2"
)
Go 会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”。这与 PHP Composer 最新最大 (Latest Greatest) 版本 相反。
07 Go Module 操作
代码语言:javascript复制go list -m all
go list -m -versions github.com/sirupsen/logrus
# 指定版本 升降级
go get github.com/sirupsen/logrus@v1.7.0
# 指定版本 升降级
go mod edit -require=github.com/sirupsen/logrus@v1.7.0
go mod tidy
使用 vendor 机制
代码语言:javascript复制go mod vendor
go build -mod=verdor
# 顶层目录下存在 vendor 目录,那么 go build 默认也会优先基于 vendor 构建,除非:
go build -mod=mod
08 Go 程序的执行次序
可执行程序的 main 包必须定义 main 函数,否则 Go 编译器会报错。
除了 main 包外,其他包也可以拥有自己的名为 main 的函数 或方法。
init 函数
除了前面讲过的 main.main 函数之外,Go 语言还有一个特殊函数,它就是用于进行包初始化的 init 函数了。main 函数之前,常量和变量初 始化之后。每个 init 函数在整个 Go 程序生命周期内仅会被执行一次。Go 包可以拥有不止一个 init 函数。
Go 在进行包初始化的过程中,会采用“深度优先”的原则,递归初始化各个包的 依赖包。
代码语言:javascript复制package main
|- import pkg1
|- import pkg2
|- const
|- var
|- init()
|- const
|- var
|- init()
|- const
|- var
|- init()
|- main()
init 函数的用途
- 重置包级变量值。被用于检查包级变量的初始状态。
- 实现对包级变量的复杂初始化。
- 在 init 函数中实现“注册模式”。通过在 init 函数中注册自己的实现的模式,就有效降低了 Go 包对外的直接 暴露,尤其是包级变量的暴露,从而避免了外部通过包级变量对包状态的改动。
09 构建一个 Web 服务
代码语言:javascript复制package main
import (
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
})
http.ListenAndServe(":8888", nil)
}
代码语言:javascript复制curl localhost:8888
Hello World
https://github.com/imzyf/go-bookstore
10 变量声明
代码语言:javascript复制var a int = 10
// 将变量名放在了类型的前面
// 修饰关键字 变量名 类型 初值
// 省略类型信息的声明
var b = 12
// 显式赋予变量初值
var b = int32(13)
// 声明多个
var a, b, c = 12, 'A', "hello"
// 短变量声明
a := 12
b := 'A'
c := "hello"
// 声明多个
a, b, c := 12, 'A', "hello"
Go 语言的两类变量
- 包级变量 (package varible)
- 局部变量 (local varible)
包级变量的声明形式
包级变量只能使用带有 var 关键字的变量声明形式,不能使用短变量声明形式,但在形式细节上可以有一定灵活度。
代码语言:javascript复制var b int32 = 17 // 显式指定类型
var f float32 = 3.14 // 显式指定类型
var a = 13 // 使用默认类型
var b = int32(17) // 显式指定类型
var f = float32(3.14) // 显式指定类型
var a int32
var f float64
代码语言:javascript复制// 声明聚类
var (
netGo bool
netCgo bool
)
var (
aLongTimeAgo = time.Unix(1, 0)
noDeadline = time.Time{}
noCancel = (chan struct{})(nil)
)
// 就近原则
// 尽可能在靠近第一次使用变量的位置声明这个变量
局部变量的声明形式
代码语言:javascript复制// 延迟初始化的局部变量
var err error
// 显式初始化的局部变量
a := 17
f := float32(3.14)
s := []byte("hello, gopher!")
// 尽量在分支控制时使用短变量声明形式
11 代码块 Block 与作用域 Scope
代码语言:javascript复制// 变量遮蔽
var a = 11
func foo(n int) {
a := 1
a = n
}
func main() {
fmt.Println("a =", a) // 11
foo(5)
fmt.Println("after calling foo, a =", a) // 11
}
- 宇宙代码块(Universe Block)
- 包代码块(Package Block)
- 文件代码块(File Block)
- 分支控制语句隐式代码块
- switch/select 的子句隐式代码块
一个标识符的作用域就是指:这个标识符在被声明后可以被有效使用的源码区域。
导出标识符:
- 声明在包代码块中
- 它名字第一个字符是一个大写的 Unicode 字符
https://github.com/imzyf/go-lesson-one/blob/main/cmd/chapter11/main.go
12 数值类型
整型
Go 采用补码(2’s complement)作为整型的比特位编码方法。Go 的补码是通过原码逐位取反后再加 1 得到的。
代码语言:javascript复制unit8 1 0 0 0 0 0 0 1 = 129
int8 1 0 0 0 0 0 0 1 = -127
0 1 1 1 1 1 1 1 127
1 0 0 0 0 0 0 0 取反
1 0 0 0 0 0 0 1 1 -127
整型的溢出问题
https://github.com/imzyf/go-lesson-one/blob/main/cmd/chapter12/main.go
这个问题最容易发生在循环语句的结束条件判断中,因为这也是经常使用整型变量的地方。
浮点型
代码语言:javascript复制IEEE 754
符号位Sign 阶码Exponent 尾数Maintissa
bit 位 | 符号位 | 阶码 | 阶码偏移值 | 尾数 |
---|---|---|---|---|
单精度 float32 | 1 | 8 | 127 | 23 |
双精度 float64 | 1 | 11 | 1023 | 52 |
eg:129.8125
代码语言:javascript复制步骤一:我们要把这个浮点数值的整数部分和小数部分,分别转换为二进制形式(后缀 d 表示十进制数,后缀 b 表示二进制数):
整数部分:139d => 10001011b;
小数部分:0.8125d => 0.1101b(十进制小数转换为二进制可采用“乘 2 取整”的竖式计算)。
0.8125 * 2 = 1.625 …… 1
0.625 * 2 = 1.25 …… 1
0.25 * 2 = 0.5 …… 0
0.5 * 2 = 1 …… 1
139.8125d -> 10001011.1101b
代码语言:javascript复制步骤二:移动小数点,直到整数部分仅有一个 1。
10001011.1101b -> 1.00010111101b
小数点向左移了 7 位,这样 指数就为 `7`,尾数为 `00010111101b`。
代码语言:javascript复制步骤三:计算阶码。对于 float32 的单精度浮点数而言:
阶码 = 指数 偏移值
偏移值的计算公式为 2^(e-1)-1,其中 e 为阶码部分的 bit 位数,这里为 8,于是单精度浮点数的阶码偏移 值就为 2^(8-1)-1 = 127。
阶码 = `7` 127 = 134d = `10000110b`。
代码语言:javascript复制步骤四:将符号位、阶码和尾数填到各自位置,得到最终浮点数的二进制表示
符号位 0
阶码 10000110
尾数 00010111101 不足 23 位补零 `0_0010111101_00_0000000000`
139.8125
-> 0_10000110_00010111101_000000000000
- Go Float32bit() result not expected | stackoverflow
复数型
矢量计算。
创建自定义的数值类型
代码语言:javascript复制type MyInt int32
var m int = 5
var n int32 = 6
var a MyInt = m // error
var a MyInt = n // error
var a = MyInt(m) // ok
var a = MyInt(n) // ok
MyInt 类型的底层类型是 int32,所以它的数值性质与 int32 完全相同,但它 们仍然是完全不同的两种类型。
类型别名(Type Alias)
代码语言:javascript复制type MyInt = int32
var n int32 = 6
var a MyInt = n
通过类型别名语法定义的新类型与原类型别无二致,可以完全相互替代。
13 字符串类型
why-what-how
非原生字符串:
- 不是原生类型,编译器不会对它进行类型校验,导致类型安全性差;
- 字符串操作时要时刻考虑结尾的