我知道一切都是通过Go中的值传递的,这意味着如果我给一个函数提供了一个分片,并且该函数使用内置append函数附加到该分片上,则原始分片将不会具有附加在该函数范围内的值。
例如:
nums := []int{1, 2, 3}
func addToNumbs(nums []int) []int {
nums = append(nums, 4)
fmt.Println(nums) // []int{1, 2, 3, 4}
}
fmt.Println(nums) // []int{1, 2, 3}
Run Code Online (Sandbox Code Playgroud)
这给我带来了一个问题,因为我试图对累积的切片进行递归,基本上是一个reduce类型函数,除了reducer本身会调用它本身。
这是一个例子:
func Validate(obj Validatable) ([]ValidationMessage, error) {
messages := make([]ValidationMessage, 0)
if err := validate(obj, messages); err != nil {
return messages, err
}
return messages, nil
}
func validate(obj Validatable, accumulator []ValidationMessage) error {
// If something is true, recurse
if something {
if err := validate(obj, accumulator); err != nil {
return err
}
}
// Append to the accumulator passed in
accumulator = append(accumulator, message)
return nil
}
Run Code Online (Sandbox Code Playgroud)
上面的代码给了我与第一个示例相同的错误,因为accumulator不会获得所有附加值,因为它们仅存在于函数范围内。
为了解决这个问题,我将指针结构传递给函数,该结构包含累加器。该解决方案效果很好。
我的问题是,是否有更好的方法可以做到这一点?
更新了解决方案(感谢icza):
我只是在递归函数中返回切片。这样的脸庞,应该想到了。
func Validate(obj Validatable) ([]ValidationMessage, error) {
messages := make([]ValidationMessage, 0)
return validate(obj, messages)
}
func validate(obj Validatable, messages []ValidationMessage) ([]ValidationMessage, error) {
err := v.Struct(obj)
if _, ok := err.(*validator.InvalidValidationError); ok {
return []ValidationMessage{}, errors.New(err.Error())
}
if _, ok := err.(validator.ValidationErrors); ok {
messageMap := obj.Validate()
for _, err := range err.(validator.ValidationErrors) {
f := err.StructField()
t := err.Tag()
if v, ok := err.Value().(Validatable); ok {
return validate(v, messages)
} else if _, ok := messageMap[f]; ok {
if _, ok := messageMap[f][t]; ok {
messages = append(messages, ValidationMessage(messageMap[f][t]))
}
}
}
}
return messages, nil
}
Run Code Online (Sandbox Code Playgroud)
And*_*ewS 33
如果要将切片作为参数传递给函数,并让该函数修改原始切片,则必须传递指向切片的指针:
func myAppend(list *[]string, value string) {
*list = append(*list, value)
}
Run Code Online (Sandbox Code Playgroud)
我不知道 Go 编译器在这方面是幼稚还是聪明;性能留作评论部分的练习。
hao*_*hao 15
您传递的切片是对数组的引用,这意味着大小是固定的。如果您刚刚修改了存储的值,没关系,该值将在被调用函数之外更新。
但是,如果您向切片添加新元素,它将重新切片以容纳新元素,换句话说,将创建一个新切片,并且不会覆盖旧切片。
总而言之,如果需要扩展或剪切切片,请将指针传递给切片。否则,使用切片本身就足够了。
我需要解释一些重要的事实。要向作为值传递给函数的切片添加新元素,有两种情况:
底层数组达到其容量,创建一个新切片来替换原始切片,显然原始切片不会被修改。
底层数组尚未达到其容量,已被修改。但是len切片的字段没有被覆盖,因为切片是按值传递的。因此,原始切片不会知道其 len 被修改,从而导致切片未被修改。
如果切片的当前大小不足以附加新值,从而更改基础数组,则切片会根据需要动态增长。如果未返回此新切片,则您的追加更改将不可见。
例:
package main
import (
"fmt"
)
func noReturn(a []int, data ...int) {
a = append(a, data...)
}
func returnS(a []int, data ...int) []int {
return append(a, data...)
}
func main() {
a := make([]int, 1)
noReturn(a, 1, 2, 3)
fmt.Println(a) // append changes will not visible since slice size grew on demand changing underlying array
a = returnS(a, 1, 2, 3)
fmt.Println(a) // append changes will be visible here since your are returning the new updated slice
}
Run Code Online (Sandbox Code Playgroud)
结果:
[0]
[0 1 2 3]
Run Code Online (Sandbox Code Playgroud)
注意: