引言
本系列会列举一些在Go面试中常见的问题。
切片循环问题
For循环在我们日常编码中可能用的很多。在很多业务场景中我们都需要用for循环处理。但golang中的for循环在使用上需要注意一些问题,大家可否遇到。先看下边这一段代码:
- functestSlice(){
- a:=[]int64{1,2,3}
- for_,v:=rangea{
- gofunc(){
- fmt.Println(v)
- }()
- }
- time.Sleep(time.Second)
- }
- output:333
那么为什么会输出的是这个结果呢?
在golang的for循环中,循环内部创建的函数变量都是共享同一块内存地址,for循环总是使用同一块内存去接收循环中的的value变量的值。不管循环多少次,value的内存地址都是相同的。我们可以测试一下:
- functestSliceWithAddress(){
- a:=[]int64{1,2,3}
- for_,v:=rangea{
- gofunc(){
- fmt.Println(&v)
- }()
- }
- time.Sleep(time.Second)
- }
- output:
- 0xc0000b2008
- 0xc0000b2008
- 0xc0000b2008
符合预期。如果大家比较感兴趣的话可以去将这段代码的汇编打印出来,就可以发现循环的v一直在操作同一块内存。
同样的,在slice循环这块我们还会遇见另一个有趣的地方,大家可以看看下边这段代码输出什么?
- functestRange3(){
- a:=[]int64{1,2,3}
- for_,v:=rangea{
- a=append(a,v)
- }
- fmt.Println(a)
- }
这段代码的输出结果是:[1 2 3 1 2 3],为什么呢?因为golang在循环前会先拷贝一个a,然后对新拷贝的a进行操作,所以循环的次数不会随着append而增多。
interface和nil比较
比如返回了一个空指针,但并不是一个空interface
- functestInterface(){
- doit:=func(argint)interface{}{
- varresult*struct{}=nil
- ifarg>0{
- result=&struct{}{}
- }
- returnresult
- }
- ifres:=doit(-1);res!=nil{
- fmt.Println("result:",res)
- }
- }
输出结果为:result:
可变参数是空接口类型
当参数的可变参数是空接口类型时,传入空接口的切片时需要注意参数展开的问题。
- functestVolatile(){
- vara=[]interface{}{1,2,3}
- fmt.Println(a)
- fmt.Println(a...)
- }
输出结果为:
- [123]
- 123
map遍历时顺序不固定
不要相信map的顺序!
- functestMap(){
- m:=map[string]string{
- "a":"a",
- "b":"b",
- "c":"c",
- }
- fork,v:=rangem{
- println(k,v)
- }
- }
具体原因大家可以看一下源码:map.go:mapiterinit,就会发现下边这个代码用来决定从哪开始遍历map。另一个原因是map 在某些特定情况下(例如扩容),会发生key的搬迁重组。而遍历的过程,就是按顺序遍历bucket,同时按顺序遍历bucket中的key。搬迁后,key的位置发生了重大的变化,所以遍历map的结果就不可能按原来的顺序了。
- funcmapiterinit(t*maptype,h*hmap,it*hiter){
- ......
- //decidewheretostart
- r:=uintptr(fastrand())
- ......
- }
原文链接:https://mp.weixin.qq.com/s/tcBH86xcN_VXIq4mzaVRkQ