认识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. 行级…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
Vue3 PC端 UI组件库我更推荐Naive UI
一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用,前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率,还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库(Naive UI、Element …...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...
