golang中map与切片的函数传参
在golang 的函数参数,如果参数是值类型的话,如果在函数中修改参数值是不会影响原变量的,因为在函数操作中是会进行一次值拷贝的,如果希望函数的修改影响原变量,则需要传指针方式。如以下代码,是不会改变原变量的值的.
package main import ( "fmt" ) func teststr(s string) { s = "bbb" } func main() { ss := "aaaa" teststr(ss) fmt.Println(ss) // 还是aaaa, 不会是bbb }
经过teststr函数以后,变量ss并没有被修改。如果想要影响原变量,需要传入指针
package main import ( "fmt" ) func teststr(s *string) { *s = "bbb" } func main() { ss := "aaaa" teststr(&ss) fmt.Println(ss) }
经过上面的修改以后,原变量ss的值就被修改为 bbb 了。
参数为map或者切片时
但是传参数是map或者切片呢?
package main import ( "fmt" ) func test(m map[string]int) { // 在函数中修改map的值 m["age"] = m["age"] + 1 } func testslice(s []int) { // 在函数中修改slice值 s[0] = s[0] + 1 } func main() { m := map[string]int{"age": 10} fmt.Println(m) test(m) fmt.Println(m) s := []int{10, 20} fmt.Println(s) testslice(s) fmt.Println(s) }
上面的代码输出为
map[age:10] map[age:11] [10 20] [11 20]
也就说明在函数中对map和切片进行修改是会影响到原变量的。
函数内部改变了切片的结构
根据上面的结果,我们在函数中如果传入切片时,其实可以不用传变量的指针,而是直接传变量本身就可以,但是这里还要注意一个问题,如果切片发生的扩容,即切片的cap值变化了,则在函数内部会生成一个新的切片,这时再对切片的修改是不会影响原切片的.
package main import "fmt" func testslice(s []int) { // 在函数中修改slice值 s = append(s, 2, 3, 4) s[0] = s[0] + 1 fmt.Println("in testslice: ", s) //in testslice: [11 0 2 3 4] } func main() { s := make([]int, 2, 2) s[0] = 10 fmt.Println(s) //[10 0] testslice(s) fmt.Println(s) //[10 0] }
先初始化一个cap为2,len为2的 []int 切片,在testslice中先对切片进行扩容,由于原切片的cap为2,所以这里在添加了三个数以后,肯定是会超过原容量,所以这时会生成一个新的切片,这里如果在函数内修改值,则是修改的是新生成的切片了,所以在testslice中打印出了in testslice: [11 0 2 3 4], 此时对切片第一个值进行的加一操作是不会影响到原切片的。 所以在main函数中最后一行打印的 s 还是[10,0], 第一个值并不会变成11.
但是如果我们在main函数中初始化s 切片变量的时候,设定长度参数为比较大的值,设置一个在使用该切片的函数中足够的长度,如在上例中,如果我们给s 变量设置长度cap为8时,s := make([]int, 2, 8), 这时在即使在testslice中添加了三个数,也不会超过切片的容量.
这时得到的输出为
[10 0] in testslice: [11 0 2 3 4] [11 0]
这时就可以在函数中的修改就会影响到外面的变量,但是还是有点奇怪,在testslice内部,s 为 [11 0 2 3 4], 在main函数最后还是[11, 0], 并没有后面的[2,3,4].
我们尝试打印一下变量的地址
package main import "fmt" func testslice(s []int) { // 在函数中修改slice值 fmt.Printf("s1, p: %p\n", s) //0xc0000200c0 s = append(s, 2, 3, 4) fmt.Printf("s2, p: %p\n", s) //0xc0000200c0 s[0] = s[0] + 1 fmt.Println("in testslice: ", s) //[11 0 2 3 4] } func main() { s := make([]int, 2, 8) s[0] = 10 fmt.Printf("%p\n", s) //0xc0000200c0 fmt.Println(s) //[10 0] testslice(s) fmt.Printf("%p\n", s) //0xc0000200c0 fmt.Println(s) // [11 0] }
可以看到s 变量的地址,始终没有变化,但是为什么最后得到的变量不一样了呢?
这里我们再尝试打印一个s变量的长度len和容量cap
func testslice(s []int) { // 在函数中修改slice值 fmt.Printf("s1, p: %p\n", s) s = append(s, 2, 3, 4) fmt.Printf("s2, p: %p\n", s) s[0] = s[0] + 1 fmt.Println("in testslice: ", s, cap(s), len(s)) //[11 0 2 3 4] 8 5 } func main() { s := make([]int, 2, 8) s[0] = 10 fmt.Printf("%p\n", s) fmt.Println(s, cap(s), len(s)) //[10 0] 8 2 testslice(s) fmt.Printf("%p\n", s) fmt.Println(s, cap(s), len(s)) //[10 0] 8 2 } 在testslice中最后打印len(s) 为 5, 在main 函数中打印出来的len都为2,在golang中,超过len长度的会被隐藏。 其实想要获取到也是可以获取到的,只需要 testslice(s) s = s[:cap(s)]
其实上面的问题都是由于切片的容量变量导致的,与其记住这些规则,还要考虑之后的代码维护,我们可以将新的切片直接返回。
func testslice(s []int) []int { // 在函数中修改slice值 s = append(s, 2, 3, 4) s[0] = s[0] + 1 return s } func main() { s := make([]int, 2, 8) s[0] = 10 s = testslice(s) fmt.Println(s) }
这样可以避免很多问题,代码也好维护,这时就可以获取到s的全部数值了!
总结
当函数的参数为map或者slice切片时,可以直接使用值变量
当版本切片作为参数的时候,要考虑到切片容量的变更会导致生成新的切片的问题