slice 和 map 分别作为函数参数时有什么区别?
slice 的结构体定义:
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}
结构体内部包含底层的数据指针。
创建 slice 实际是调用 makeslice
函数返回的是 Slice
结构体:
func makeslice(et *_type, len, cap int) slice{
...
}
在源码中,表示 map 的结构体是 hmap,它是 hashmap 的“缩写”:
// A header for a Go map.
type hmap struct {
// 元素个数,调用 len(map) 时,直接返回此值
count int
flags uint8
// buckets 的对数 log_2
B uint8
// overflow 的 bucket 近似数
noverflow uint16
// 计算 key 的哈希的时候会传入哈希函数
hash0 uint32
// 指向 buckets 数组,大小为 2^B
// 如果元素个数为0,就为 nil
buckets unsafe.Pointer
// 等量扩容的时候,buckets 长度和 oldbuckets 相等
// 双倍扩容的时候,buckets 长度会是 oldbuckets 的两倍
oldbuckets unsafe.Pointer
// 指示扩容进度,小于此地址的 buckets 迁移完成
nevacuate uintptr
extra *mapextra // optional fields
}
创建 map 实际是调用makemap
函数,主要做的工作就是初始化 hmap
结构体的各种字段,例如计算 B 的大小,设置哈希种子 hash0 等等。注意,这个函数返回的结果:*hmap
,它是一个指针。
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
...
}
makemap 和 makeslice 的区别,带来一个不同点:当 map 和 slice 作为函数参数时,在函数参数内部对 map 的操作会影响 map 自身;而对 slice 却不会(之前讲 slice 的文章里有讲过)。
主要原因:一个是指针(*hmap
),一个是结构体(slice
)。Go 语言中的函数传参都是值传递,在函数内部,参数会被 copy 到本地。*hmap
指针 copy 完之后,仍然指向同一个 map,因此函数内部对 map 的操作会影响实参。而 slice 被 copy 后,会成为一个新的 slice,对它进行的操作不会影响到实参。但值得注意的是,不管传的是 slice 还是 slice 指针,如果改变了 slice 底层数组的数据,会反应到实参 slice 的底层数据。为什么能改变底层数组的数据?很好理解:底层数据在 slice 结构体里是一个指针,尽管 slice 结构体自身不会被改变,也就是说底层数据地址不会被改变。 但是通过指向底层数据的指针,可以改变切片的底层数据,没有问题。
package main
import (
"fmt"
)
type Cat struct {
Name string
Age int
}
func main() {
slice := []int{1, 2, 3, 4, 5, 6}
fmt.Println(slice) //[1 2 3 4 5 6]
ChangeSlice(slice)
fmt.Println(slice) //[100 2 3 4 5 6]
m := make(map[string]string)
m["aaa"] = "bbb"
fmt.Println(m) //map[aaa:bbb]
ChangeMap(m)
fmt.Println(m) //map[aaa:ccc]
c1 := Cat{
Name: "pussy",
Age: 1,
}
fmt.Println(c1) //{pussy 1}
ChangeStruct(c1)
fmt.Println(c1) //{pussy 1}
c2 := &Cat{
Name: "pussy2",
Age: 1,
}
fmt.Println(c2) //&{pussy2 1}
ChangePoint(c2)
fmt.Println(c2) //&{pussy2 2}
}
func ChangeSlice(s []int) {
s[0] = 100
}
func ChangeMap(m map[string]string) {
m["aaa"] = "ccc"
}
func ChangeStruct(c Cat) {
c.Age = 2
}
func ChangePoint(c *Cat) {
c.Age = 2
}
可能遇到的坑:因为传的是结构体,所以主函数的数组指针不会变化,如果在函数中进行append扩容,那使用的指针已经不是原指针
package main
import "fmt"
func main() {
a := []int{55, 56, 57, 58, 59}
ChangeSlice(a)
// [1 56 57 58 59] len:5 cap:5 ptr:0xc0000be060
fmt.Printf("%v len:%d cap:%d ptr:%p\n", a, len(a), cap(a), a)
fmt.Println("------------------------")
ChangeSliceP(&a)
// [2 56 57 58 59 100 101 102 103 104 105 106] len:12 cap:12 ptr:0xc0000164e0
fmt.Printf("%v len:%d cap:%d ptr:%p\n", a, len(a), cap(a), a)
}
func ChangeSlice(a []int) {
a[0] = 1
// [1 56 57 58 59] len:5 cap:5 ptr:0xc0000be060
fmt.Printf("%v len:%d cap:%d ptr:%p\n", a, len(a), cap(a), a)
a = append(a, 100, 101, 102, 103, 104, 105, 106)
a[0] = 2
// [2 56 57 58 59 100 101 102 103 104 105 106] len:12 cap:12 ptr:0xc0000860c0 append进行扩容,底层数组已经不是ptr:0xc0000be060
fmt.Printf("%v len:%d cap:%d ptr:%p\n", a, len(a), cap(a), a)
}
func ChangeSliceP(a *[]int) {
(*a)[0] = 1
// &[1 56 57 58 59] len:5 cap:5 ptr:0xc000010450
fmt.Printf("%v len:%d cap:%d ptr:%p\n", a, len(*a), cap(*a), *a)
*a = append(*a, 100, 101, 102, 103, 104, 105, 106)
(*a)[0] = 2
// &[2 56 57 58 59 100 101 102 103 104 105 106] len:12 cap:12 ptr:0xc0000164e0
fmt.Printf("%v len:%d cap:%d ptr:%p\n", a, len(*a), cap(*a), *a)
}
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付