Go 的零值有什么用?看看這四個場景
背景
Go 語言中有一個有些特殊的概念,叫做零值。許多轉語言的同學經常會弄混淆,一開始會不適應。
代碼如下:
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
輸出結果:
0 0 false ""
這會導致大家在定義各種數據庫字段時比較糾結,又會說 Go 這零值,例如:整型的零值 0,跟數據庫里的 0 枚舉值沖突。又或是做入參判斷時怎么區別開?
一時半會想不明白,為什么 Go 要定義這個零值來增加復雜度?
有什么用
官方詮釋
Go 核心團隊的 @Dave Cheney 在《What is the zero value, and why is it useful?[1]》中對零值進行了詳細的闡釋,以下部分為該文的展開介紹。
將一個值設置為已知默認值的特性,對于程序的安全性和正確性非常重要。可以使你的 Go 程序更加簡單和緊湊。這就是 Go 程序員常說的 "給你的結構一個有用的零值"。
官方案例
以下是 Go 官方給出的幾個零值的例子,非常具有代表性。分別是:
- sync.Mutex。
- byte.Buffer。
- slices。
- nil func。
Sync.Mutex
sync.Mutex 被設計為無需顯式初始化就可以使用,可以實現這個功能的原因是 sync.Mutex 包 含兩個未導出的整數字段。
用大白話講,就是由于零值的存在,只要聲明了 sync.Mutex,這些字段就會被設置為 0(值會被初始化)。
因此可以無需顯式初始化就可以開箱即用。
如下代碼:
package main
import "sync"
type MyInt struct {
mu sync.Mutex
val int
}
func main() {
var i MyInt
// i.mu is usable without explicit initialisation.
i.mu.Lock()
i.val++
i.mu.Unlock()
}
Byte.Buffer
因為有零值的存在,bytes.Buffer 在進行寫入或讀取的操作時,不需要人為的進行明確的初始化。也能做到很好的開箱即用。
如下代碼:
package main
import "bytes"
import "io"
import "os"
func main() {
var b bytes.Buffer
b.Write([]byte("Hello world"))
io.Copy(os.Stdout, &b)
}
注:io.Copy 需要一個 io.Reader 作為它的第二個參數,所以我們需要傳遞一個指向 b 的指針。
Slices
在 slices 的定義中,它的零值是 nil。這意味著你不需要顯式定義一個 slices,只需要直接聲明它,就可以使用了。
如下:
package main
import "fmt"
import "strings"
func main() {
// s := make([]string, 0)
// s := []string{}
var s []string
s = append(s, "Hello")
s = append(s, "world")
fmt.Println(strings.Join(s, " "))
}
Nil func
你可以在有 nil 值的類型上調用方法,這也是零值作為缺省值的作用之一。
這個場景,甚至一度讓許多 Go 程序員以為是 BUG,我還專門寫過文章。這是可行的,其實是零值的延伸出來的用法。
如下代碼:
package main
import "fmt"
type Config struct {
path string
}
func (c *Config) Path() string {
if c == nil {
return "/usr/home"
}
return c.path
}
func main() {
var c1 *Config
var c2 = &Config{
path: "/export",
}
fmt.Println(c1.Path(), c2.Path())
}
總結
零值,在 Go 里雖然是一個缺省值。但它本質上不僅僅是一個 “缺省”,它還在 Go 程序里的許多應用場景提供了不少的便捷特性,甚至是隱晦的功能。
希望本文對你對零值的理解有進一步的幫助!