英文:
Generic way to duplicate slices
问题
我有一个需要复制切片(以及底层数组的一部分)的需求,这样调用者就不会改变原始数组的元素。我认为我可以编写一个函数来处理特定类型的数组:
func duplicateSliceOfSomeType(sliceOfSomeType []SomeType) []SomeType {
duplicate := make([]SomeType, len(sliceOfSomeType))
copy(duplicate, sliceOfSomeType)
return duplicate
}
但是是否有一种通用的方式来创建相同的方法,可能不需要泛型?
func duplicateSlice(slice []?) []? {
duplicate := make([]?, len(slice))
copy(duplicate, slice)
return duplicate
}
请注意,上述代码中的问号(?)表示需要根据具体情况替换为适当的类型。
英文:
I have a need to duplicate slices (and part of the underlying array) so a caller won't mutate the original elements of an array. I think I can write a function to do this for arrays of specific types:
func duplicateSliceOfSomeType(sliceOfSomeType []SomeType) []SomeType {
dulicate := make([]SomeType, len(sliceOfSomeType))
copy(duplicate, sliceOfSomeType)
return duplicate
}
But is there a way to create the same method generically, perhaps without generics?
func duplicateSlice(slice []?) []?{
duplicate := make([]?, len(slice))
copy(duplicate, slice)
return duplicate
}
答案1
得分: 33
你可以编写一个简单的语句来创建一个切片的浅拷贝:
b := append([]T(nil), a...)
这等同于:
b := make([]T, len(a))
copy(b, a)
例如:
package main
import "fmt"
type T int
func main() {
a := []T{4, 2}
b := append([]T(nil), a...)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
输出结果:
0x10328000 [4 2] 0x10328020 [4 2]
0x10328000 [4 2] 0x10328020 [9 2]
补充说明:
如果人们刚开始学习Go,他们根本不应该使用反射。
- Rob
即使对于专家来说,反射也是微妙的。它暴露了一些细节,其理解取决于对语言的基本工作原理的了解,以及在较小程度上,对其实现方式的了解。即使对于经验丰富的Go程序员来说,它也可能令人困惑;对于新手来说,有更重要、更简单的事情要先学习。那些过早学习反射的人会混淆自己,使他们对这些基本原理的理解变得模糊。最好保持一定的距离,直到整个图景变得清晰。
- Rob
不过,你也可以使用反射来实现:
package main
import (
"fmt"
"reflect"
)
func CopySlice(s interface{}) interface{} {
t, v := reflect.TypeOf(s), reflect.ValueOf(s)
c := reflect.MakeSlice(t, v.Len(), v.Len())
reflect.Copy(c, v)
return c.Interface()
}
type T int
func main() {
{
// append
a := []T{4, 2}
b := append([]T(nil), a...)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
{
// make and copy
a := []T{4, 2}
b := make([]T, len(a))
copy(b, a)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
{
// reflection
a := []T{4, 2}
b := CopySlice(a).([]T)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
}
输出结果:
0xc20800a200 [4 2] 0xc20800a210 [4 2]
0xc20800a200 [4 2] 0xc20800a210 [9 2]
0xc20800a290 [4 2] 0xc20800a2a0 [4 2]
0xc20800a290 [4 2] 0xc20800a2a0 [9 2]
0xc20800a310 [4 2] 0xc20800a320 [4 2]
0xc20800a310 [4 2] 0xc20800a320 [9 2]
[1]: https://groups.google.com/d/topic/golang-nuts/abpF-Ykd0Z0/discussion
英文:
You could write one simple statement to make a shallow copy of a slice,
b := append([]T(nil), a...)
Which is equivalent to,
b := make([]T, len(a))
copy(b, a)
For example,
package main
import "fmt"
type T int
func main() {
a := []T{4, 2}
b := append([]T(nil), a...)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
Output:
0x10328000 [4 2] 0x10328020 [4 2]
0x10328000 [4 2] 0x10328020 [9 2]
ADDENDUM:
> Common difficulties with reflection
>
> If people are new to Go, they shouldn't be using reflection at all.
>
> -rob
>
> Reflection is subtle even for experts. It exposes details whose
> understanding depends on knowing pretty fundamental things about how
> the language works and, to a lesser extent, how it is implemented. It
> can be bewildering even for experienced Go programmers; for newly
> minted Gophers there are much more important, simpler things to learn
> first. Those who learn reflection too early confuse themselves cloud
> their understanding of those fundamentals. Best to keep it at arm's
> length until the rest of the picture is clear.
>
> -rob
That said,
package main
import (
"fmt"
"reflect"
)
func CopySlice(s interface{}) interface{} {
t, v := reflect.TypeOf(s), reflect.ValueOf(s)
c := reflect.MakeSlice(t, v.Len(), v.Len())
reflect.Copy(c, v)
return c.Interface()
}
type T int
func main() {
{
// append
a := []T{4, 2}
b := append([]T(nil), a...)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
{
// make and copy
a := []T{4, 2}
b := make([]T, len(a))
copy(b, a)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
{
// reflection
a := []T{4, 2}
b := CopySlice(a).([]T)
fmt.Println(&a[0], a, &b[0], b)
b[0] = 9
fmt.Println(&a[0], a, &b[0], b)
}
}
Output:
0xc20800a200 [4 2] 0xc20800a210 [4 2]
0xc20800a200 [4 2] 0xc20800a210 [9 2]
0xc20800a290 [4 2] 0xc20800a2a0 [4 2]
0xc20800a290 [4 2] 0xc20800a2a0 [9 2]
0xc20800a310 [4 2] 0xc20800a320 [4 2]
0xc20800a310 [4 2] 0xc20800a320 [9 2]
答案2
得分: 1
Go 1.18
在 Go 1.18 中引入了类型参数,因此可以轻松实现这一点。你可以编写一个通用函数,如下所示:
func duplicateSlice[T any](src []T) []T {
dup := make([]T, len(src))
copy(dup, src)
return dup
}
然后可以这样使用它:
package main
import (
"fmt"
)
func duplicateSlice[T any](src []T) []T {
dup := make([]T, len(src))
copy(dup, src)
return dup
}
func main() {
a := []string{"foo", "bar"}
a2 := duplicateSlice(a)
a[0] = "baz"
fmt.Println(a) // [baz bar]
fmt.Println(a2) // [foo bar]
b := []uint64{8, 12, 30}
b2 := duplicateSlice(b)
b[0] = 7
fmt.Println(b) // [7 12 30]
fmt.Println(b2) // [8 12 30]
}
你可以在这个 GoTip playground 中运行这段代码。
实验性的 slices
包
golang.org/x/exp/slices
包提供了用于切片的通用函数。它可能会在将来被移入标准库中。
我们可以使用 slices.Clone
来实现与之前的 duplicateSlice
函数相同的功能。请注意,这是一个浅拷贝:
Clone 返回切片的副本。元素使用赋值进行复制,因此这是一个浅克隆。
package main
import (
"fmt"
"golang.org/x/exp/slices"
)
func main() {
a := []string{"foo", "bar"}
a2 := slices.Clone(a)
fmt.Println(a2) // [foo bar]
}
GoTip playground: https://gotipplay.golang.org/p/-W3_I0eYLdF
反射,在 Go 1.18 之前
在 Go 1.18 之前,你也可以使用 reflect.Copy
来实现相同的功能:
package main
import (
"fmt"
"reflect"
)
func main() {
src := []int{1,2,3}
target := duplicateSlice(src)
src[0] = 9
fmt.Println(src) // [9 2 3]
fmt.Println(target) // [1 2 3]
}
func duplicateSlice(src interface{}) interface{} {
t := reflect.TypeOf(src)
if t.Kind() != reflect.Slice {
panic("not a slice!")
}
v := reflect.ValueOf(src)
target := reflect.MakeSlice(t, v.Cap(), v.Len())
reflect.Copy(target, v)
return target.Interface()
}
请注意,使用 reflect
包会比当前 接受的答案 中的方法慢得多。将此处提供的代码视为一个参考的人为示例。
Playground 链接:https://play.golang.org/p/vZ1aQOFTLmU
英文:
Go 1.18
With the introduction of type parameters in Go 1.18 this is trivial to accomplish. You can write a generic function like this:
func duplicateSlice[T any](src []T) []T {
dup := make([]T, len(src))
copy(dup, src)
return dup
}
And use it as such:
package main
import (
"fmt"
)
func duplicateSlice[T any](src []T) []T {
dup := make([]T, len(src))
copy(dup, src)
return dup
}
func main() {
a := []string{"foo", "bar"}
a2 := duplicateSlice(a)
a[0] = "baz"
fmt.Println(a) // [baz bar]
fmt.Println(a2) // [foo bar]
b := []uint64{8, 12, 30}
b2 := duplicateSlice(b)
b[0] = 7
fmt.Println(b) // [7 12 30]
fmt.Println(b2) // [8 12 30]
}
You can run this code in this GoTip playground.
<hr>
Experimental slices
package
The package golang.org/x/exp/slices
provides generic functions for slices. It will probably be moved into the standard lib <strike>in Go 1.19.</strike> in the future.
We can use slices.Clone
to accomplish the same as with the previous duplicateSlice
function. Note that this is a shallow copy:
> Clone returns a copy of the slice. The elements are copied using assignment, so this is a shallow clone.
package main
import (
"fmt"
"golang.org/x/exp/slices"
)
func main() {
a := []string{"foo", "bar"}
a2 := slices.Clone(a)
fmt.Println(a2) // [foo bar]
}
GoTip playground: https://gotipplay.golang.org/p/-W3_I0eYLdF
<hr>
Reflection, pre-Go 1.18
Here's also an example of how to use reflect.Copy
to accomplish the same thing before Go 1.18:
package main
import (
"fmt"
"reflect"
)
func main() {
src := []int{1,2,3}
target := duplicateSlice(src)
src[0] = 9
fmt.Println(src) // [9 2 3]
fmt.Println(target) // [1 2 3]
}
func duplicateSlice(src interface{}) interface{} {
t := reflect.TypeOf(src)
if t.Kind() != reflect.Slice {
panic("not a slice!")
}
v := reflect.ValueOf(src)
target := reflect.MakeSlice(t, v.Cap(), v.Len())
reflect.Copy(target, v)
return target.Interface()
}
Keep in mind that using the reflect
package will be much slower than using the approach in the currently accepted answer. Consider the code presented here as just a contrived example for reference.
Link to playground: https://play.golang.org/p/vZ1aQOFTLmU
答案3
得分: 0
你可以使用reflect
包中的reflect.Copy
函数来对任何类型进行复制操作。具体的文档可以参考:http://golang.org/pkg/reflect/#Copy
英文:
You can do a copy on any type by using the reflect
package, specifically reflect.Copy
: http://golang.org/pkg/reflect/#Copy
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论