golang中map与切片的函数传参

作者: adm 分类: go 发布时间: 2023-04-17

在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切片时,可以直接使用值变量
当版本切片作为参数的时候,要考虑到切片容量的变更会导致生成新的切片的问题

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!