第一个陷阱
下面是Go语言中一个函数尝试修改数组元素的例子:
package main
import "fmt"
func mod(a [2]int) {
a[0] = 100
}
func main() {
a := [2]int{1, 2}
mod(a)
fmt.Println(a)
}
此程序的正确输出为
[1 2]
,表明数组a
在调用mod
函数后没有发生变化。
在 Go 语言中,数组是一种值类型,并且具有固定大小;具有不同长度的数组被视为不同类型的值。例如
[1]int
和[30]int
属于不同的类型。当值类型作为参数传递时,参数是该值的一个拷贝,因此更改拷贝的值并不会影响原值。
如果希望避免数组复制带来的开销或使函数能够修改原始数组中的元素,可以考虑传递数组的指针给函数,或者使用Slice代替数组。
切片(slice)是由以下三个组成部分构成的:
*ptr
:指向底层数组(underlying array)的指针。len
:切片的长度,表示切片中有多少个可使用的元素。cap
:切片的容量,表示从切片的第一个元素到最后一个元素之间可用的最大元素数量。
当我们将切片作为参数传递给函数时,实际上会创建一个新的切片变量,这个新的切片包含了指向相同底层数组的指针以及长度和容量信息。因此,尽管传递的是切片的副本,但由于这些副本指向同一个底层数组,所以对切片中的元素进行修改时,实际上是在修改底层数组的内容。这导致了即使是在函数内部修改了切片中的元素,外部的原始切片也会体现出这种变化。
第二个陷阱
下面这段Go语言代码展示了当向切片追加元素时,原切片不受影响的情况:
func push(a []int) {
a = append(a, 3, 4, 5, 6, 7, 8)
a[0] = 100
}
func main() {
a := []int{1, 2}
push(a)
fmt.Println(a) // 输出仍为 [1 2]
}
输出结果是
[1 2]
,说明切片a
并未受到函数push
内部操作的影响。
当将切片作为参数传递给函数时,虽然传递的是切片本身的副本,但副本与原切片共享相同的底层数组。然而,在函数 push
中,当调用 append
函数时,它可能会导致创建一个新的底层数组来容纳更多的元素。在这种情况下,由于原始底层数组不足以存储新增的元素,因此 append
返回的新切片指向了一个全新的底层数组。这样一来,尽管修改了新切片中的元素,但这不会影响到原来的切片。
如果希望 push
函数中的操作能够影响到原切片,可以采用以下两种方法之一:
设置返回值:让函数
push
返回修改后的切片,并在main
函数中接收这个返回值,然后将其赋值给变量a
。func push(a []int) []int { a = append(a, 3, 4, 5, 6, 7, 8) a[0] = 100 return a } func main() { a := []int{1, 2} a = push(a) fmt.Println(a) }
使用指针传递:另一种方法是使用切片的指针来传递,这样函数可以直接修改传递进来的切片。
func push(a *[]int) { *a = append(*a, 3, 4, 5, 6, 7, 8) (*a)[0] = 100 } func main() { a := []int{1, 2} push(&a) fmt.Println(a) }
从代码的可读性和清晰性角度来看,推荐使用第一种方法,即通过返回值来更新切片。这种方法不仅清晰地表达了意图,还保持了函数的职责单一,符合好的编程实践。
评论区