go 当参数是slice,传值还是传指针()

下午面试,面试官让我写二叉树的中序遍历。
遥想半年前的秋招,我还只会C++,做啥题自然都是C++。
半年后的今天,经过了几个月的go语言学习,go学的咋样不敢说,反正C++是忘光了。。。
经过几秒钟的思考(其实我是想先定义个二叉树的结构体,可是实在是想不起来C的结构体怎么定义了。。。),我毅然决定,用go来写!
讲真,这是我第一次用go做题(别看我已经写了那么多篇go的文章,仿佛自己是个go语言专家)。。。好在,中序遍历的递归写法实在是没啥难的,所以,我很快就写出了下面这段代码:

func inOrder(root *TreeNode, array []int){ if root == nil { return } inOrder(root.Left, array) array = append(array, root.Val) inOrder(root.Right, array) }

面试官看了看,给出的评价是,恩,虽然细节上有些问题,但思路是对的,嘿嘿,那当然了,这我还能写不对,等下!细节有些问题是什么鬼???就这两行代码还能有错???我心想,肯定是这个面试官不懂go语言,随口一说的,所以当时也没追问这件事儿,万一一追问,他说他不会go,多尴尬啊对吧,哈哈哈。。。
然鹅,我是一个严谨的人,面试结束之后,我还是决定亲自跑一遍这段代码,用事实证明,我这几个月的go,不是白学的!
首先,我输入了一个这样的树:
go 当参数是slice,传值还是传指针()
文章图片

【go 当参数是slice,传值还是传指针()】这个中序遍历完,array那个slice应该变成[1,2,3,4]。
然鹅,结果是这样的:
go 当参数是slice,传值还是传指针()
文章图片

卧槽,怎么是个空的???
我开始努力回忆初学slice的时候,当时我还写过一篇blog啊,怎么会搞错呢。。。
go 当参数是slice,传值还是传指针()
文章图片

这段是当时我当时写的,看来看去,没错啊,slice里面存的是指向数组的指针,所以传slice的值进去,也是可以改变底层数组的啊,为啥我那么写就不行呢???
百思不得其解的我,百度了一下slice作为参数的用法,看到一个人说,如果slice在函数内部扩容了,就会#¥$&(他说的我没太看懂。。。) ,是啊,我给函数传进来的是一个容量为0的slice,在append的时候会扩容,而扩容的过程是:
  • 先分配一段新的内存
  • 然后把原来数组的内容拷贝过去
想到这儿我开始有些动摇了,可还是没有完全想明白,我看了下我的代码:
array = append(array, root.Val)

append的时候确实扩容了,slice里的指针也确实是变了,可是变了之后我又把新的赋值给array了啊,这个还是我外面传进来那个array啊,所以函数执行完外面那个array应该被修改了啊,没毛病啊。。。
这时我突然想到,array我是以值的形式传递进来的,也就是说,array里那个指向数组的指针,也是以值的形式传进来的,也就是说,函数内部修改了这个指针(注意是修改了指针本身,而不是修改了指针指向的内容),外面是看不到的!!!
阿西吧,原来如此啊。。。
于是,我把函数改成了传递slice指针进来,像下面这样:
func inOrder(root *TreeNode, array *[]int){ if root == nil { return } inOrder(root.Left, array) *array = append(*array, root.Val) inOrder(root.Right, array) }

再一运行,终于是对了。。。
天哪,我之前竟然还自信的以为是面试官不懂go,我简直就是个傻屌。。。
总结 最后总结一下吧,slice作为参数,如果以值的形式传递,确实可以在函数内部修改数组,但前提是,函数内部slice不会扩容,如果函数内部slice会扩容,那还是乖乖的传slice指针进去吧。
最后再说一句,我真的是是太年轻了。。。继续努力吧。。。

    推荐阅读