没错了,对于问题1、3和4我们应该都已经解释清楚了,但是,关于第2点为什么函数内外对于这三个内建类型变量的地址打印却是一致的?我们已经更加确定了golang中的参数传递的确是值类型,那么,造成这一现象的唯一可能就是出在打印函数fmt.Printf()中有些小操作了。因为我们是通过%p来打印地址信息的,为此,我们需要关注的是fmt包中fmtPointer():
func (p *pp) fmtPointer(value reflect.Value, verb rune) { var u uintptr switch value.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: u = value.Pointer() default: p.badVerb(verb) return } ... }我们发现在fmtPointer()中,对于map、channel和slice,都被当成了指针来处理,通过Pointer()函数获取对应的值的指针。我们知道channel和map是因为make函数返回的就已经是指针了,无可厚非,但是对于slice这个非指针,在value.Pointer()是如何处理的呢?
// If v's Kind is Slice, the returned pointer is to the first // element of the slice. If the slice is nil the returned value // is 0. If the slice is empty but non-nil the return value is non-zero. func (v Value) Pointer() uintptr { // TODO: deprecate k := v.kind() switch k { case Chan, Map, Ptr, UnsafePointer: return uintptr(v.pointer()) case Func: ... case Slice: return (*SliceHeader)(v.ptr).Data } ... }果不其然,在Pointer()函数中,对于Slice类型的数据,返回的一直是指向第一个元素的地址,所以我们通过fmt.Printf()中%p来打印Slice的地址,其实打印的结果是内部存储数组元素的首地址,这也就解释了问题2中为什么地址会一致的原因了。
总结通过上述的一系列总结,我们可以很高兴的确定的是:在golang中的传参一定是值传递了!
然而golang隐藏了一些实现细节,在处理map,channel和slice等这些内置结构的数据时,其实处理的是一个指针类型的数据,也是因此,在函数内部可以修改(部分修改)数据的内容。
但是,这些修改得以实现的原因,是因为数据本身是个指针类型,而不是因为golang采用了引用传递,注意二者的区别哦~