乐趣区

关于golang:聊一聊Go语言中的零值它有什么用

背景

哈喽,大家好,我是 asong。明天与大家聊一聊 Go 语言中的零值。大学期间我是一名C 语言爱好者,工作了当前感觉 Go 语言和 C 语言很像,所以抉择了 Go 语言的工作,时不时就会把这两种语言的一些个性做个比拟,明天要比拟的就是零值个性。相熟 C 语言的敌人晓得在 C 语言中默认状况下不初始化局部变量。未初始化的变量能够蕴含任何值,其应用会导致未定义的行为;如果咱们未初始局部变量,在编译时就会报正告 C4700,这个正告批示一个 Bug,这个Bug 可能导致程序中呈现不可预测的后果或故障。而在 Go 语言就不会有这样的问题,Go 语言的设计者汲取了在设计 C 语言时的一些教训,所以 Go 语言的零值标准如下:

以下内容来自官网 blog:https://golang.org/ref/spec#T…

当通过申明或 new 调用为变量调配存储空间时,或通过复合文字或 make 调用创立新值时,且未提供显式初始化,则给出变量或值一个默认值。此类变量或值的每个元素都为其类型设置为零值:布尔型为 false,数字类型为 0,字符串为“”,指针、函数、接口、切片、通道和映射为 nil。此初始化是递归实现的,例如,如果未指定任何值,则构造体数组的每个元素的字段都将其清零。

例如这两个简略的申明是等价的:

var i int 
var i int = 0

在或者这个构造体的申明:

type T struct {i int; f float64; next *T}
t := new(T)

这个构造体 t 中成员字段零值如下:

t.i == 0
t.f == 0.0
t.next == nil

Go语言中这种始终将值设置为已知默认值的个性对于程序的安全性和正确性起到了很重要的作用,这样也使整个 Go 程序更简略、更紧凑。

零值有什么用

通过零值来提供默认值

咱们在看一些 Go 语言库的时候,都会看到在初始化对象时采纳 ” 动静初始化 ” 的模式,其实就是在创建对象时判断如果是零值就应用默认值,比方咱们在剖析 hystrix-go 这个库时,在配置 Command 时就是应用的这种形式:

func ConfigureCommand(name string, config CommandConfig) {settingsMutex.Lock()
    defer settingsMutex.Unlock()

    timeout := DefaultTimeout
    if config.Timeout != 0 {timeout = config.Timeout}

    max := DefaultMaxConcurrent
    if config.MaxConcurrentRequests != 0 {max = config.MaxConcurrentRequests}

    volume := DefaultVolumeThreshold
    if config.RequestVolumeThreshold != 0 {volume = config.RequestVolumeThreshold}

    sleep := DefaultSleepWindow
    if config.SleepWindow != 0 {sleep = config.SleepWindow}

    errorPercent := DefaultErrorPercentThreshold
    if config.ErrorPercentThreshold != 0 {errorPercent = config.ErrorPercentThreshold}

    circuitSettings[name] = &Settings{Timeout:                time.Duration(timeout) * time.Millisecond,
        MaxConcurrentRequests:  max,
        RequestVolumeThreshold: uint64(volume),
        SleepWindow:            time.Duration(sleep) * time.Millisecond,
        ErrorPercentThreshold:  errorPercent,
    }
}

通过零值判断进行默认值赋值,加强了 Go 程序的健壮性。

开箱即用

为什么叫开箱即用呢?因为 Go 语言的零值让程序变得更简略了,有些场景咱们不须要显示初始化就能够间接用,举几个例子:

  • 切片,他的零值是 nil,即便不必make 进行初始化也是能够间接应用的,例如:
package main

import (
    "fmt"
    "strings"
)

func main() {var s []string

    s = append(s, "asong")
    s = append(s, "真帅")
    fmt.Println(strings.Join(s, " "))
}

然而零值也并不是万能的,零值切片不能间接进行赋值操作:

var s []string
s[0] = "asong 真帅"

这样的程序就报错了。

  • 办法接收者的演绎

利用零值可用的个性,咱们配合空构造体的办法接受者个性,能够将办法组合起来,在业务代码中便于后续扩大和保护:

type T struct{}

func (t *T) Run() {fmt.Println("we run")
}

func main() {
  var t T
  t.Run()}

我在一些开源我的项目中看到很多中央都这样应用了,这样的代码最结构化~。

  • 规范库无需显示初始化

咱们常常应用 sync 包中的 mutexoncewaitgroup 都是无需显示初始化即可应用,拿 mutex 包来举例说明,咱们看到 mutex 的构造如下:

type Mutex struct {
    state int32
    sema  uint32
}

这两个字段在未显示初始化时默认零值都是0,所以咱们就看到上锁代码就针对这个个性来写的:

func (m *Mutex) Lock() {
    // Fast path: grab unlocked mutex.
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        if race.Enabled {race.Acquire(unsafe.Pointer(m))
        }
        return
    }
    // Slow path (outlined so that the fast path can be inlined)
    m.lockSlow()}

原子操作替换时应用的 old 值就是 0,这种设计让mutex 调用者无需思考对 mutex 的初始化则能够间接应用。

还有一些其余规范库也应用零值可用的个性,应用办法都一样,就不在举例了。

零值并不是万能

Go语言零值的设计大大便当了开发者,然而零值并不是万能的,有些场景下零值是不能够间接应用的:

  • 未显示初始化的切片、map,他们能够间接操作,然而不能写入数据,否则会引发程序 panic:
var s []string
s[0] = "asong"
var m map[string]bool
m["asong"] = true

这两种写法都是谬误的应用。

  • 零值的指针

零值的指针就是指向 nil 的指针,无奈间接进行运算,因为是没有无内容的地址:

var p *uint32
*p++ // panic: panic: runtime error: invalid memory address or nil pointer dereference

这样才能够:

func main() {
    var p *uint64
    a := uint64(0)
    p = &a
    *p++
    fmt.Println(*p) // 1
}
  • 零值的 error 类型

error 内置接口类型是示意谬误条件的惯例接口,nil 值示意没有谬误,所以调用 Error 办法时类型 error 不能是零值,否则会引发panic

func main() {rs := res()
    fmt.Println(rs.Error())
}

func res() error {return nil}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6f27]
  • 闭包中的 nil 函数

在日常开发中咱们会应用到闭包,然而这其中暗藏一个问题,如果咱们函数遗记初始化了,那么就会引发panic

var f func(a,b,c int)

func main(){f(1,2,3) // panic: runtime error: invalid memory address or nil pointer dereference
}
  • 零值 channels

咱们都晓得 channels 的默认值是nil,给定一个nil channel c:

  • <-cc 接管将永远阻塞
  • c <- v 发送值到c 会永远阻塞
  • close(c) 敞开c 引发panic

对于零值不可用的场景先介绍这些,把握这些能力在日常开发中缩小写 bug 的频率。

总结

总结一下本文叙述的几个知识点:

  • Go语言中所有变量或者值都有默认值,对程序的安全性和正确性起到了很重要的作用
  • Go语言中的一些规范库利用零值个性来实现,简化操作
  • 能够利用 ” 零值可用 ” 的个性能够晋升代码的结构化、使代码更简略、更紧凑
  • 零值也不是万能的,有一些场景下零值是不可用的,开发时要留神

欢送关注公众号:【Golang 梦工厂】

举荐往期文章:

  • 学习 channel 设计:从入门到放弃
  • 详解内存对齐
  • 警觉请勿滥用 goroutine
  • 源码分析 panic 与 recover,看不懂你打我好了!
  • 面试官:小松子来聊一聊内存逃逸
  • 面试官:两个 nil 比拟后果是什么?
  • Go 语言如何操纵 Kafka 保障无音讯失落
退出移动版