修改函数中的切片。

huangapple go评论88阅读模式
英文:

modify slice in function

问题

我注意到今天,当我通过值传递切片时,切片的地址不会改变,所以在这种情况下,我期望main()中的切片与函数内部的切片具有相同的底层数组。

但是,这是不可能的,因为函数内部和主函数中的切片内容在函数返回后是不同的。

以下是一个示例代码(使用golang 1.16),其中append()不会创建新对象(因为我设置了容量以避免这种情况):

package main

import (
	"fmt"
)

// 必须使用指针,因为要修改切片
func modSlice(mySlice *[]string) {
	*mySlice = append(*mySlice, "abc")
	fmt.Printf("In modSlice(): %p %v\n", *mySlice, len(*mySlice))
}

// 这个函数显示mySlice与main函数中的地址相同!
func modSliceFail(mySlice []string) {
	mySlice = append(mySlice, "ABC")
	fmt.Printf("In modSliceFail(): %p %v\n", mySlice, len(mySlice))
}

func main() {
	mySlice := make([]string, 0, 100)
	fmt.Printf("In main() before: %p %v\n\n", mySlice, len(mySlice))

	modSlice(&mySlice)
	fmt.Printf("In main() after modSlice(pointer to slice): %p %v\n\n", mySlice, len(mySlice))

	modSliceFail(mySlice)
	fmt.Printf("In main() after modSliceFail(): %p %v\n\n", mySlice, len(mySlice))
}

运行上述代码会产生以下输出:

In main() before: 0xc000115500 0

In modSlice(): 0xc000115500 1
In main() after modSlice(pointer to slice): 0xc000115500 1

In modSliceFail(): 0xc000115500 2
In main() after modSliceFail(): 0xc000115500 1

请注意,切片的指针在整个过程中保持不变。如果在modSliceFail()中切片是按值复制的,那么这是如何可能的呢?

编辑
到目前为止,前两个答案都没有回答我的问题。我理解append的工作原理,如果容量不足,将返回一个新对象。这就是为什么我在上面的示例代码中设置了容量,以避免这种情况。问题在于,在modSliceFail()函数内部,指针与主函数中的指针相同,无论是在追加之前(显示函数不仅使用相同的切片头部,还使用相同的切片对象),还是在追加之后(再次显示对象,这不是容量为1的情况)。

英文:

I noticed today that the address of a slice does not change when I pass slice by value, so in that case I would expect the slice in main() to have the same backing array as the slice inside the function.

BUT that cannot be, because the contents of the slice are different in the function vs in main after function return.

Here is sample code to demonstrate (golang 1.16), where append() DOES NOT CREATE a new object (because I set the capacity to avoid that):

package main

import (
	"fmt"
)

// must use pointer since modifying the slice
func modSlice(mySlice *[]string) {
	*mySlice = append(*mySlice, "abc")
	fmt.Printf("In modSlice(): %p %v\n", *mySlice, len(*mySlice))
}

// this ones shows same address for mySlice as in main!
func modSliceFail(mySlice []string) {
	mySlice = append(mySlice, "ABC")
	fmt.Printf("In modSliceFail(): %p %v\n", mySlice, len(mySlice))
}

func main() {
	mySlice := make([]string, 0, 100)
	fmt.Printf("In main() before: %p %v\n\n", mySlice, len(mySlice))

	modSlice(&mySlice)
	fmt.Printf("In main() after modSlice(pointer to slice): %p %v\n\n", mySlice, len(mySlice))

	modSliceFail(mySlice)
	fmt.Printf("In main() after modSliceFail(): %p %v\n\n", mySlice, len(mySlice))
}

which produces the following output:

In main() before: 0xc000115500 0

In modSlice(): 0xc000115500 1
In main() after modSlice(pointer to slice): 0xc000115500 1

In modSliceFail(): 0xc000115500 2
In main() after modSliceFail(): 0xc000115500 1

Notice how the pointer to the slice is the same throughout. How is this possible, if the slice is copied by value in the case of modSliceFail()?

Edit:
The 2 answers so far do not answer my question. I understand how append works and that if the capacity is insufficient, a new object will be returned. That's why I set the capacity in the example code above, so this would not happen. The issue is that pointer inside the fail function is same as in main, both before append (showing the function is using not just the same slice header, but the same slice object) and after append (showing again the object, which is not the case of capacity is 1).

答案1

得分: 1

其他答案很好,我只想总结一下关于这个主题的一些要点。如果参数是切片,你可以编辑但不能追加,无论容量如何。如果参数是指针,你可以编辑和追加。示例代码如下:

package main
import "fmt"

func oneTwo(b []byte) {
   b[0] = 1
   b = append(b, 2)
}

func threeFour(b *[]byte) {
   (*b)[0] = 3
   *b = append(*b, 4)
}

func main() {
   b := make([]byte, 1, 9)
   oneTwo(b)
   fmt.Println(b) // [1]
   threeFour(&b)
   fmt.Println(b) // [3 4]
}
英文:

Other answer is great, I just wanted to summarize some of the points on this
topic. If the argument is a slice, you can edit but not append, regardless of
capacity. If the argument is a pointer, you can edit and append. Example:

package main
import "fmt"

func oneTwo(b []byte) {
   b[0] = 1
   b = append(b, 2)
}

func threeFour(b *[]byte) {
   (*b)[0] = 3
   *b = append(*b, 4)
}

func main() {
   b := make([]byte, 1, 9)
   oneTwo(b)
   fmt.Println(b) // [1]
   threeFour(&b)
   fmt.Println(b) // [3 4]
}

答案2

得分: 0

如果你仔细阅读我的问题,它是关于指针的,我期望它们是不同的,但实际输出显示它们是相同的,而我们知道它们应该是不同的。所有的答案都集中在典型的其他问题上,即为什么在切片追加(append())之后指针可以不同。

正确的答案是,对切片使用%p并不打印切片的地址,而是打印切片的第一个元素的地址,即其后备数组的地址(参见https://pkg.go.dev/fmt?utm_source=gopls#pkg-overview)。

所以我的示例代码实际上没有打印我认为它打印的内容。这是一个愚蠢的错误。如果我在切片本身而不是切片的地址上使用%p,一切就都清楚了。

这里是简化的代码示例:

package main

import (
	"fmt"
)

func modSliceFail(mySlice []string) {
	fmt.Println("\nIn modSlice():")
	fmt.Printf("%50v: %p    %p\n", "mySlice header address, backend array address", &mySlice, mySlice)
	fmt.Printf("%50v\n", "(header different from main, same backing array)")
}

func main() {
	mySlice := make([]string, 0)
	fmt.Println("INIT:")
	fmt.Printf("%50v: %p    %p\n", "mySlice header address, backend array address", &mySlice, mySlice)

	modSliceFail(mySlice)
}

输出结果为:

INIT:
     mySlice header address, backend array address: 0xc00000c030    0xc000010240

In modSlice():
     mySlice header address, backend array address: 0xc00000c060    0xc000010240
  (header different from main, same backing array)

在我的原始帖子中,我打印的是后备数组的地址(下面输出中的第二列),而不是切片的地址。

英文:

If you read carefully my question, it is about pointers I'm expecting to be different but instead output shows them to be the same, whereas we know they should be different. The answers all focus on the typical other question of why pointers can be different after slice append() etc.

The correct answer is that %p on a slice does not print the address of slice, it prints the address of the first element of slice, ie of its backing array (see https://pkg.go.dev/fmt?utm_source=gopls#pkg-overview).

So my sample code was not actually printing what I thought it was. It's admittedly a silly mistake. If I use %p on the address of slice instead of slice itself, everything makes sense.

Here is paired down code that shows this:

package main

import (
	"fmt"
)

func modSliceFail(mySlice []string) {
	fmt.Println("\nIn modSlice():")
	fmt.Printf("%50v: %p    %p\n", "mySlice header address, backend array address", &mySlice, mySlice)
	fmt.Printf("%50v\n", "(header different from main, same backing array)")
}

func main() {
	mySlice := make([]string, 0)
	fmt.Println("INIT:")
	fmt.Printf("%50v: %p    %p\n", "mySlice header address, backend array address", &mySlice, mySlice)

	modSliceFail(mySlice)
}

outputs

INIT:
     mySlice header address, backend array address: 0xc00000c030    0xc000010240

In modSlice():
     mySlice header address, backend array address: 0xc00000c060    0xc000010240
  (header different from main, same backing array)

In my original post, I was printing the address of backing array (2nd column in the below output), not that of slice.

答案3

得分: -1

在切片上追加元素 如果需要重新分配支持数组,可能会返回一个_新的_切片。

在playground中的这个例子

func printSlice(desc string, s []int) {
	if cap(s) == 0 {
		fmt.Printf("%s:\tempty\n", desc)
		return
	}
	full := s[:cap(s)]
	fmt.Printf("%s:\tlen=%d,\tcap=%d,\tarray=%v @ %p,\tslice=%v\n",
		desc, len(s), cap(s), full, &full[0], s)
}

func main() {
	arr := [10]int{}

	s := arr[:0]
	printSlice("start", s)

	// There is plenty of room for 5 elements
	s = append(s, 1, 1, 1, 1, 1)
	printSlice("first 5", s)

	// and just enough room  for 5 more, so the backing array is reused
	s = append(s, 2, 2, 2, 2, 2)
	printSlice("second 5", s)

	// but an entirely new slice with a different array is returned once it can't fit
	s = append(s, 3, 3, 3, 3, 3)
	printSlice("third 5", s)
}

将打印出:

start:      len=0,	cap=10,	array=[0 0 0 0 0 0 0 0 0 0]                     @ 0xc0000be000,	slice=[]
first 5:	len=5,	cap=10,	array=[1 1 1 1 1 0 0 0 0 0]                     @ 0xc0000be000,	slice=[1 1 1 1 1]
second 5:	len=10,	cap=10,	array=[1 1 1 1 1 2 2 2 2 2]                     @ 0xc0000be000,	slice=[1 1 1 1 1 2 2 2 2 2]
third 5:	len=15,	cap=20,	array=[1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 0 0 0 0 0] @ 0xc0000c4000,	slice=[1 1 1 1 1 2 2 2 2 2 3 3 3 3 3]

这就是为什么append内置函数返回一个切片而不是直接修改它的原因:为了获得增长后的新切片,你必须替换append所给定的值。

在你的代码中,当你写下

    *mySlice = append(*mySlice, "abc")

你正在覆盖切片的值,所以调用者能够观察到append的结果。然而,当你写下

    mySlice = append(mySlice, "ABC")

那只是覆盖了一个局部变量,所以调用者无法看到新的切片。如果你需要一个可能需要改变切片维度的函数,你可能想要写成这样:

func modSliceFailFixed(mySlice []string) []string {
    mySlice = append(mySlice, "ABC")
    return mySlice
}
// 调用方式:
s = modSliceFailFixed(s)
英文:

Appending onto a slice may return a new slice if it needs to reallocate a backing array.

This example in the playground:

func printSlice(desc string, s []int) {
	if cap(s) == 0 {
		fmt.Printf("%s:\tempty\n", desc)
		return
	}
	full := s[:cap(s)]
	fmt.Printf("%s:\tlen=%d,\tcap=%d,\tarray=%v @ %p,\tslice=%v\n",
		desc, len(s), cap(s), full, &full[0], s)
}

func main() {
	arr := [10]int{}

	s := arr[:0]
	printSlice("start", s)

	// There is plenty of room for 5 elements
	s = append(s, 1, 1, 1, 1, 1)
	printSlice("first 5", s)

	// and just enough room  for 5 more, so the backing array is reused
	s = append(s, 2, 2, 2, 2, 2)
	printSlice("second 5", s)

	// but an entirely new slice with a different array is returned once it can't fit
	s = append(s, 3, 3, 3, 3, 3)
	printSlice("third 5", s)
}

will print:

start:      len=0,	cap=10,	array=[0 0 0 0 0 0 0 0 0 0]                     @ 0xc0000be000,	slice=[]
first 5:	len=5,	cap=10,	array=[1 1 1 1 1 0 0 0 0 0]                     @ 0xc0000be000,	slice=[1 1 1 1 1]
second 5:	len=10,	cap=10,	array=[1 1 1 1 1 2 2 2 2 2]                     @ 0xc0000be000,	slice=[1 1 1 1 1 2 2 2 2 2]
third 5:	len=15,	cap=20,	array=[1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 0 0 0 0 0] @ 0xc0000c4000,	slice=[1 1 1 1 1 2 2 2 2 2 3 3 3 3 3]

This is why the append builtin returns a slice instead of modifying it in place: in order to get the new slice after it is grown, you have to replace the value that was given to append.

In your code, when you say

    *mySlice = append(*mySlice, "abc")

you are overwriting the slice value, so the caller is able to observe the result of the append. However, when you say

    mySlice = append(mySlice, "ABC")

that is only overwriting a local variable, so the caller can't see the new slice. You will likely want to write something like

func modSliceFailFixed(mySlice []string) []string {
    mySlice = append(mySlice, "ABC")
    return mySlice
}
// call like:
s = modSliceFailFixed(s)

if you need to have a function that might require changing the dimensions of a slice.

huangapple
  • 本文由 发表于 2021年7月12日 11:31:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/68341611.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定