0

0

聊一聊Go语言中的零值,它有什么用?

Golang菜鸟

Golang菜鸟

发布时间:2023-08-08 16:23:01

|

1386人浏览过

|

来源于Golang菜鸟

转载

背景

哈喽,大家好,我是asong。今天与大家聊一聊Go语言中的零值。大学时期我是一名C语言爱好者,工作了以后感觉Go语言和C语言很像,所以选择了Go语言的工作,时不时就会把这两种语言的一些特性做个比较,今天要比较的就是零值特性。熟悉C语言的朋友知道在C语言中默认情况下不初始化局部变量。未初始化的变量可以包含任何值,其使用会导致未定义的行为;如果我们未初始局部变量,在编译时就会报警告 C4700,这个警告指示一个Bug,这个Bug可能导致程序中出现不可预测的结果或故障。而在Go语言就不会有这样的问题,Go语言的设计者吸取了在设计C语言时的一些经验,所以Go语言的零值规范如下:

以下内容来自官方blog:https://golang.org/ref/spec#The_zero_value

当通过声明或 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:

  • c 接收将永远阻塞
  • c 发送值到c 会永远阻塞
  • close(c) 关闭c 引发panic

关于零值不可用的场景先介绍这些,掌握这些才能在日常开发中减少写bug的频率。

总结

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

  • Go语言中所有变量或者值都有默认值,对程序的安全性和正确性起到了很重要的作用
  • Go语言中的一些标准库利用零值特性来实现,简化操作
  • 可以利用"零值可用"的特性可以提升代码的结构化、使代码更简单、更紧凑
  • 零值也不是万能的,有一些场景下零值是不可用的,开发时要注意

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

387

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

612

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

352

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

256

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

597

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

523

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

639

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

599

2023.09.22

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

8

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 4.4万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 2.5万人学习

Go 教程
Go 教程

共32课时 | 3.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号