认识GO语言中的nil,零值与空结构体
go语言的初学者,特别是java开发者新学习go语言,对于一些和java类似但是又有差异的概念很容易混淆,比如说go中的零值,nil 和 空结构体。本文就来详细探讨一下go中这些特殊概念的含义和实际场景中的应用:
零值
零值(The Zero Value)可以看作为当你声明了一个变量,但没有显式的初始化的时候,系统为变量赋予的一个默认初始值。官方对零值的定义如下:
When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, “” for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
据此我们可总结出:
-
值类型 布尔类型为 false, 数值类型为 0,字符串为”“,数组和结构体(struct)会递归初始化其元素或字段,即其初始值取决于元素或字段。这里所谓的值类型其实就相当于java中的 primary 类型,只是需要注意的是string在java中是对象类型,而go中string则是值类型。
-
引用类型 均为 nil,包括指针 pointer,函数 function,接口 interface,切片 slice,管道 channel,映射 map。
tip: 其实go里面没有真正的引用类型,可以粗略的理解为值类型的变量直接存储值,引用类型的变量存储的是一个地址,这个地址用于存储最终的值
值类型
因为有零值的存在,使得我们在使用变量时,大部分情况下可以不必进行初始化而直接使用,这样能够保持代码的简洁性,也能够尽量避免出现Java开发中常见的**NullPointerException,**以下是一些例子:
package mainimport "sync"type Value struct {mu sync.Mutex //无需初始化,声明就能用val int
}func (v *Value)Incr(){defer v.mu.Unlock()v.mu.Lock()v.val++
}func main() {var i Valuei.Incr()
}
sync.Mutex本质上是一个结构体:
// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {state int32sema uint32
}
那么如果是引用类型,零值为nil,是不是就不能直接用了呢?这个实际上也要分情况,按照类型我们一个个来看:
切片(Slices)
切片的零值是一个nil slice,除了不能按照序号索引查询以外,其它的操作都能做:
func testNilSlice() {var nilSlice []stringfmt.Println(nilSlice == nil) // truefmt.Println(nilSlice[0]) //index out of rangeemptySlice = append(nilSlice, "dd") // append操作会自动扩容fmt.Println(nilSlice[0]) //输出dd
}
nil slice与not nil slice的区别:
type Person {Friends []string
}var f1 []string //nil切片
json1, _ := json.Marshal(Person{Friends: f1})
fmt.Printf("%s\n", json1) //output:{"Friends": null}f2 := make([]string, 0) //non-nil空切片 ,等价于 f2 := []string{}
json2, _ := json.Marshal(Person{Friends: f2})
fmt.Printf("%s\n", json2) //output: {"Friends": []}
推荐在日常使用时,没有特殊需求都使用var nilSlice []string 这样的形式声明空切片:https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices
Map
对于nil的map,我们可以简单把它看成是一个只读的map,不能进行写操作,否则就会panic:
func testNilMap() {var m map[string]stringfmt.Println(m["key"]) //输出""m["key"]="value" //panic: assignment to entry in nil map
}
那么nil map有啥用呢,可以看看以下的例子:
func NewGet(url string, headers map[string]string) (*http.Request, error) {req, err := http.NewRequest(http.MethodGet, url, nil)if err != nil {return nil, err}for k, v := range headers {req.Header.Set(k, v)}return req, nil
}//调用该方法时如果没有header,可以传入一个空的map,例如:
NewGet("http://google.com", map[string]string{})
//也可以直接传入nil
NewGet("http://google.com", nil)
Channel
nil channel会阻塞对该channel的所有读、写。所以,可以将某个channel设置为nil,进行强制阻塞,对于select分支来说,就是强制禁用此分支
func addIntegers(c chan int) {sum := 0t := time.NewTimer(time.Second)for {select {case input := <-c:sum = sum + inputcase <-t.C:c = nilfmt.Println(sum) // 输出10}}
}func main() {c := make(chan int, 1)go addIntegers(c)for i := 0; i <= 10; i++ {c <- itime.Sleep(time.Duration(200) * time.Millisecond)}
}
指针(Pointers)
指针如果为nil,则对指针进行解引用的话,会引发我们在java中非常熟悉的空指针错误
type Person struct {Name stringSex stringAge int
}var p *Person
fmt.Println(p.Name) // panic: runtime error: invalid memory address or nil pointer dereference
神奇的nil
nil 是 Golang 中预先声明的标识符,其主要用来表示引用类型的零值(指针,接口,函数,映射,切片和通道),表示它们未初始化的值。
// [src/builtin/builtin.go](https://golang.org/src/builtin/builtin.go#L98)
//
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
nil在go语言里面不是一个关键字或者保留字,所以你可以用nil作为变量名(作死):
var nil = errors.New("my god")
nil没有默认的类型,所以不能给一个未声明类型的变量赋值,也不能和自己比较:
a := nil
// cannot declare variable as untyped nil: a
fmt.Println(nil == nil)
// invalid operation: nil == nil (operator == not defined on nil)
fmt.Printf("%T", nil)
// use of untyped nil
比较nil时一定要注意nil实际上是有类型的,不同类型的nil是不相等的,比如下面的例子:
var p *int
var i interface{}fmt.Println(p) // <nil>
fmt.Println(i) // <nil>fmt.Println(p == i) // false
再看一个在实际编码里面很容易犯的错误:
type BusinessError struct {errorerrorCode int64
}func doBusiness() *BusinessError {return nil
}func wrapDoBusiness() error {err := doBusiness()return err
}func testError() {err := wrapDoBusiness() //这里面拿到的本质上是一个<T:*BusinessError,V:nil>的nilfmt.Println(err == nil)
}
建议:如果任何地方有判断interface是否为 nil 值的逻辑,一定不要写任何有关于将interface赋值为具体实现类型(可能为nil)的代码,如果是 nil 值就直接赋给interface,而不要过具体类型的转换
type BusinessError struct {errorerrorCode int64
}func doBusiness() *BusinessError {return nil
}func wrapDoBusiness() error {err := doBusiness() if err == nil {return nil //如果返回值为nil,直接返回nil,不要做类型转换} else {return err}
}func testError() {err := wrapDoBusiness()fmt.Println(err == nil)
}
空结构体
golang 正常的 struct 就是普通的一个内存块,必定是占用一小块内存的,并且结构体的大小是要经过边界,长度的对齐的,但是“空结构体”是不占内存的,size 为 0;
var q struct{}
fmt.Println(unsafe.Sizeof(q)) // 0
空结构体 struct{ } 为什么会存在的核心理由就是为了节省内存。当你需要一个结构体,但是却丝毫不关系里面的内容,那么就可以考虑空结构体。以下是几个经典的用法:
map & struct{}
map 和 struct {} 一般的结合姿势是这样的:
// 创建 map
m := make(map[int]struct{})
// 赋值
m[1] = struct{}{}
// 判断 key 键存不存在
_, ok := m[1]
一般 map 和 struct {} 的结合使用场景是:只关心 key,不关注值。比如查询 key 是否存在就可以用这个数据结构,通过 ok 的值来判断这个键是否存在,map 的查询复杂度是 O(1) 的,查询很快。这种方式在部分场景下可以起到类似Java中Set的作用
chan & struct{}
channel 和 struct{} 结合是一个最经典的场景,struct{} 通常作为一个信号来传输,并不关注其中内容。chan 本质的数据结构是一个管理结构加上一个 ringbuffer ,如果 struct{} 作为元素的话,ringbuffer 就是 0 分配的。
chan 和 struct{} 结合基本只有一种用法,就是信号传递,空结构体本身携带不了值,所以也只有这一种用法啦,一般来说,配合 no buffer 的 channel 使用。
waitc := make(chan struct{})// ...
goroutine 1:// 发送信号: 投递元素waitc <- struct{}// 发送信号: 关闭close(waitc)goroutine 2:select {// 收到信号,做出对应的动作case <-waitc:}
相关文章:
认识GO语言中的nil,零值与空结构体
go语言的初学者,特别是java开发者新学习go语言,对于一些和java类似但是又有差异的概念很容易混淆,比如说go中的零值,nil 和 空结构体。本文就来详细探讨一下go中这些特殊概念的含义和实际场景中的应用: 零值 零值&…...
Node.js sqlite3:Statement对象详解
在Node.js的sqlite3库中,Statement对象是一个非常重要的概念。它代表了一个预编译的SQL语句,可以多次执行以提高性能。通过使用Statement对象,你可以避免重复解析和编译SQL语句的开销,特别是在需要频繁执行相同SQL语句的情况下。本…...
ELK学习笔记——如何给Kibana新增用户和角色
Kibana新增用户和角色 首先用超管账号登录上Kibana,按照下面步骤操作 1、创建角色 按图操作 2、创建用户 按图操作 3、给用户分配角色 至此,角色和用户绑定成功; 最后,可以退出管理员账号,登录这个新…...
Minikube Install Kubernetes v1.18.1
文章目录 简介安装工具配置代理运行集群检查集群加入rancher 简介 模拟客户环境,测试 kubernetes v1.18.x 是否可以被 rancher v2.9.1 纳管。 安装工具 docker 安装Install and Set Up kubectl on Linux 安装 minikube 配置代理 docker proxylinux proxy 运行…...
重修设计模式-创建型-工厂模式
重修设计模式-创建型-工厂模式 一、概述 工厂模式(Factory Pattern)是设计模式中非常基础且常用的一种模式,主要目的是通过封装对象的创建过程,从而实现代码的解耦和灵活性的提升。 工厂模式的核心思想 封装对象的创建&#x…...
使用Cskin时候 遇到按钮有默认阴影问题解决
使用Cskin时候 遇到按钮有默认阴影 设置 DrawType 属性就可以了...
121.rk3399 uboot(2017.09) 源码分析1(2024-09-05)
参考源码 : uboot(2017.09) 硬件平台:rk3399 辅助工具:linux虚拟机,sourceinsight4,文件浏览器(可以使用samba访问),ultraeidt(查看bin文件比较方便) 说明:…...
【图论】虚树 - 模板总结
适用于解决一棵树中只需要用到少部分点的时候,将需要用到的点提出来单独建一棵树 /********************* 虚树 *********************/ struct edge {int to, next;int val; };struct Virtual_Tree {int n; // 点数int dfn[N]; // dfs序int dep[N]; // 深度int fa…...
[C#学习笔记]注释
官方文档:Documentation comments - C# language specification | Microsoft Learn 一、常用标记总结 1.1 将文本设置为代码风格的字体:<c> 1.2 源代码或程序输出:<code> 1.3 异常指示:<exception> 1.4 段落 <para> 1.5 换行&…...
c# checkbox的text文字放到右边
checkbox的text文字放到右边 实现方法如下图 特此记录 anlog 2024年9月2日...
【node.js】基础之修改文件
node.js 基础(一) node.js是什么? 上面这句话的意思就是:Node.js 是一个开源的,跨平台的javascript运行环境。通俗的说就是一个应用程序或者说是一个软件,可以运行javascript。 Node.js的作用: 开发服务器应用。 将数…...
Notepad++回车不自动补全
问题 使用Notepad时,按回车经常自动补全,但我们希望回车进行换行,而不是自动补全,而且自动补全使用Tab进行补全足够了。下文介绍设置方法。 设置方法 打开Notepad,进入设置 - 首选项 - 自动完成,在插入选…...
CSS线性渐变拼接,一个完整的渐变容器(div),要拆分成多个渐变容器(div),并且保持渐变效果一致
1 需求 一个有渐变背景的div,需要替换成多个渐变背景div拼接,渐变效果需要保持一致(不通过一个大的div渐变,其他子的div绝对定位其上并且背景透明来解决) 2 分析 主要工作: 计算完整div背景线性渐变时的…...
【60天备战软考高级系统架构设计师——第十天:软件设计与架构综合练习】
经过前十天的学习,我们已经了解了软件工程生命周期模型、需求分析与管理方法,以及软件设计与架构的核心内容。为了巩固这些知识点,今天我们将进行一个综合练习。 前十天学习内容回顾 第1-3天:软件工程概述 学习了软件生命周期模…...
2024.8.15(python管理mysql、Mycat实现读写分离)
一、python管理mysql 1、搭建主mysql [rootmysql57 ~]# tar -xf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz [rootmysql57 ~]# cp -r mysql-5.7.44-linux-glibc2.12-x86_64 /usr/local/mysql [rootmysql57 ~]# rm -rf /etc/my.cnf [rootmysql57 ~]# mkdir /usr/local/mysql…...
CMU 10423 Generative AI:lec2
文章目录 1 概述2 部分摘录2.1 噪声信道模型(Noisy Channel Models)主要内容:公式解释:应用举例: 2.2 n-Gram模型1. 什么是n-Gram模型2. 早期的n-Gram模型3. Google n-Gram项目4. 模型规模与训练数据5. n-Gram模型的局…...
恋爱相亲交友系统源码原生源码可二次开发APP 小程序 H5,web全适配
直播互动:平台设有专门的直播间,允许房间主人与其他异性用户通过视频连线的方式进行一对一互动。语音视频交流:异性用户可以发起语音或视频通话,以增进了解和交流。群组聊天:用户能够创建群聊,邀请自己关注…...
OceanBase 4.x 存储引擎解析:如何让历史库场景成本降低50%+
据国际数据公司(IDC)的报告显示,预计到2025年,全球范围内每天将产生高达180ZB的庞大数据量,这一趋势预示着企业将面临着更加严峻的海量数据处理挑战。随着数据日渐庞大,一些存储系统会出现诸如存储空间扩展…...
js 如何写构造函数 ,构造函数和普通函数有什么区别
在 JavaScript 中,构造函数是一种特殊的函数,用于初始化一个新创建的对象。构造函数通常用来创建具有相似属性和方法的对象实例。构造函数的主要特点是在调用时使用 new 关键字,这样就会创建一个新对象,并将其原型设置为构造函数的…...
MySQL-进阶篇-锁(全局锁、表级锁、行级锁)
文章目录 1. 锁概述2. 全局锁2.1 介绍2.2 数据备份2.3 使用全局锁造成的问题 3. 表级锁3.1 表锁3.1.1 语法3.1.2 读锁3.1.3 写锁3.1.4 读锁和写锁的区别 3.2 元数据锁(Meta Data Lock,MDL)3.3 意向锁3.3.1 案例引入3.3.2 意向锁的分类 4. 行级…...
如何确保MVP.css样式一致性:完整的自动化测试策略指南
如何确保MVP.css样式一致性:完整的自动化测试策略指南 【免费下载链接】mvp MVP.css — Minimalist classless CSS stylesheet for HTML elements 项目地址: https://gitcode.com/gh_mirrors/mv/mvp MVP.css是一个极简主义的无类CSS样式表,专为HT…...
Linux 系统管理核心命令详解(软件包 + 进程 + 服务篇)
一、yum /apt:软件包管理(安装 / 卸载 / 更新)yum/dnf:用于 RHEL/CentOS/Rocky/AlmaLinux 等红帽系发行版apt/apt-get:用于 Ubuntu/Debian 等 Debian 系发行版作用:一键安装、卸载、更新软件,自…...
开源工具MediaCreationTool.bat一站式解决Windows系统安装全流程攻略
开源工具MediaCreationTool.bat一站式解决Windows系统安装全流程攻略 【免费下载链接】MediaCreationTool.bat Universal MCT wrapper script for all Windows 10/11 versions from 1507 to 21H2! 项目地址: https://gitcode.com/gh_mirrors/me/MediaCreationTool.bat 系…...
口碑好的动态压剪试验机哪个更优
在众多工业和科研领域,动态压剪试验机是一种至关重要的设备,它能够模拟各种复杂的力学环境,对材料和构件的性能进行精确测试。然而,面对市场上众多的品牌和型号,如何选择一款口碑好且性能优越的动态压剪试验机成为了许…...
2026降AI降重工具实测:高效过审首选方案推荐
2026年学术写作辅助工具的选择核心看四个维度:降重精准度、去AI痕迹效果、格式保留能力、学科适配性。经过多场景实测,SpeedAI科研小助手、飞降AI、超能降AI、快降AI、思笔AI是当前覆盖全需求的第一梯队工具,能满足从专科到硕博、从中文到英文…...
解决OpenCore EFI配置难题:OpCore-Simplify如何实现零门槛系统搭建
解决OpenCore EFI配置难题:OpCore-Simplify如何实现零门槛系统搭建 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 问题剖析:为…...
Windows Btrfs驱动:在Windows系统上使用Btrfs文件系统的完整专业指南
Windows Btrfs驱动:在Windows系统上使用Btrfs文件系统的完整专业指南 【免费下载链接】btrfs WinBtrfs - an open-source btrfs driver for Windows 项目地址: https://gitcode.com/gh_mirrors/bt/btrfs WinBtrfs是一个开源项目,旨在为Windows系统…...
智慧算力枢纽中心建设方案:从“烟囱林立”到“云网融合”的数字化重构(PPT)
摘要:本文基于《智慧算力枢纽中心建设方案》,深度剖析了在数字经济爆发式增长背景下,如何通过“云-网-端”一体化架构解决传统IT基础设施“资源孤岛、运维割裂、安全脆弱”的行业痛点。文章详细阐述了从传统服务器向全栈资源池化演进的技术路…...
如何破解网易云音乐加密限制?ncmdump让音乐文件自由播放
如何破解网易云音乐加密限制?ncmdump让音乐文件自由播放 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否遇到过这样的困扰:从网易云音乐下载的歌曲只能在特定客户端播放,无法在其他设备或播…...
CC324条提示词意外泄露——第31条让我出了一身冷汗
324条提示词意外泄露——第31条让我出了一身冷汗 原创 硅谷Alan Walker 硅谷Alan Walker 嘉妍Kea 2026年4月2日 02:47 美国 22人 在小说阅读器中沉浸阅读 当 AI 可以代替你发 Slack、fork 自己,人与 AI 的边界在哪里? src/constants/prompts.ts 57…...
