近年来,Go语言迅速占领了开发领域的重要地位,成为众多公司和开发者的首选语言。
其简捷、高效、并发性强的特点使得它在处理大型系统和复杂工程问题时表现出色,为开发者提供了良好的体验。
随着云计算、容器化、微服务、服务网格等技术的兴起,Go语言的应用场景愈发广泛,这也进一步推动其社区的繁荣和发展。
可以预见,Go语言的发展前景将会更加广阔。
本文分享几个你可能不知道的Go语言小细节,希望能帮助大家更好地学习这门语言~~
01
数字字面量
Go语言中声明数字变量时,可以使用数字字面量让开发者以二进制、八进制或十六进制浮点数的格式定义数字。例如:
代码语言:javascript复制v1 := 0b00101101 // 二进制字面量
v2 := 0o377 // 八进制字面量
v3 := 0x1p-2 // 十六进制浮点字面量
v4 := 01e2 // 十进制浮点字面量
在表达数字字面量时,可以使用_分隔数字。例如:
代码语言:javascript复制v5 := 123_456 // 可以使用_分隔数字
02
切片表达式
切片表达式指从字符串、数组、指向数组或切片的指针构造子字符串或切片。
它有两种变体,其中一种是指定low和high两个索引界限值。
由于切片的底层是数组,所以我们可以基于数组通过切片表达式得到切片,切片表达式中的low和high表示索引范围(左包含,右不包含)。
例如,下面的代码表示从数组a中选出1≤索引值<4的元素组成切片s,切片s的长度为high-low,容量为其底层数组的容量。
代码语言:javascript复制// 对数组取切片
a := [5]int{1, 2, 3, 4, 5}
s := a[1:3] // s := a[low:high]
fmt.Printf("s:%v type:%T len:%v cap:%vn", s, s, len(s), cap(s))
// 对字符串取切片得到的还是字符串类型
b := "hello world"
s2 := b[1:3] // s2 := b[low:high]
fmt.Printf("s2:%v type:%T len:%vn", s2, s2, len(s2))
输出:
代码语言:javascript复制s:[2 3] type:[]int len:2 cap:4
s2:el type:string len:2
方便起见,可以省略切片表达式中的任何索引。如果省略low则默认为0,如果省略high则默认为切片操作数的长度。
代码语言:javascript复制a[2:] // 等同于 a[2:len(a)]
a[:3] // 等同于 a[0:3]
a[:] // 等同于 a[0:len(a)]
注意:对于数组或字符串,如果0 ≤ low ≤ high ≤ len(a),则索引合法;否则会索引越界(out of range)。
对切片再执行切片表达式时(切片再切片),high的上限是切片的容量cap(a),而不是长度。
常量索引必须是非负的,并且可以用int类型的值表示。
对于数组或常量字符串,常量索引必须在有效范围内。
如果low和high两个指标都是常数,则它们必须满足low≤high。
如果索引在运行时超出范围,就会发生运行时panic。
代码语言:javascript复制a := [5]int{1, 2, 3, 4, 5}
s := a[1:3] // s := a[low:high]
fmt.Printf("s:%v len:%v cap:%vn", s, len(s), cap(s))
s2 := s[3:4] // 索引的上限是cap(s)而不是len(s)
fmt.Printf("s2:%v len:%v cap:%vn", s2, len(s2), cap(s2))
输出:
代码语言:javascript复制s:[2 3] len:2 cap:4
s2:[5] len:1 cap:1
另一种变体是除了指定low和high索引界限值,还指定容量的完整形式。
注意:字符串不支持完整切片表达式。
代码语言:javascript复制a[low:high:max]
上面的代码会构造与简单切片表达式a[low:high]的类型、长度和元素相同的切片。
另外,它会将得到的结果切片的容量设置为max-low。
在完整切片表达式中只有第一个索引值(low)可以省略,该值默认为0。
代码语言:javascript复制a := [5]int{1, 2, 3, 4, 5}
s1 := a[1:3:4] // 通过额外指定max,控制切片的容量
fmt.Printf("s1:%v len:%v cap:%vn", s1, len(s1), cap(s1))
s2 := a[1:3]
fmt.Printf("s2:%v len:%v cap:%vn", s2, len(s2), cap(s2))
输出结果:
代码语言:javascript复制s1:[2 3] len:2 cap:2
s2:[2 3] len:2 cap:4
完整切片表达式需要满足的条件是0 ≤ low ≤ high ≤ max ≤ cap(a),其他条件和简单切片表达式相同。
03
go test cache
在包列表模式下,go test会缓存测试成功的包的测试结果,以避免运行不必要的重复测试。
当测试结果可以从缓存中获取时,go test将直接显示以前缓存的输出,而不是再次运行测试二进制文件。
当这种情况发生时,go test会输出(cached)来代替摘要行中的运行时间。
执行两次 go test . -v,从下面的输出结果可以看到,第二次的输出结果中有cached标识。
代码语言:javascript复制❯ go test . -v
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
PASS
ok split 0.005s
❯ go test . -v
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
PASS
ok split (cached)
如果多次执行测试时运行的二进制文件相同,并且命令行上的参数都是可缓存测试参数(-bachtime、-cpu、-list、-pallel、-run、-short、-timeout、-failfast和-v),就会匹配到缓存中的结果。
只要测试时添加了除上述可缓存参数外的任何参数就不会缓存结果,显式禁用测试缓存的惯用方法是在命令行使用 -count=1 参数。
代码语言:javascript复制❯ go test . -v -count=1
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
PASS
ok split 0.005s
04
JSON序列化时不转义
json包中的encoder可以通过SetEscapeHTML指定是否应该在JSON字符串中转义有问题的HTML字符。
它默认将&、<和>转义为u0026、u003c和u003e,以避免在HTML中嵌入JSON时出现安全问题。
如果在是非HTML场景下不想被转义,那么可以通过SetEscapeHTML(false)禁用此行为。
例如,在有些业务场景下可能需要序列化带查询参数的URL,我们并不希望转义&符号。
代码语言:javascript复制// URLInfo 一个包含URL字段的结构体
type URLInfo struct {
URL string
// ...
}
func jsonEncodeDontEscapeHTML(data URLInfo) {
// 默认序列化时会转义 &、<和>
b, _ := json.Marshal(data)
fmt.Printf("json.Marshal(data) result:%sn", b)
// 通过 SetEscapeHTML(false) 设置不转义
buf := bytes.Buffer{}
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(false)
encoder.Encode(data)
fmt.Printf("encoder.Encode(data) result:%sn", buf.String())
}
func main() {
jsonEncodeDontEscapeHTML(URLInfo{URL: "https://liwenzhou.com?name=q1mi&age=18"})
}
输出结果如下:
代码语言:javascript复制json.Marshal(data) result:{"URL":"https://liwenzhou.com?name=q1miu0026age=18"}
encoder.Encode(data) result:{"URL":"https://liwenzhou.com?name=q1mi&age=18"}
以上内容节选自《Go语言之路》一书,欢迎阅读本书了解更多Go语言相关内容。
《Go语言之路》这本书是七米对Go语言学习方法的一次系统性总结。
作为国内较早一批Go语言爱好者,作者把他自己学习Go语言的经验总结下来,将Go语言的相关知识点全部串联起来,完整地介绍了如何从零开始学习Go语言。