关于数组和切片,golang官方博客有文章详细说明,点击这里。其实这里说的已经很清楚了,论好好阅读官方说明的重要性。
package main import ( "fmt" ) func main() { slice := []int{0, 1, 2, 3} fmt.Printf("slice: %v slice addr %p \n", slice, &slice) // slice: [0 1 2 3] slice addr 0xc00000c080 ret := changeSlice(slice) fmt.Printf("slice: %v slice addr %p | ret: %v ret addr %p \n", slice, &slice, ret, &ret) // slice: [0 111 2 3] slice addr 0xc00000c080 | ret: [0 111 2 3] ret addr 0xc00000c0c0 res := appendSlice(slice) fmt.Printf("slice: %v slice addr %p | res: %v ret addr %p \n", slice, &slice, res, &res) // slice: [0 111 2 3] slice addr 0xc00000c080 | res: [0 111 2 3 1] ret addr 0xc00000c120 } func changeSlice(s []int) []int { s[1] = 111 return s } func appendSlice(s []int) []int { s = append(s, 1) return s }
从上面代码和输出结果(注释部分)可以看出:
changeSlice()
函数对外部slice生效了appendSlcie()
函数对外部没有生效golang中只有值传递,所有的引用传递都是直接把对应的指针拷贝过去了,所以修改能直接在原对象生效。
很多地方都说slice是引用类型(这是相对于slice底层的数组而言的),其实slice是一个结构体类型(也就是值类型)。
type slice struct { array unsafe.Pointer len int cap int }
因为,slice
是一个结构体且参数传递是值传递,所以changeSlice()
函数中的s
是slice
的一个副本,所以changeSlice()
函数的返回值ret
的地址与slice
不同,他们是内存中的两个对象。
在slice
中array
是一个指针,指向底层数组的开头,所以在changeSlice()
函数中s[1] = 111
是对底层数组的修改。那么在main()
函数中不论是读取slice
还是读取ret
,他们都指向同一个底层数组,所以看起来就是changeSlice()
函数修改了传入的切片对象的原始值。
根据上面的分析,在appendSlice()
函数中的append()
操作是作用在res
上而不是slice
上。
appendSlice
生效因为slice其实是一个结构体而不是一个引用。要让appendSlice
生效,只要传入引用就可以,代码修改如下:
res := appendSlice(&slice) func appendSlice(s *[]int) *[]int { *s = append(*s, 1) return s }