关于数组和切片,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
}