Go 函数的 Map 型参数,会发生扩容后指向不同底层内存的事儿吗?
Posted 网管叨bi叨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 函数的 Map 型参数,会发生扩容后指向不同底层内存的事儿吗?相关的知识,希望对你有一定的参考价值。
最近跟同事做项目,由于要在函数里向一个 Map 中写入不少数据,这个 Map 是作为参数传到函数里的。他问了我一个问题: “如果把 Map 作为函数参数传递,会不会像用 Slice 做参数时一样诡异,是不是一定要把 Map 当成返回值返回才能让函数外部的 Map 变量看到这里添加的数据”?
啥叫会不会像用 Slice 做参数时一样诡异?同事没有明说,其实我已经猜到他说的是什么意思了,说的应该是 Slice 的底层数组如果发生了扩容后会让函数内外原本指向同一个底层数组的两个 Slice 变量,分别指向两个不同的底层数组。
最后就导致了函数内做的数据添加,但是函数外原来的 Slice 变量并没有任何改变的诡异效果。光看字儿解释起来有点难懂,举个例子,有下面这样一个程序。
了。下面这个图,展示了这个函数内外切片指向的底层数组发生变化的过程。
那么如果用 Map 当函数参数时,有这档子破事儿吗?诶,提到这我就要吐槽下这个一切都是传值的设计了,把一些写 Go 的程序员搞的战战兢兢,用 Map 和结构体指针当参数的时候也老琢磨底层会不会变。
当然我也不是写 Go 的时候都盲目自信,一般书上、别人文章里写的东西我在用的时候,如果不确定他们说的对不对,我都会写个单测试一试。事后再找找解释这些知识点的资料看看,自己解惑一下。
聊远了,下面说下答案哈,如果用 Map 当函数参数,Map发生扩容后,函数内外的Map变量指向的底层内存仍是一致的。这是为什么呢?答案我是在《Go 语言设计与实现》哈希表这一章找到的,有书的可以翻开 75 页看看。
如果没有书的可以看文末的引用链接里贴的在线书籍地址。
关于 Map 的初始化是这么描述的
使用 make
创建哈希,Go 语言编译器都会在类型检查期间将它们转换成 runtime.makemap
,使用字面量初始化哈希也只是语言提供的辅助工具,最后调用的都是 runtime.makemap
:
本身的内存地址。所以当 Map 由于函数内的操作发生扩容时,不会像上面例子里的 Slice 指向不同底层数组的诡异现象。
不知道大家有没有看明白我这里的分析,这篇文章其实是我自己对思考问题的一个记录,防止时间长了以后忘掉。传值、传引用这些在不同的语言里不一样,对于像我们掌握了至少三门编程语言的男人:)也就只能靠写写笔记防止混淆啦。
(我相信绝大多数人的职业生涯是不能靠一门编程语言吃遍天的)
还有一点我是觉得 Go 的 Slice 使用起来确实要耗费的心智有点高,一不注意就容易踩坑,时间长了,搞的大家用 Map 和 指针当参数时也会先自我怀疑一下,希望这篇文章对解决掉你们的使用疑虑有一定帮助。
Go 语言设计与实现 --哈希表 https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-hashmap
扫码关注公众号「网管叨bi叨」
给网管个星标,第一时间吸我的知识 Go常用类型和函数笔记
人生忽如寄,莫负茶汤好天气
map、slice、channel这些数据类型本身就是指针类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向底层数据结构,所以修改它们可能会影响外部数据结构的值。
make只能构建slice、map和channel这3种结构的数据对象,因为它们都指向底层数据结构,都需要先为底层数据结构分配好内存并初始化。本文将对这三个特殊的结构进行解析。
1. slice
https://www.cnblogs.com/f-ck-need-u/p/9854932.html
Go中的slice依赖于数组,它的底层就是数组,所以数组具有的优点,slice都有,且slice支持可以通过append向slice中追加元素,长度不够时会动态扩展,通过再次slice切片,可以得到得到更小的slice结构,可以迭代、遍历等。实际上slice是这样的结构:先创建一个有特定长度和数据类型的底层数组,然后从这个底层数组中选取一部分元素,返回这些元素组成的集合(或容器),并将slice指向集合中的第一个元素。换句话说,slice自身维护了一个指针属性,指向它底层数组中的某些元素的集合。 每一个slice结构都由3部分组成:容量(capacity)、长度(length)和指向底层数组某元素的指针,它们各占1个机器字长(1个机器字长,64位机器上一个机器字长为64bit,共8字节大小,32位架构则是32bit,占用4字节)。
slice的本质是[x/y]0xADDR,[
length/capacity]pointer.
1.1 创建初始化
2. channel
3. map
4. interface
接口(interface)是一种类型,用来定义行为(方法)。接口类型的数据结构是2个指针,占用2个机器字长。
参考连接:go接口
4.1 接口存储数据结构
4.2 接口方法集规则
4.3 接口类型作为参数
5. 字符串遍历 字节索引 和字符索引
// 使用字节索引需要转换一下
func main()
str := "Hello 你好"
r := []rune(str) // 8
for i := 0; i < len(r); i++
fmt.Printf("%c", r[i])
// 迭代字符串的时候,是按照字符而非字节进行索引的。
func main()
var a = "Xiaofang,你好"
for index,value := range a
println(index,string(value))
6. 内置函数使用
close
用于关闭channeldelete
用于删除map中的元素copy
用于拷贝sliceappend
用于追加slicecap
用于获取slice的容量len
用于获取- slice的长度
- map的元素个数
- array的元素个数
- 指向array的指针时,获取array的长度
- string的字节数
- channel的channel buffer中的未读队列长度
print
和println
:底层的输出函数,用来调试用。在实际程序中,应该使用fmt中的print类函数complex
、imag
、real
:操作复数(虚数)panic
和recover
:处理错误new
和make
:分配内存并初始化- new适用于为值类(value type)的数据类型(如array,int等)和struct类型的对象分配内存并初始化,并返回它们的指针给变量。如
v := new(int)
- make适用于为内置的引用类的类型(如slice、map、channel等)分配内存并初始化底层数据结构,并返回它们的指针给变量,同时可能会做一些额外的操作
- new适用于为值类(value type)的数据类型(如array,int等)和struct类型的对象分配内存并初始化,并返回它们的指针给变量。如
7. defer- recover-panic
package main
import "fmt"
func main()
println("start main")
b()
println("end main")
func a()
println("start a")
panic("panic in a")
println("end a")
func b()
println("start b")
defer func()
if str := recover(); str != nil
fmt.Println(str)
()
a()
println("end b")
参考文档:
以上是关于Go 函数的 Map 型参数,会发生扩容后指向不同底层内存的事儿吗?的主要内容,如果未能解决你的问题,请参考以下文章