福州网站制作官网,昆山网络公司哪家比较好,网站网络营销公司,怎么把网站改为正在建设中Go语言数组详解 var 数组变量名 [元素数量]Type 数组变量名#xff1a;数组声明及使用时的变量名。 元素数量#xff1a;数组的元素数量#xff0c;可以是一个表达式#xff0c;但最终通过编译期计算的结果必须是整型数值#xff0c;元素数量不能含有到运行时才能确认大小… Go语言数组详解 var 数组变量名 [元素数量]Type 数组变量名数组声明及使用时的变量名。 元素数量数组的元素数量可以是一个表达式但最终通过编译期计算的结果必须是整型数值元素数量不能含有到运行时才能确认大小的数值。 Type可以是任意基本类型包括数组本身类型为数组本身时可以实现多维数组。 var a [3]int // 定义三个整数的数组
fmt.Println(a[0]) // 打印第一个元素
fmt.Println(a[len(a)-1]) // 打印最后一个元素
// 打印索引和元素
for i, v : range a {fmt.Printf(%d %d\n, i, v)
}
// 仅打印元素
for _, v : range a {fmt.Printf(%d\n, v)
} 默认情况下数组的每个元素都会被初始化为元素类型对应的零值对于数字类型来说就是 0同时也可以使用数组字面值语法用一组值来初始化数组 var q [3]int [3]int{1, 2, 3}
var r [3]int [3]int{1, 2}
fmt.Println(r[2]) // 0 在数组的定义中如果在数组长度的位置出现“...”省略号则表示数组的长度是根据初始化值的个数来计算因此上面数组 q 的定义可以简化为 q : [3]int{1, 2, 3}
q [4]int{1, 2, 3, 4} // 编译错误无法将 [4]int 赋给 [3]int 如果两个数组类型相同包括数组的长度数组中元素的类型的情况下我们可以直接通过较运算符和!来判断两个数组是否相等只有当两个数组的所有元素都是相等的时候数组才是相等的不能比较两个类型不同的数组否则程序将无法完成编译 a : [2]int{1, 2}
b : [...]int{1, 2}
c : [2]int{1, 3}
fmt.Println(a b, a c, b c) // true false false
d : [3]int{1, 2}
fmt.Println(a d) // 编译错误无法比较 [2]int [3]int var team [3]string
team[0] hammer
team[1] soldier
team[2] mum
for k, v : range team {fmt.Println(k, v)
} Go语言的多维数组 var array_name size1...[sizen] array_type array_name 为数组的名字array_type 为数组的类型size1、size2 等等为数组每一维度的长度。 // 声明一个二维整型数组两个维度的长度分别是 4 和 2
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化数组中索引为 1 和 3 的元素
array [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化数组中指定的元素
array [4][2]int{1: {0: 20}, 3: {1: 41}} 只要类型一致就可以将多维数组互相赋值如下所示多维数组的类型包括每一维度的长度以及存储在元素中数据的类型 // 声明两个二维整型数组
var array1 [2][2]int
var array2 [2][2]int
// 为array2的每个元素赋值
array2[0][0] 10
array2[0][1] 20
array2[1][0] 30
array2[1][1] 40
// 将 array2 的值复制给 array1
array1 array2 因为数组中每个元素都是一个值所以可以独立复制某个维度如下所示。 // 将 array1 的索引为 1 的维度复制到一个同类型的新数组里
var array3 [2]int array1[1]
// 将数组中指定的整型值复制到新的整型变量里
var value int array1[1][0] Go语言切片 Go 数组的长度不可改变在特定场景中这样的集合就不太适用Go 中提供了一种灵活功能强悍的内置类型切片(动态数组)与数组相比切片的长度是不固定的可以追加元素在追加时可能使切片的容量增大。 var identifier []type 切片不需要说明长度。或使用 make() 函数来创建切片: var slice1 []type make([]type, len) 也可以简写为 slice1 : make([]type, len) 也可以指定容量其中 capacity 为可选参数。 make([]T, length, capacity) 这里 len 是数组的长度并且也是切片的初始长度。 切片初始化 s :[] int {1,2,3 } 直接初始化切片[] 表示是切片类型{1,2,3} 初始化值依次是 1,2,3其 caplen3。 s : arr[:] 初始化切片 s是数组 arr 的引用。 s : arr[startIndex:endIndex] 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。 s : arr[startIndex:] 默认 endIndex 时将表示一直到arr的最后一个元素。 s : arr[:endIndex] 默认 startIndex 时将表示从 arr 的第一个元素开始。 s1 : s[startIndex:endIndex] 通过切片 s 初始化切片 s1。 s :make([]int,len,cap) 通过内置函数 make() 初始化切片s[]int 标识为其元素类型为 int 的切片。 len() 和 cap() 函数 切片是可索引的并且可以由 len() 方法获取长度。 切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。 package main
import fmt
func main() {var numbers make([]int,3,5)
printSlice(numbers)
}
func printSlice(x []int){fmt.Printf(len%d cap%d slice%v\n,len(x),cap(x),x)
} 空(nil)切片 一个切片在未初始化之前默认为 nil长度为 0实例如下 package main
import fmt
func main() {var numbers []int
printSlice(numbers)
if(numbers nil){fmt.Printf(切片是空的)}
}
func printSlice(x []int){fmt.Printf(len%d cap%d slice%v\n,len(x),cap(x),x)
} 切片截取 可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]实例如下 package main
import fmt
func main() {/* 创建切片 */numbers : []int{0,1,2,3,4,5,6,7,8} printSlice(numbers)
/* 打印原始切片 */fmt.Println(numbers , numbers)
/* 打印子切片从索引1(包含) 到索引4(不包含)*/fmt.Println(numbers[1:4] , numbers[1:4])
/* 默认下限为 0*/fmt.Println(numbers[:3] , numbers[:3])
/* 默认上限为 len(s)*/fmt.Println(numbers[4:] , numbers[4:])
numbers1 : make([]int,0,5)printSlice(numbers1)
/* 打印子切片从索引 0(包含) 到索引 2(不包含) */number2 : numbers[:2]printSlice(number2)
/* 打印子切片从索引 2(包含) 到索引 5(不包含) */number3 : numbers[2:5]printSlice(number3)
}
func printSlice(x []int){fmt.Printf(len%d cap%d slice%v\n,len(x),cap(x),x)
} append() 和 copy() 函数 如果想增加切片的容量我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。 在使用 append() 函数为切片动态添加元素时如果空间不足以容纳足够多的元素切片就会进行“扩容”此时新切片的长度会发生改变。 切片在扩容时容量的扩展规律是按容量的 2 倍数进行扩充例如 1、2、4、8、16…… package main
import fmt
func main() {var numbers []intprintSlice(numbers)
/* 允许追加空切片 */numbers append(numbers, 0)printSlice(numbers)
/* 向切片添加一个元素 */numbers append(numbers, 1)printSlice(numbers)
/* 同时添加多个元素 */numbers append(numbers, 2,3,4)printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/numbers1 : make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */copy(numbers1,numbers)printSlice(numbers1)
}
func printSlice(x []int){fmt.Printf(len%d cap%d slice%v\n,len(x),cap(x),x)
} 除了在切片的尾部追加我们还可以在切片的开头添加元素 var a []int{1,2,3}
a append([]int{0}, a...) // 在开头添加1个元素
a append([]int{-3,-2,-1}, a...) // 在开头添加1个切片 在切片开头添加元素一般都会导致内存的重新分配而且会导致已有元素全部被复制 1 次因此从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。 因为 append 函数返回新切片的特性所以切片也支持链式操作我们可以将多个 append 操作组合起来实现在切片中间插入元素 var a []int
a append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片 每个添加操作中的第二个 append 调用都会创建一个临时切片并将 a[i:] 的内容复制到新创建的切片中然后将临时创建的切片再追加到 a[:i] 中。 Go语言copy()切片复制切片拷贝 copy( destSlice, srcSlice []T) int 其中 srcSlice 为数据来源切片destSlice 为复制的目标也就是将 srcSlice 复制到 destSlice目标切片必须分配过空间且足够承载复制的元素个数并且来源和目标的类型必须一致copy() 函数的返回值表示实际发生复制的元素个数。 slice1 : []int{1, 2, 3, 4, 5}
slice2 : []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置 然通过循环复制切片元素更直接不过内置的 copy() 函数使用起来更加方便copy() 函数的第一个参数是要复制的目标 slice第二个参数是源 slice两个 slice 可以共享同一个底层数组甚至有重叠也没有问题。 Go语言从切片中删除元素 从开头位置删除 a []int{1, 2, 3}
a a[1:] // 删除开头1个元素
a a[N:] // 删除开头N个元素 也可以不移动数据指针但是将后面的数据向开头移动可以用 append 原地完成所谓原地完成是指在原有的切片数据对应的内存区间内完成不会导致内存空间结构的变化 a []int{1, 2, 3}
a append(a[:0], a[1:]...) // 删除开头1个元素
a append(a[:0], a[N:]...) // 删除开头N个元素 还可以用 copy() 函数来删除开头的元素 a []int{1, 2, 3}
a a[:copy(a, a[1:])] // 删除开头1个元素
a a[:copy(a, a[N:])] // 删除开头N个元素 从中间位置删除 对于删除中间的元素需要对剩余的元素进行一次整体挪动同样可以用 append 或 copy 原地完成 a []int{1, 2, 3, ...}
a append(a[:i], a[i1:]...) // 删除中间1个元素
a append(a[:i], a[iN:]...) // 删除中间N个元素
a a[:icopy(a[i:], a[i1:])] // 删除中间1个元素
a a[:icopy(a[i:], a[iN:])] // 删除中间N个元素 从尾部删除 a []int{1, 2, 3}
a a[:len(a)-1] // 删除尾部1个元素
a a[:len(a)-N] // 删除尾部N个元素 删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况 Go语言range关键字循环迭代切片 // 创建一个整型切片并赋值
slice : []int{10, 20, 30, 40}
// 迭代每一个元素并显示其值
for index, value : range slice {fmt.Printf(Index: %d Value: %d\n, index, value)
} 需要强调的是range 返回的是每个元素的副本而不是直接返回对该元素的引用 // 创建一个整型切片并赋值
slice : []int{10, 20, 30, 40}
// 迭代每个元素并显示值和地址
for index, value : range slice {fmt.Printf(Value: %d Value-Addr: %X ElemAddr: %X\n, value, value, slice[index])
}
Value: 10 Value-Addr: 10500168 ElemAddr: 1052E100
Value: 20 Value-Addr: 10500168 ElemAddr: 1052E104
Value: 30 Value-Addr: 10500168 ElemAddr: 1052E108
Value: 40 Value-Addr: 10500168 ElemAddr: 1052E10C 因为迭代返回的变量是一个在迭代过程中根据切片依次赋值的新变量所以 value 的地址总是相同的要想获取每个元素的地址需要使用切片变量和索引值 如果不需要索引值也可以使用下划线_来忽略这个值 // 创建一个整型切片并赋值
slice : []int{10, 20, 30, 40}
// 迭代每个元素并显示其值
for _, value : range slice {fmt.Printf(Value: %d\n, value)
} Go语言多维切片简述 var sliceName ...[]sliceType 其中sliceName 为切片的名字sliceType为切片的类型每个[ ]代表着一个维度切片有几个维度就需要几个[ ] //声明一个二维切片
var slice [][]int
//为二维切片赋值
slice [][]int{{10}, {100, 200}} // 声明一个二维整型切片并赋值
slice : [][]int{{10}, {100, 200}} Go语言mapGo语言映射 Go语言中 map 是一种特殊的数据结构一种元素对pair的无序集合pair 对应一个 key索引和一个 value值所以这个结构也称为关联数组或字典这是一种能够快速寻找值的理想结构给定 key就可以迅速找到对应的 value。 map 是引用类型可以使用如下方式声明 var mapname map[keytype]valuetype mapname 为 map 的变量名。 keytype 为键类型。 valuetype 是键对应的值类型。 在声明的时候不需要知道 map 的长度因为 map 是可以动态增长的未初始化的 map 的值是 nil使用函数 len() 可以获取 map 中 pair 的数目。 package main
import fmt
func main() {var mapLit map[string]int//var mapCreated map[string]float32var mapAssigned map[string]intmapLit map[string]int{one: 1, two: 2}mapCreated : make(map[string]float32)mapAssigned mapLitmapCreated[key1] 4.5mapCreated[key2] 3.14159mapAssigned[two] 3fmt.Printf(Map literal at \one\ is: %d\n, mapLit[one])fmt.Printf(Map created at \key2\ is: %f\n, mapCreated[key2])fmt.Printf(Map assigned at \two\ is: %d\n, mapLit[two])fmt.Printf(Map literal at \ten\ is: %d\n, mapLit[ten])
} mapCreated 的创建方式mapCreated : make(map[string]float)等价于mapCreated : map[string]float{}。 注意可以使用 make()但不能使用 new() 来构造 map如果错误的使用 new() 分配了一个引用对象会获得一个空引用的指针相当于声明了一个未初始化的变量并且取了它的地址 mapCreated : new(map[string]float) 接下来当我们调用mapCreated[key1] 4.5的时候编译器会报错 invalid operation: mapCreated[key1] (index of type *map[string]float). map容量 和数组不同map 可以根据新增的 key-value 动态的伸缩因此它不存在固定长度或者最大限制但是也可以选择标明 map 的初始容量 capacity make(map[keytype]valuetype, cap) 当 map 增长到容量上限的时候如果再增加新的 key-valuemap 的大小会自动加 1所以出于性能的考虑对于大的 map 或者会快速扩张的 map即使只是大概知道容量也最好先标明。 noteFrequency : map[string]float32 {
C0: 16.35, D0: 18.35, E0: 20.60, F0: 21.83,
G0: 24.50, A0: 27.50, B0: 30.87, A4: 440} 用切片作为 map 的值 既然一个 key 只能对应一个 value而 value 又是一个原始类型那么如果一个 key 要对应多个值怎么办例如当我们要处理 unix 机器上的所有进程以父进程pid 为整形作为 key所有的子进程以所有子进程的 pid 组成的切片作为 value。通过将 value 定义为 []int 类型或者其他类型的切片就可以优雅的解决这个问题示例代码如下所示 mp1 : make(map[int][]int)
mp2 : make(map[int]*[]int) Go语言遍历map访问map中的每一个键值对 map 的遍历过程使用 for range 循环完成代码如下 scene : make(map[string]int)
scene[route] 66
scene[brazil] 4
scene[china] 960
for k, v : range scene {fmt.Println(k, v)
} 如只遍历值可以使用下面的形式 将不需要的键使用_改为匿名变量形式 for _, v : range scene { 只遍历键时使用下面的形式 for k : range scene { 遍历输出元素的顺序与填充顺序无关不能期望 map 在遍历时返回某种期望顺序的结果如果需要特定顺序的遍历结果正确的做法是先排序代码如下 scene : make(map[string]int)
// 准备map数据
scene[route] 66
scene[brazil] 4
scene[china] 960
// 声明一个切片保存map数据
var sceneList []string
// 将map数据遍历复制到切片中
for k : range scene {sceneList append(sceneList, k)
}
// 对切片进行排序
sort.Strings(sceneList)
// 输出
fmt.Println(sceneList) 输出结果[brazil china route] Go语言map元素的删除和清空 使用 delete() 函数从 map 中删除键值对 使用 delete() 内建函数从 map 中删除一组键值对delete() 函数的格式如下 delete(map, 键) 其中 map 为要删除的 map 实例键为要删除的 map 中键值对的键。 scene : make(map[string]int)
// 准备map数据
scene[route] 66
scene[brazil] 4
scene[china] 960
delete(scene, brazil)
for k, v : range scene {fmt.Println(k, v)
}
//输出结果
route 66
china 960 清空 map 中的所有元素 Go语言中并没有为 map 提供任何清空所有元素的函数、方法清空 map 的唯一办法就是重新 make 一个新的 map不用担心垃圾回收的效率Go语言中的并行垃圾回收效率比写一个清空函数要高效的多。 Go语言map的多键索引——多个数值条件可以同时查询 Go语言sync.Map在并发环境中使用的map Go语言中的 map 在并发情况下只读是线程安全的同时读写是线程不安全的。 下面来看下并发情况下读写 map 时会出现的问题代码如下 // 创建一个int到int的映射
m : make(map[int]int)
// 开启一段并发代码
go func() {
// 不停地对map进行写入for {m[1] 1}
}()
// 开启一段并发代码
go func() {
// 不停地对map进行读取for {_ m[1]}
}()
// 无限循环, 让并发程序在后台执行
for {
}
//运行代码会报错输出如下
//fatal error: concurrent map read and map write 错误信息显示并发的 map 读和 map 写也就是说使用了两个并发函数不断地对 map 进行读和写而发生了竞态问题map 内部会对这种并发操作进行检查并提前发现。 需要并发读写时一般的做法是加锁但这样性能并不高Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Mapsync.Map 和 map 不同不是以语言原生形态提供而是在 sync 包下的特殊结构。特性如下 无须初始化直接声明即可。 sync.Map 不能使用 map 的方式进行取值和设置等操作而是使用 sync.Map 的方法进行调用Store 表示存储Load 表示获取Delete 表示删除。 使用 Range 配合一个回调函数进行遍历操作通过回调函数返回内部遍历出来的值Range 参数中回调函数的返回值在需要继续迭代遍历时返回 true终止迭代遍历时返回 false。 package main
import (fmtsync
)
func main() {
var scene sync.Map
// 将键值对保存到sync.Mapscene.Store(greece, 97)scene.Store(london, 100)scene.Store(egypt, 200)
// 从sync.Map中根据键取值fmt.Println(scene.Load(london))
// 根据键删除对应的键值对scene.Delete(london)
// 遍历所有sync.Map中的键值对scene.Range(func(k, v interface{}) bool {
fmt.Println(iterate:, k, v)return true})
}
//代码输出如下
100 true
iterate: egypt 200
iterate: greece 97 sync.Map 没有提供获取 map 数量的方法替代方法是在获取 sync.Map 时遍历自行计算数量sync.Map 为了保证并发安全有一些性能损失因此在非并发情况下使用 map 相比使用 sync.Map 会有更好的性能。 Go语言list列表 列表是一种非连续的存储容器由多个节点组成节点通过一些变量记录彼此之间的关系列表有多种实现方法如单链表、双链表等。 列表的原理可以这样理解假设 A、B、C 三个人都有电话号码如果 A 把号码告诉给 BB 把号码告诉给 C这个过程就建立了一个单链表结构如下图所示。 如果在这个基础上再从 C 开始将自己的号码告诉给自己所知道号码的主人这样就形成了双链表结构如下图所示。 如果 B 换号码了他需要通知 A 和 C将自己的号码移除这个过程就是列表元素的删除操作如下图所示。 在Go语言中列表使用 container/list 包来实现内部的实现原理是双链表列表能够高效地进行任意位置的元素插入和删除操作。 初始化列表 list 的初始化有两种方法分别是使用 New() 函数和 var 关键字声明两种方法的初始化效果都是一致的。 通过 container/list 包的 New() 函数初始化 list 变量名 : list.New() 通过 var 关键字声明初始化 list var 变量名 list.List 列表与切片和 map 不同的是列表并没有具体元素类型的限制因此列表的元素可以是任意类型这既带来了便利也引来一些问题例如给列表中放入了一个 interface{} 类型的值取出值后如果要将 interface{} 转换为其他类型将会发生宕机。 在列表中插入元素 双链表支持从队列前方或后方插入元素分别对应的方法是 PushFront 和 PushBack。 这两个方法都会返回一个 *list.Element 结构如果在以后的使用中需要删除插入的元素则只能通过 *list.Element 配合 Remove() 方法进行删除这种方法可以让删除更加效率化同时也是双链表特性之一。 l : list.New()
l.PushBack(fist)
l.PushFront(67) 第 1 行创建一个列表实例。 第 3 行将 fist 字符串插入到列表的尾部此时列表是空的插入后只有一个元素。 第 4 行将数值 67 放入列表此时列表中已经存在 fist 元素67 这个元素将被放在 fist 的前面。 列表插入元素的方法如下表所示。 方 法功 能InsertAfter(v interface {}, mark * Element) * Element在 mark 点之后插入元素mark 点由其他插入函数提供InsertBefore(v interface {}, mark * Element) *Element在 mark 点之前插入元素mark 点由其他插入函数提供PushBackList(other *List)添加 other 列表元素到尾部PushFrontList(other *List)添加 other 列表元素到头部 从列表中删除元素 列表插入函数的返回值会提供一个 *list.Element 结构这个结构记录着列表元素的值以及与其他节点之间的关系等信息从列表中删除元素时需要用到这个结构进行快速删除。 package main
import container/list
func main() {l : list.New()
// 尾部添加l.PushBack(canon)
// 头部添加l.PushFront(67)
// 尾部添加后保存元素句柄element : l.PushBack(fist)
// 在fist之后添加highl.InsertAfter(high, element)
// 在fist之前添加noonl.InsertBefore(noon, element)
// 使用l.Remove(element)
} 第 6 行创建列表实例。 第 9 行将字符串 canon 插入到列表的尾部。 第 12 行将数值 67 添加到列表的头部。 第 15 行将字符串 fist 插入到列表的尾部并将这个元素的内部结构保存到 element 变量中。 第 18 行使用 element 变量在 element 的位置后面插入 high 字符串。 第 21 行使用 element 变量在 element 的位置前面插入 noon 字符串。 第 24 行移除 element 变量对应的元素。 操作内容列表元素l.PushBack(canon)canonl.PushFront(67)67, canonelement : l.PushBack(fist)67, canon, fistl.InsertAfter(high, element)67, canon, fist, highl.InsertBefore(noon, element)67, canon, noon, fist, highl.Remove(element)67, canon, noon, high 遍历列表——访问列表的每一个元素 遍历双链表需要配合 Front() 函数获取头元素遍历时只要元素不为空就可以继续进行每一次遍历都会调用元素的 Next() 函数代码如下所示。 l : list.New()
// 尾部添加
l.PushBack(canon)
// 头部添加
l.PushFront(67)
for i : l.Front(); i ! nil; i i.Next() {fmt.Println(i.Value)
}
//代码输出如下
67
canon 第 1 行创建一个列表实例。 第 4 行将 canon 放入列表尾部。 第 7 行在队列头部放入 67。 第 9 行使用 for 语句进行遍历其中 i:l.Front() 表示初始赋值只会在一开始执行一次每次循环会进行一次 i ! nil 语句判断如果返回 false表示退出循环反之则会执行 i i.Next()。 第 10 行使用遍历返回的 *list.Element 的 Value 成员取得放入列表时的原值。 Go语言nil空值/零值 在Go语言中布尔类型的零值初始值为 false数值类型的零值为 0字符串类型的零值为空字符串而指针、切片、映射、通道、函数和接口的零值则是 nil。 nil 是Go语言中一个预定义好的标识符有过其他编程语言开发经验的开发者也许会把 nil 看作其他语言中的 nullNULL其实这并不是完全正确的因为Go语言中的 nil 和其他语言中的 null 有很多不同点。 nil 标识符是不能比较的 package main
import (fmt
)
func main() {fmt.Println(nilnil)
} 对于 nil 来说是一种未定义的操作 nil 不是关键字或保留字 nil 并不是Go语言的关键字或者保留字也就是说我们可以定义一个名称为 nil 的变量比如下面这样 var nil errors.New(my god)不建议这样做 nil 没有默认类型 package main
import (fmt
)
func main() {fmt.Printf(%T, nil)print(nil)
} PS D:\code go run .\main.go
\# command-line-arguments
.\main.go:9:10: use of untyped nil 不同类型 nil 的指针是一样的 package main
import (fmt
)
func main() {var arr []intvar num *intfmt.Printf(%p\n, arr)fmt.Printf(%p, num)
} PS D:\code go run .\main.go
0x0
0x0 通过运行结果可以看出 arr 和 num 的指针都是 0x0。 不同类型的 nil 是不能比较的 package main
import (fmt
)
func main() {var m map[int]stringvar ptr *intfmt.Printf(m ptr)
} PS D:\code go run .\main.go
# command-line-arguments
.\main.go:10:20: invalid operation: arr ptr (mismatched types []int and *int) 两个相同类型的 nil 值也可能无法比较 在Go语言中 map、slice 和 function 类型的 nil 值不能比较比较两个无法比较类型的值是非法的下面的语句无法编译。 package main
import (fmt
)
func main() {var s1 []intvar s2 []intfmt.Printf(s1 s2)
} PS D:\code go run .\main.go
# command-line-arguments
.\main.go:10:19: invalid operation: s1 s2 (slice can only be compared to nil) 通过上面的错误提示可以看出能够将上述不可比较类型的空值直接与 nil 标识符进行比较如下所示 package main
import (fmt
)
func main() {var s1 []intfmt.Println(s1 nil)
} 运行结果如下所示 PS D:\code go run .\main.go
true nil 是 map、slice、pointer、channel、func、interface 的零值 package main
import (fmt
)
func main() {var m map[int]stringvar ptr *intvar c chan intvar sl []intvar f func()var i interface{}fmt.Printf(%#v\n, m)fmt.Printf(%#v\n, ptr)fmt.Printf(%#v\n, c)fmt.Printf(%#v\n, sl)fmt.Printf(%#v\n, f)fmt.Printf(%#v\n, i)
} 运行结果如下所示 PS D:\code go run .\main.go
map[int]string(nil)
(*int)(nil)
(chan int)(nil)
[]int(nil)
(func())(nil)
nil 零值是Go语言中变量在声明之后但是未初始化被赋予的该类型的一个默认值。 不同类型的 nil 值占用的内存大小可能是不一样的 一个类型的所有的值的内存布局都是一样的nil 也不例外nil 的大小与同类型中的非 nil 类型的大小是一样的。但是不同类型的 nil 值的大小可能不同。 package main
import (fmtunsafe
)
func main() {var p *struct{}fmt.Println( unsafe.Sizeof( p ) ) // 8
var s []intfmt.Println( unsafe.Sizeof( s ) ) // 24
var m map[int]boolfmt.Println( unsafe.Sizeof( m ) ) // 8
var c chan stringfmt.Println( unsafe.Sizeof( c ) ) // 8
var f func()fmt.Println( unsafe.Sizeof( f ) ) // 8
var i interface{}fmt.Println( unsafe.Sizeof( i ) ) // 16
} 运行结果如下所示 PS D:\code go run .\main.go
8
24
8
8
8
16 具体的大小取决于编译器和架构上面打印的结果是在 64 位架构和标准编译器下完成的对应 32 位的架构的打印的大小将减半。