认识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. 行级…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...