设计哲学之于编程语言,就好比一个人的价值观之于这个人的行为。
go get golang.org/dl/go1.15.13
go1.15.13 download
go1.15.13 version
go env
go help environment
import "fmt"
一行中 fmt
代表的是包的导入路径(Import),它表示的是标准库下的 fmt 目录,整个 import 声明语句的含义是导入标准库 fmt 目录下的包fmt.Println
函数调用一行中的 fmt
代表的则是包名。fmt
指的是包名,其实并不是这样的。gofmt main.go
go mod init
go mod tidy
loccount 工具
tree -LF 1 .
GOPATH -> Vendor -> Go Module
go env
GOPATH="/Users/v_yfanzhao/go"
go get github.com/sirupsen/logrus
Go Module 与 go.mod 是一一对应的。go.mod 文件所在的顶层目录也被称为 module 的根目录,module 根目录以及它子目录 下的所有 Go 包均归属于这个 Go Module,这个 module 也被称为 main module。
package main
import "github.com/sirupsen/logrus"
func main() {
logrus.Println("hello, go module mode")
}
go mod init
go mod tidy
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 时不加入路径。
因此甚至可以同时依赖一个包的两个不兼容版本:
import (
"github.com/sirupsen/logrus"
logv2 "github.com/sirupsen/logrus/v2"
)
Go 会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”。这与 PHP Composer 最新最大 (Latest Greatest) 版本 相反。
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
go mod vendor
go build -mod=verdor
# 顶层目录下存在 vendor 目录,那么 go build 默认也会优先基于 vendor 构建,除非:
go build -mod=mod
可执行程序的 main 包必须定义 main 函数,否则 Go 编译器会报错。
除了 main 包外,其他包也可以拥有自己的名为 main 的函数 或方法。
除了前面讲过的 main.main 函数之外,Go 语言还有一个特殊函数,它就是用于进行包初始化的 init 函数了。main 函数之前,常量和变量初 始化之后。每个 init 函数在整个 Go 程序生命周期内仅会被执行一次。Go 包可以拥有不止一个 init 函数。
Go 在进行包初始化的过程中,会采用“深度优先”的原则,递归初始化各个包的 依赖包。
package main
|- import pkg1
|- import pkg2
|- const
|- var
|- init()
|- const
|- var
|- init()
|- const
|- var
|- init()
|- main()
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)
}
curl localhost:8888
Hello World
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"
包级变量只能使用带有 var 关键字的变量声明形式,不能使用短变量声明形式,但在形式细节上可以有一定灵活度。
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
// 声明聚类
var (
netGo bool
netCgo bool
)
var (
aLongTimeAgo = time.Unix(1, 0)
noDeadline = time.Time{}
noCancel = (chan struct{})(nil)
)
// 就近原则
// 尽可能在靠近第一次使用变量的位置声明这个变量
// 延迟初始化的局部变量
var err error
// 显式初始化的局部变量
a := 17
f := float32(3.14)
s := []byte("hello, gopher!")
// 尽量在分支控制时使用短变量声明形式
// 变量遮蔽
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
}
一个标识符的作用域就是指:这个标识符在被声明后可以被有效使用的源码区域。
导出标识符:
https://github.com/imzyf/go-lesson-one/blob/main/cmd/chapter11/main.go
Go 采用补码(2’s complement)作为整型的比特位编码方法。Go 的补码是通过原码逐位取反后再加 1 得到的。
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
这个问题最容易发生在循环语句的结束条件判断中,因为这也是经常使用整型变量的地方。
IEEE 754
符号位Sign 阶码Exponent 尾数Maintissa
\bit 位\ | 符号位 | 阶码 | 阶码偏移值 | 尾数 |
---|---|---|---|---|
单精度 float32 | 1 | 8 | 127 | 23 |
双精度 float64 | 1 | 11 | 1023 | 52 |
eg:129.8125
步骤一:我们要把这个浮点数值的整数部分和小数部分,分别转换为二进制形式(后缀 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
步骤二:移动小数点,直到整数部分仅有一个 1。
10001011.1101b -> 1.00010111101b
小数点向左移了 7 位,这样 指数就为 `7`,尾数为 `00010111101b`。
步骤三:计算阶码。对于 float32 的单精度浮点数而言:
阶码 = 指数 + 偏移值
偏移值的计算公式为 2^(e-1)-1,其中 e 为阶码部分的 bit 位数,这里为 8,于是单精度浮点数的阶码偏移 值就为 2^(8-1)-1 = 127。
阶码 = `7` + 127 = 134d = `10000110b`。
步骤四:将符号位、阶码和尾数填到各自位置,得到最终浮点数的二进制表示
符号位 0
阶码 10000110
尾数 00010111101 不足 23 位补零 `0_0010111101_00_0000000000`
139.8125
-> 0_10000110_00010111101_000000000000
矢量计算。
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 MyInt = int32
var n int32 = 6
var a MyInt = n
通过类型别名语法定义的新类型与原类型别无二致,可以完全相互替代。
why-what-how
非原生字符串:
\0
,防止缓冲区溢出;string 类型的数据是不可变的,提高了字符串的并发安全性和存储利用率(同一个字符串值分配同一块存储)。
var s string = "hello"
s[0] = 'k' // cannot assign to s[0] (value of type byte)
s = "gopher" // ok
没有结尾 \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 类型其实是一个“描述符”
type StringHeader struct {
// 一个指向底层存储的指针
Data uintptr
// 字符串的长度
Len int
}
直接将 string 类型通过函数或方法参数传入也不会带来太多的开销。因为传入的仅仅是一个“描述符”,而不是真正的字符串数据。
下标操作;下标操作,我们获取的是字符串中特定下标上的字节,而不是字符。
字符迭代:
字符串连接;+
+=
strings.Builder
strings.Join
fmt.Sprintf
。
字符串比较;= =、!= 、>=、<=、> 和 <。
字符串转换;string -> []rune
[]byte
type myInt int
// 无类型常量(Untyped Constant)
const n = 13
func main() {
var a myInt = 5
// 隐式转型
fmt.Println(a + n)
}
// 无类型常量 + 隐式转型:使得在 Go 这样的具有强类型系统的语言,在处理表达式混合数据类型运算的时候具有比较大的灵活性,代码编写也得到了一定程度的简化。
Go 的 const 语法提供了“隐式重复前一个非空表达式”的机制。
const (
Apple, Banana = iota, iota + 10 // 0, 10 (iota = 0)
Strawberry, Grape // 1, 11 (iota = 1)
Pear, Watermelon // 2, 12 (iota = 2)
)
const (
_ = iota // 略过 iota = 0
IPV6_V6ONLY // 1
SOMAXCONN // 2
SO_ERROR // 3
)
数组是一个固定长度的、由同构类型元素组成的连续序列。不仅是逻辑上的连续序列,而且在实际内存分配时也占据着一整块内存。
切片不定长同构数据类型。切片可以看成是数组的“描述符”(句柄),为数组打开了一个访问与修改的“窗口”。
// 切片
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // 切片的长度,即切片中当前元素的个数
cap int // 底层数组的长度,也是切片的最大容量,cap 值永远大于等于 len 值
}
var sl1 []int // 是声明,未初始化,是nil值,底层没有分配内存空间
var sl2 = []int{} // 初始化了,不是nil值,底层分配了内存空间,有地址。
一组无序的键值对。
map[key_type]value_type
key 的类型必须支持“==”和“!=”两种比较操作符。
– EOF —