4.4. 让零值更有用

假设变量没有初始化,每个变量声明都会自动初始化为与零内存的内容相匹配的值。 这就是零值。 值的类型决定了其零值; 对于数字类型,它为 0,对于指针类型为 nilslicesmapchannel 同样是 nil

始终设置变量为已知默认值的属性对于程序的安全性和正确性非常重要,并且可以使 Go 语言程序更简单、更紧凑。 这就是 Go 程序员所说的“给你的结构一个有用的零值”。

对于 sync.Mutex 类型。sync.Mutex 包含两个未公开的整数字段,它们用来表示互斥锁的内部状态。 每当声明 sync.Mutex 时,其字段会被设置为 0 初始值。sync.Mutex 利用此属性来编写,使该类型可直接使用而无需初始化。

  1. type MyInt struct {
  2. mu sync.Mutex
  3. val int
  4. }
  5. func main() {
  6. var i MyInt
  7. // i.mu is usable without explicit initialisation.
  8. i.mu.Lock()
  9. i.val++
  10. i.mu.Unlock()
  11. }

另一个利用零值的类型是 bytes.Buffer。您可以声明 bytes.Buffer 然后就直接写入而无需初始化。

  1. func main() {
  2. var b bytes.Buffer
  3. b.WriteString("Hello, world!\n")
  4. io.Copy(os.Stdout, &b)
  5. }

切片的一个有用属性是它们的零值 nil。如果我们看一下切片运行时 header 的定义就不难理解:

  1. type slice struct {
  2. array *[...]T // pointer to the underlying array
  3. len int
  4. cap int
  5. }

此结构的零值意味着 lencap 的值为 0,而 array(指向保存切片的内容数组的指针)将为 nil。这意味着你不需要 make 切片,你只需声明它即可。

  1. func main() {
  2. // s := make([]string, 0)
  3. // s := []string{}
  4. var s []string
  5. s = append(s, "Hello")
  6. s = append(s, "world")
  7. fmt.Println(strings.Join(s, " "))
  8. }

注意:var s []string 类似于它上面的两条注释行,但并不完全相同。值为 nil 的切片与具有零长度的切片就可以来相互比较。以下代码将输出 false

  1. func main() {
  2. var s1 = []string{}
  3. var s2 []string
  4. fmt.Println(reflect.DeepEqual(s1, s2))
  5. }

nil pointers — 未初始化的指针变量的一个有用属性是你可以在具有 nil 值的类型上调用方法。它可以简单地用于提供默认值。

  1. type Config struct {
  2. path string
  3. }
  4. func (c *Config) Path() string {
  5. if c == nil {
  6. return "/usr/home"
  7. }
  8. return c.path
  9. }
  10. func main() {
  11. var c1 *Config
  12. var c2 = &Config{
  13. path: "/export",
  14. }
  15. fmt.Println(c1.Path(), c2.Path())
  16. }