02 设计哲学
设计哲学之于编程语言,就好比一个人的价值观之于这个人的行为。
- 简单:Go 生产力的源泉。
- 显式:Go 希望开发人员 明确知道自己在做什么;显式的基于值比较的错误处理方案。
- 组合:类型嵌入(Type Embedding)。
- 并发:面向多核、原生支持并发、用户层轻量级线程 goroutine。
- 面向工程:将解决工程问题作为 Go 的 设计原则之一,这些问题包括:程序构建慢、依赖管理失控、代码难于理 解、跨语言构建难等。
03 配好环境
安装多个 Go 版本
go get golang.org/dl/go1.15.13 |
配置 Go
go env |
04 Go 程序的结构
import "fmt"
一行中fmt
代表的是包的导入路径(Import),它表示的是标准库下的 fmt 目录,整个 import 声明语句的含义是导入标准库 fmt 目录下的包fmt.Println
函数调用一行中的fmt
代表的则是包名。- 通常导入路径的最后一个分段名与包名是相同的,这也很容易让人误解 import 声明语句中的
fmt
指的是包名,其实并不是这样的。
gofmt main.go |
Go module
go mod init |
05 Go 项目的布局标准
loccount 工具
tree -LF 1 . |
06 解决包依赖管理
GOPATH -> Vendor -> Go Module
GOPATH
go env |
vendor
- Go 项目必须放在 GOPATH 环境变量配置的路径下,庞大的 vendor 目录需要提交到代码仓库,不仅占用代码仓库空间,减慢仓库下载和更新的速度, 而且还会干扰代码评审,对实施代码统计等开发者效能工具也有比较大影响。
- 你还需要手工管理 vendor 下面的 Go 依赖包,包括项目依赖包的分析、版本的记 录、依赖包获取和存放,等等,最让开发者头疼的就是这一点。
Go Module
Go Module 与 go.mod 是一一对应的。go.mod 文件所在的顶层目录也被称为 module 的根目录,module 根目录以及它子目录 下的所有 Go 包均归属于这个 Go Module,这个 module 也被称为 main module。
package main |
go mod init |
module go-lesson-one |
major.minor.patch
Go 的语义导入版本机制:将包主版本号引入到包导入路径中。v0、v1 时不加入路径。
因此甚至可以同时依赖一个包的两个不兼容版本:
import ( |
Go 会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”。这与 PHP Composer 最新最大 (Latest Greatest) 版本 相反。
07 Go Module 操作
go list -m all |
使用 vendor 机制
go mod vendor |
08 Go 程序的执行次序
可执行程序的 main 包必须定义 main 函数,否则 Go 编译器会报错。
除了 main 包外,其他包也可以拥有自己的名为 main 的函数 或方法。
init 函数
除了前面讲过的 main.main 函数之外,Go 语言还有一个特殊函数,它就是用于进行包初始化的 init 函数了。main 函数之前,常量和变量初 始化之后。每个 init 函数在整个 Go 程序生命周期内仅会被执行一次。Go 包可以拥有不止一个 init 函数。
Go 在进行包初始化的过程中,会采用“深度优先”的原则,递归初始化各个包的 依赖包。
package main |
init 函数的用途
- 重置包级变量值。被用于检查包级变量的初始状态。
- 实现对包级变量的复杂初始化。
- 在 init 函数中实现“注册模式”。通过在 init 函数中注册自己的实现的模式,就有效降低了 Go 包对外的直接 暴露,尤其是包级变量的暴露,从而避免了外部通过包级变量对包状态的改动。
09 构建一个 Web 服务
package main |
curl localhost:8888 |
10 变量声明
var a int = 10 |
Go 语言的两类变量
- 包级变量 (package varible)
- 局部变量 (local varible)
包级变量的声明形式
包级变量只能使用带有 var 关键字的变量声明形式,不能使用短变量声明形式,但在形式细节上可以有一定灵活度。
var b int32 = 17 // 显式指定类型 |
// 声明聚类 |
局部变量的声明形式
// 延迟初始化的局部变量 |
11 代码块 Block 与作用域 Scope
// 变量遮蔽 |
- 宇宙代码块(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 得到的。
unit8 1 0 0 0 0 0 0 1 = 129 |
整型的溢出问题
https://github.com/imzyf/go-lesson-one/blob/main/cmd/chapter12/main.go
这个问题最容易发生在循环语句的结束条件判断中,因为这也是经常使用整型变量的地方。
浮点型
IEEE 754 |
\bit 位\ | 符号位 | 阶码 | 阶码偏移值 | 尾数 |
---|---|---|---|---|
单精度 float32 | 1 | 8 | 127 | 23 |
双精度 float64 | 1 | 11 | 1023 | 52 |
eg:129.8125
步骤一:我们要把这个浮点数值的整数部分和小数部分,分别转换为二进制形式(后缀 d 表示十进制数,后缀 b 表示二进制数): |
步骤二:移动小数点,直到整数部分仅有一个 1。 |
步骤三:计算阶码。对于 float32 的单精度浮点数而言: |
步骤四:将符号位、阶码和尾数填到各自位置,得到最终浮点数的二进制表示 |
139.8125
-> 0_10000110_00010111101_000000000000
复数型
矢量计算。
创建自定义的数值类型
type MyInt int32 |
MyInt 类型的底层类型是 int32,所以它的数值性质与 int32 完全相同,但它 们仍然是完全不同的两种类型。
类型别名(Type Alias)
type MyInt = int32 |
通过类型别名语法定义的新类型与原类型别无二致,可以完全相互替代。
13 字符串类型
why-what-how
非原生字符串:
- 不是原生类型,编译器不会对它进行类型校验,导致类型安全性差;
- 字符串操作时要时刻考虑结尾的
\0
,防止缓冲区溢出; - 以字符数组形式定义的“字符串”,它的值是可变的,在并发场景中需要考虑同步问题;
- 获取一个字符串的长度代价较大,通常是 O(n) 时间复杂度;
- C 语言没有内置对非 ASCII 字符(如中文字符)的支持。
string 类型的数据是不可变的,提高了字符串的并发安全性和存储利用率(同一个字符串值分配同一块存储)。
var s string = "hello" |
没有结尾 \0
,而且获取长度的时间复杂度是常数时间,消除了获取字符串长度的开销。
反引号原生支持“所见即所得”的原始字符串,大大降低构造多行字符串时的心智负担。
对非 ASCII 字符提供原生支持,消除了源码在不同环境下显示乱码的可能。Unicode 字符是以 UTF-8 编码格式存储在内存。
通过单引号括起的字符字面值:
https://github.com/imzyf/go-lesson-one/blob/main/cmd/chapter13/main.go
UTF-8 编码解决的是 Unicode 码点值在计算机中如何存储和表示(位模式)的问题。UTF-8 方案使用变长度字节,从 1 个到 4 个不等。
一个 rune 存储一个 unicode 码点或 utf-32 的四字节编码;从字节视角,string 对应的底层存储存放的是 utf8 编码。
Go 字符串类型的内部标示
// StringHeader 是一个 string 的运行时表示 |
直接将 string 类型通过函数或方法参数传入也不会带来太多的开销。因为传入的仅仅是一个“描述符”,而不是真正的字符串数据。
Go 字符串类型的常见操作
下标操作;下标操作,我们获取的是字符串中特定下标上的字节,而不是字符。
字符迭代:
- or 迭代,字节视角的迭代
- 字符串中 Unicode 字符的码点值,以及该字符在字符串中的偏移值(字节视角)
字符串连接;+
+=
strings.Builder
strings.Join
fmt.Sprintf
。
字符串比较;= =、!= 、>=、<=、> 和 <。
字符串转换;string -> []rune
[]byte
14 常量
- 支持无类型常量
- 支持隐式自动转型
- 可用于实现枚举
type myInt int |
Go 的 const 语法提供了“隐式重复前一个非空表达式”的机制。
const ( |
15 数组与切片
数组是一个固定长度的、由同构类型元素组成的连续序列。不仅是逻辑上的连续序列,而且在实际内存分配时也占据着一整块内存。
切片不定长同构数据类型。切片可以看成是数组的“描述符”(句柄),为数组打开了一个访问与修改的“窗口”。
// 切片 |
var sl1 []int // 是声明,未初始化,是nil值,底层没有分配内存空间 |
16 map 类型
一组无序的键值对。
map[key_type]value_type |
key 的类型必须支持“==”和“!=”两种比较操作符。
References
– EOF —