Go语言实践[回顾]教程21--详解Go语言的空值、零值、nil

  • Go 语言中 零值、空值、nil 概念之我见
  • 基本数据类型的零值
  • 复合数据类型的空值
  • 通过实例体验零值、空值 nil 的差别
  • nil 的特殊性
  • 自动赋零值的意义

  在很多编程书籍和教程当中,我们经常看到 零值、空值 这类描述,还经常看到 null、nil 这类的值定义。那么在 Go 语言里,零值、空值、nil 又确切的代表什么呢,我们就详细描述一下。

Go 语言中 零值、空值、nil 概念之我见

  ▲ 零值:如果是整数类型,那么零值就很好理解,那就是 0。但是其他类型也都有零值的说法,那该如何定义 零值 的概念呢。针对 Go 语言,可以这样定义,就是基础数据类型变量声明后但没有做初始化时系统默认给设置的那个值。

  ▲ 空值:很多文章书籍将空值描述为等同零值,这点本人在语义上略有不同观点。虽然把零值和空值等同化并不影响代码开发,但是对初学者对深入理解代码经常会造成障碍。个人认为,空值应该是用于描述复合数据类型变量在声明后但没有初始化时的值,也就是这个复合数据类型里面没有任何成员,这个集合是个空的。

  ▲ nil:Go 语言的 nil 经常会被理解成其他语言中的 null,但是它们是有区别的。在其他大部分语言里,null 代表不存在,拿变量来说就是这个变量根本不存在,没有这个变量。而 Go 语言的 nil 不是这样,它是指这个变量在,但没有任何值(内容),包括零值也没有。又因为 Go 语言在声明基本数据类型的变量时,在没有给定初始化值的情况下会自动初始化为该类型的零值,那么针对数据类型来说,只有复合数据类型才会有 nil 的情况,所以 nil 其实就是空值的意思。

基本数据类型的零值

数据类型 零值
数值型(整型、浮点、复数) 0
字符串 “”(空字符串)
布尔型 false

复合数据类型的空值

数据类型 零值
slice(切片) nil
map(映射) nil
pointer(指针) nil
channel(通道) nil
func(函数) nil
interface(接口) nil

  上表中的 channel、func、interface 后面章节会重点描述,这里先不用去仔细理解。但是大家可能会问,为什么没有数组?因为 Go 语言的数组不支持动态增加元素,而数组的动态操作都是通过切片完成的,所以声明一个没有任何元素的空数组没有实际应用意义。

通过实例体验零值、空值 nil 的差别

// test01 项目的 main 包,文件名 main.go
package mainimport ("fmt"
)// 主函数,程序入口
func main() {var a intvar b float32var c complex64var d stringvar e boolvar f []intvar g map[string]intvar h *intvar i chan intvar j func()var k interface{}fmt.Println("整数型零值:", a)fmt.Println("浮点型零值:", b)fmt.Println("复数型零值:", c)fmt.Println("字符串零值:", d)fmt.Println("字符串零值:", e)fmt.Println()fmt.Println("切片空值:", f, " 是否等于nil:", f == nil)fmt.Println("映射空值:", g, " 是否等于nil:", g == nil)fmt.Println("指针空值:", h, " 是否等于nil:", h == nil)fmt.Println("通道空值:", i, " 是否等于nil:", i == nil)fmt.Println("函数空值:", j, " 是否等于nil:", j == nil)fmt.Println("接口空值:", k, " 是否等于nil:", k == nil)
}

  上述代码编译运行结果如下:

整数型零值: 0
浮点型零值: 0
复数型零值: (0+0i)
字符串零值:
字符串零值: false切片空值: []  是否等于nil: true
映射空值: map[]  是否等于nil: true
指针空值: <nil>  是否等于nil: true
通道空值: <nil>  是否等于nil: true
函数空值: <nil>  是否等于nil: true
接口空值: <nil>  是否等于nil: true

  从上面编译运行结果可以清楚的看出,零值与空值的区别。基本数据类型的零值就是 0 或 该类型的可以理解为 0 的值,它不能与 nil 进行比较,会报错。而复合数据类型的空值输出也有标志符,但没有任何成员,且与 nil 比较也相等。所以用空值描述复合数据类型未初始化时的值更贴切、易懂,也就是 nil。

nil 的特殊性

  ▲ nil 不是数据类型,是某个复合类型的空值标识符,所以它也不是一个固定的类型,不同类型的 nil 值变量之间无法比较。所以下面的代码是错误的:

fmt.Println(nil==nil)  // nil 没有固定的类型,只有与声明了空值的复合数据类型变量才可以与 nil 比较

  下面的示例也将无法正常运行:

// test01 项目的 main 包,文件名 main.go
package mainimport ("fmt"
)// 主函数,程序入口
func main() {var a []intvar b map[string]intfmt.Println(a == b)   // 即使都是空值 nil,但因类型不同无法比较
}

  上述代码最后一样输出语句会报错,属于不同类型的值在比较,这是不允许的。

  下面的示例也将无法正常运行:

// test01 项目的 main 包,文件名 main.go
package mainimport ("fmt"
)// 主函数,程序入口
func main() {var a []intvar b []intfmt.Println(a == b)   // 即使类型相同,两个空值也无法比较
}

  上述代码最后一样输出语句会报错,属于同类型的两个空值在比较,在 Go 语言中,两个空值 nil 的变量是不能进行比较的,这也是与零值的区别。

  ▲ nil 值的变量是不能直接赋值操作的,因为还没有为其在内存中初始化保存数据的区块。所以直接写入操作会引发错误。如下面的示例无法正产运行:

// test01 项目的 main 包,文件名 main.go
package mainimport ("fmt"
)// 主函数,程序入口
func main() {var m map[string]intm["asong"] = 5 // m 还没有显示声明,所以无法赋值操作fmt.Println(m)
}

  上述代码无法编译通过,而下面的代码就可以正常编译运行:

// test01 项目的 main 包,文件名 main.go
package mainimport ("fmt"
)// 主函数,程序入口
func main() {m := make(map[string]int, 8)m["asong"] = 5 // m 还没有显示声明,所以无法赋值操作fmt.Println(m)
}

  后面这个示例可以正常编译运行的原因,是将声明改成了使用 make 函数,make 函数在声明的同时为变量进行了初始化内存,所以可以正常赋值操作了。这里也看出了使用 make 声明的与直接声明的区别,以及零值空值有很多不同。

自动赋零值的意义

  为了提高程序的安全性、正确性、书写效率,简化编程者操作,Go 语言提供了基础数据类型默认零值的特性。同时零值是可以使用的,是代码可以更简单、紧凑,无需显示初始化,开箱即用。零值还可以作为判断依据,来识别变量的赋值状态。
.
.
上一节:Go语言实践[回顾]教程20–详解Go语言复合数据类型之映射 map

下一节:Go语言实践[回顾]教程22–详解Go语言的流程控制
.

Go语言实践[回顾]教程21--详解Go语言的空值、零值、nil相关推荐

  1. Go语言实践[回顾]教程23--详解Go语言函数的声明、变参、参数传递

    Go语言实践[回顾]教程23--详解Go语言函数的声明.变参.参数传递 函数的声明(定义) 函数的基本声明格式与调用 函数的变参(不定参) 值传递还是引用地址传递   函数是 Go 语言源代码的基本构 ...

  2. Go语言实践[回顾]教程15--详解Go语言的基本数据类型

    Go语言实践[回顾]教程15--详解Go语言的基本数据类型 布尔型(bool) 整数型(int) 浮点型(float) 复数型(complex) 字符串型(string) 字符型(byte / run ...

  3. Go语言实践[回顾]教程03--Go语言的编译与运行的命令行

    Go语言实践[回顾]教程03--Go语言的编译与运行的命令行 Go语言是编译型静态语言 如何编译Go语言的源文件 如何执行(运行)编译后的文件 开发中如何编译后立即执行 总结 Go语言是编译型静态语言 ...

  4. Go语言实践[回顾]教程06--通过时间判断时辰的示例【上】

    Go语言实践[回顾]教程06--通过时间判断时辰的示例[上] 示例项目的需求 实现示例需求的源代码 使用 if 判断逻辑实现的源代码 使用 if else if 判断逻辑实现的源代码 使用 switc ...

  5. Go语言实践[回顾]教程09--学习成绩统计的示例【上】

    Go语言实践[回顾]教程09--学习成绩统计的示例[上] 在数组格式成绩数据中统计及格人数和及格率并取出前三名 创建只有成绩分数的数据源码文件 创建用于统计以 int 数组为数据源的源码文件 修改主文 ...

  6. Go语言实践[回顾]教程08--通过时间判断时辰的示例【下】

    Go语言实践[回顾]教程08--通过时间判断时辰的示例[下] 封装函数向模块化开发方向修改 使用单个返回值函数的源代码 使用多个返回值函数的源代码 使用命名返回值函数的源代码 分离文件继续模块化实践 ...

  7. Go语言实践[回顾]教程10--学习成绩统计的示例【中】

    Go语言实践[回顾]教程10--学习成绩统计的示例[中] 基于整体需求优化上节代码 需求增加按等级分类统计 本节小结 基于整体需求优化上节代码   在上一节中,是基于三个基本需求各自独立实现的逻辑,创 ...

  8. Go语言实践[回顾]教程11--学习成绩统计的示例【下】

    Go语言实践[回顾]教程11--学习成绩统计的示例[下] 需求未变但源数据结构改为 map 类型 创建 map 类型的数据源文件 创建操作 map 类型数据进行统计排序的源文件 本节小结 需求未变但源 ...

  9. c语言程序设计中北答案详解,C语言程序设计试题及答案解析汇编.doc

    C语言程序设计试题及答案解析汇编 C语言程序设计试题 第1.2.3章 概述.类型.表达式 一.选择题 一个C程序由若干个C函数组成,各个函数在文件中的位置顺序为:( ) 任意 第一个函数必须是主函数, ...

最新文章

  1. SAP Cloud for Customer Price-计价简介
  2. 4.7 CNN 特征可视化-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  3. Golang经典面试题下
  4. 隐马尔科夫模型原理解析
  5. UDP打洞NAT大致分为下面四类 P2P
  6. 1分钟 搭建xxl-job任务调度中心
  7. 如何在有限的时间内编写完整有效的测试用例?
  8. C语言求斐波那契数列前10项
  9. java毕业设计选题之《校园管理、教育教学类》
  10. 基于Berkeley DB实现的持久化队列
  11. 阿里云移动热修复Sophix问题汇总
  12. Python实现淘宝准点抢单!双十一秒杀神器啊!还不来学?
  13. SpringBoot+H5微信登陆(网页)
  14. Android-手撸抖音“潜艇大挑战”,非科班面试之旅
  15. python查询12306余票_使用 Python 在 12306 查询火车票余票
  16. intel无线网卡的型号和驱动一览
  17. pygame 游戏开场动画渲染学习,绘制 10*7=70 个小方块
  18. Unity 常见英文单词
  19. 史克鲁克唱诗班-我最爱的!
  20. 本机修改虚拟机linux中的代码文件

热门文章

  1. Android中LayoutParams类精炼详解
  2. 无法打开物理文件 XXX.mdf。操作系统错误 5:5(拒绝访问。)的解决办法
  3. C++ explicit 用法
  4. mysql 内部xa_MySQL-XA事务(一)简介
  5. JXTA 点对点管道通信
  6. 计算机系网络中的知识点,计算机网络重要知识点.doc
  7. android源码目录结构(2.1版本和2.2版本)
  8. 开云集团旗下巴黎世家、圣罗兰将生产口罩
  9. 免费获取某个城市或者地区的 天气信息(温度,湿度,风力)
  10. STM32 TIM PWM中阶操作:互补PWM输出