为了账号安全,请及时绑定邮箱和手机立即绑定

为什么 append 修改传递的切片

为什么 append 修改传递的切片

Go
慕慕森 2021-12-20 16:36:33
我如何遍历切片并将切片传递到当前元素之外的某个地方?正如我们在文档中看到的那样,似乎append()函数修改了底层切片。但无论如何我仍然不知道如何达到这一点。func main() {    args := []string{ "2", "3", "8" }    for i, _ := range args {        fmt.Println(append(args[:i], args[i+1:]...)) // or pass to function    }    fmt.Println(args)}结果:[3 8][3 8][3 8][3 8 8] // it is args now我的期望: [3 8] [2 8] [2 3]但是切片的容量是多少对我来说是秘密,我不明白为什么我超过了它。
查看完整描述

3 回答

?
素胚勾勒不出你

TA贡献1827条经验 获得超9个赞

性能是很大的原因。创建一个新切片并将所有元素复制到它上面是昂贵的,因此切片代码不会无缘无故地复制。但是,如果超出了切片的容量,它会通过复制底层切片以适当的量增长。这意味着返回的切片append可能与您传入的切片不同。


首选的使用方式是:


args = append(args, newarg)

如果您采用子切片,容量保持不变,但您对切片的看法会发生变化。这意味着丢失的元素仍然存在,但在新切片的边界之外。


这解释了代码的奇怪输出。您append每次都打印结果但不存储该结果,这意味着args与您打印的结果不同。


初始args切片有 3 个元素大。对于每个索引i- 也就是说 for 0, 1and 2- 你取一个子切片args[:i]并将数组剩余部分的所有元素附加args[i+1:]到它。这意味着对于:


i    args[:i]     args[i+1]...   Result         args

0    {}           {"3", "8"}     {"3", "8"}     {"3", "8", "8"}

1    {"3"}        {"8"}          {"3", "8"}     {"3", "8", "8"}

2    {"3", "8"}   {}             {"3", "8"}     {"3", "8", "8"}

tl;dr 你应该总是保存 的结果append,如果你想制作一个副本以便你可以改变它,那么你自己制作一个副本。


查看完整回答
反对 回复 2021-12-20
?
慕盖茨4494581

TA贡献1850条经验 获得超11个赞

Append 总是尝试修改底层数组。


让我们看一下循环的第一次执行


append(args[:0], args[0+1:]...)

这样做是将切片 {3,8} 附加到切片 {},因为 args[:0] 为您提供了一个在数组开头结束的空切片。这就是为什么你的数组作为 [3 8 8] 出现的原因,因为 3 8 被附加到数组中。在 wiki 上阅读有关此内容的更多信息。


您可以使用 make 设置默认容量,即


args := make([]string, 0, CAPACITY)

您还可以检查切片的容量


a := []int{1,2,3}

fmt.Println(cap(a))

>>> 3

最后,如果您不想像 Elwinar 的回答那样每次都重新复制数组,我建议将两个切片 a[:i] 和 a[i+1:] 传递给函数。


查看完整回答
反对 回复 2021-12-20
?
杨魅力

TA贡献1811条经验 获得超6个赞

Go 编程语言规范


追加和复制切片


内置函数 append 和 copy 辅助常见的切片操作。对于这两个函数,结果与参数引用的内存是否重叠无关。


可变参数函数 append 将零个或多个值 x 附加到类型 S 的 s 中,该类型必须是切片类型,并返回结果切片,也是 S 类型。值 x 被传递给类型为 ...T 的参数,其中 T是 S 的元素类型,适用相应的参数传递规则。作为一种特殊情况, append 还接受可分配给类型 []byte 的第一个参数和字符串类型的第二个参数,后跟 ...。这种形式附加字符串的字节。


append(s S, x ...T) S  // T is the element type of S

如果 s 的容量不足以容纳附加值,则 append 分配一个新的、足够大的底层数组,该数组既适合现有切片元素又适合其他值。否则, append 会重新使用底层数组。


s0 := []int{0, 0}

s1 := append(s0, 2)                // append a single element     s1 == []int{0, 0, 2}

s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}

s3 := append(s2, s0...)            // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}

s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}


var t []interface{}

t = append(t, 42, 3.1415, "foo")   //                             t == []interface{}{42, 3.1415, "foo"}


var b []byte

b = append(b, "bar"...)            // append string contents      b == []byte{'b', 'a', 'r' }

函数 copy 将切片元素从源 src 复制到目标 dst 并返回复制的元素数。两个参数必须具有相同的元素类型 T,并且必须可分配给类型为 []T 的切片。复制的元素数是 len(src) 和 len(dst) 中的最小值。作为一种特殊情况,copy 还接受可分配给类型 []byte 的目标参数,并带有字符串类型的源参数。这种形式将字节从字符串复制到字节片中。


copy(dst, src []T) int

copy(dst []byte, src string) int

例子:


var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}

var s = make([]int, 6)

var b = make([]byte, 5)

n1 := copy(s, a[0:])            // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}

n2 := copy(s, s[2:])            // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}

n3 := copy(b, "Hello, World!")  // n3 == 5, b == []byte("Hello")

不要使用 append 覆盖您的输入。为您的输出使用单独的变量(函数参数)。例如,


package main


import "fmt"


func main() {

    args := []string{"2", "3", "8"}

    fmt.Println(args)

    funcArg := make([]string, len(args)-1)

    for i := range args {

        copy(funcArg, args[:i])

        copy(funcArg[i:], args[i+1:])

        fmt.Println(funcArg)

    }

    fmt.Println(args)

}

输出:


[2 3 8]

[3 8]

[2 8]

[2 3]

[2 3 8]


查看完整回答
反对 回复 2021-12-20
  • 3 回答
  • 0 关注
  • 165 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信