跳到主要内容

Go 值传递、引用传递与切片详解

值传递

在值传递中,函数接收参数值的一个副本。这意味着在函数内部对参数的任何修改都不会影响到函数外部的原始数据。值传递通常更安全,因为它保证了函数不会意外地修改外部数据。但是,值传递可能效率较低,特别是当传递大对象或数组时,因为需要复制整个对象。

package main

import (
"fmt"
)

func increment(num int) int {
num = num + 1
return num
}

func main() {
count := 5
count = increment(count) // 需要将结果重新赋值给 count
fmt.Println(count) // 输出 6
}

引用传递

在引用传递中,函数接收参数的引用而非实际数据。这意味着在函数内部对参数的任何修改都会影响到函数外部的原始数据。引用传递通常更高效,因为它避免了复制数据,这在处理大对象或数组时特别有用。但是,引用传递可能会导致难以调试的错误,因为函数可能会意外地修改外部数据。

package main

import (
"fmt"
)

func increment(numPtr *int) {
*numPtr = *numPtr + 1
}

func main() {
count := 5
increment(&count) // 传递 count 的指针
fmt.Println(count) // 输出 6
}

注意&count 获取变量 count 的地址,*numPtr 对指针进行解引用,以获取指针指向的实际值。

slice 的组成

在 Go 语言中,slice 是对数组的抽象,它由以下三个部分组成:

指针

指向底层数组中 slice 起始位置的指针。

长度(len)

slice 的长度,表示它包含的元素数量,即从 slice 的开始位置到结束位置的元素个数。

容量(cap)

slice 的容量,表示从 slice 的开始位置到底层数组末尾的元素数量。

slice 的存储方式

slice 在底层是一个结构体,包含了上述的指针、长度和容量。它的指针指向底层数组的一段连续内存空间。通过 len 可以确定 slice 目前有多少元素,通过 cap 可以知道在不扩容的情况下还可以存储多少元素。

当向 slice 添加元素且超过其容量时,Go 会自动分配一个更大的底层数组,并将现有的元素复制到新数组中。

package main

import "fmt"

func main() {
// 创建一个底层数组
numbers := [...]int{1, 2, 3, 4, 5}

// 创建一个 slice,引用数组的第 2 到第 4 个元素
subSlice := numbers[1:4]

fmt.Println(subSlice) // 输出 [2 3 4]
fmt.Println(len(subSlice)) // 输出 3
fmt.Println(cap(subSlice)) // 输出 4

// 向 slice 添加元素
subSlice = append(subSlice, 6)

fmt.Println(subSlice) // 输出 [2 3 4 6]
fmt.Println(len(subSlice)) // 输出 4
fmt.Println(cap(subSlice)) // 输出 4
}

为什么需要容量(cap)

cap 函数用于返回 slice 的容量,即从 slice 的起始位置到底层数组末尾的元素数量。容量的概念很重要,因为它影响了 slice 的性能,特别是在添加新元素时。

当向 slice 添加元素并超过其容量时,Go 会分配一个新的底层数组,并将现有元素复制到新数组中。这个过程会影响性能,尤其是对于大型 slice。了解 slice 的容量,有助于编写性能更佳的代码。

package main

import "fmt"

func main() {
// 创建一个初始容量为 3 的 slice
numbers := make([]int, 3)
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3

fmt.Println(numbers) // 输出 [1 2 3]
fmt.Println(len(numbers)) // 输出 3
fmt.Println(cap(numbers)) // 输出 3

// 追加元素,超过容量,触发扩容
numbers = append(numbers, 4)

fmt.Println(numbers) // 输出 [1 2 3 4]
fmt.Println(len(numbers)) // 输出 4
fmt.Println(cap(numbers)) // 输出 6 或更大,取决于扩容策略
}

提示:合理地设置 slice 的容量,可以减少扩容次数,提升性能。