5

golang中map与切片的函数传参

 1 year ago
source link: https://www.yangyanxing.com/article/map-slice-in-func-params.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

golang中map与切片的函数传参

2022-08-13 Go

1665

4分钟

在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的全部数值了!

  1. 当函数的参数为map或者slice切片时,可以直接使用值变量
  2. 当版本切片作为参数的时候,要考虑到切片容量的变更会导致生成新的切片的问题

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK