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

如何修改切片中结构的字段?

如何修改切片中结构的字段?

Go
DIEA 2022-06-27 16:59:13
我有一个名为的 JSON 文件test.json,其中包含:[    {        "name" : "john",        "interests" : ["hockey", "jockey"]    },    {        "name" : "lima",        "interests" : ["eating", "poker"]    }]现在我编写了一个 golang 脚本,它将 JSON 文件读取到结构切片中,然后在进行条件检查时,通过迭代切片来修改结构字段。这是我迄今为止尝试过的:package mainimport (    "log"    "strings"    "io/ioutil"    "encoding/json")type subDB struct {    Name       string   `json:"name"`    Interests  []string `json:"interests"`}var dbUpdate []subDBfunc getJSON() {    // open the file    filename := "test.json"    val, err := ioutil.ReadFile(filename)    if err != nil {        log.Fatal(err)    }    err = json.Unmarshal(val, &dbUpdate)}func (v *subDB) Change(newresponse []string) {    v.Interests = newresponse}func updater(name string, newinterest string) {    // iterating over the slice of structs    for _, item := range dbUpdate {        // checking if name supplied matches to the current struct        if strings.Contains(item.Name, name) {            flag := false  // declare a flag variable            // item.Interests is a slice, so we iterate over it            for _, intr := range item.Interests {                // check if newinterest is within any one of slice value                if strings.Contains(intr, newinterest) {                    flag = true                    break  // if we find one, we terminate the loop                }            }            // if flag is false, then we change the Interests field            // of the current struct            if !flag {                // Interests holds a slice of strings                item.Change([]string{newinterest}) // passing a slice of string            }        }    }}func main() {    getJSON()    updater("lima", "jogging")    log.Printf("%+v\n", dbUpdate)}我得到的输出是:[{Name:john Interests:[hockey jockey]} {Name:lima Interests:[eating poker]}]但是我应该得到如下输出:[{Name:john Interests:[hockey jockey]} {Name:lima Interests:[jogging]}]我的理解是,既然Change()传了一个指针,就应该直接修改字段。谁能指出我做错了什么?
查看完整描述

1 回答

?
一只甜甜圈

TA贡献1836条经验 获得超5个赞

问题

让我们引用语言规范在循环上所说的内容for ... range:


带有“range”子句的“for”语句遍历数组、切片、字符串或映射的所有条目,或通道上接收的值。对于每个条目,如果存在,它将迭代值分配给相应的迭代变量,然后执行该块。


所以,在


for _, item := range dbUpdate { ... }

整个语句形成了一个作用域,其中声明了一个名为的变量item,并为它分配了每个元素的值dbUpdate,依次从第一个到最后一个 - 因为语句执行它的迭代。


Go 中的所有赋值,无论何时何地,都会将被赋值的表达式的值复制到接收该值的变量中。


所以,当你有


type subDB struct {

    Name       string   `json:"name"`

    Interests  []string `json:"interests"`

}


var dbUpdate []subDB

你有一个切片,它的后备数组包含一组元素,每个元素都有 type subDB。

因此,当for ... range迭代切片时,每次迭代subDB都会完成当前切片元素中包含的值的字段的浅拷贝:这些字段的值被复制到变量item中。


我们可以将发生的事情重写为:


for i := 0; i < len(dbUpdate); i++ {

  var item subDB


  item = dbUpdate[i]


  ...

}

如您所见,如果您item在循环体中发生变异,您对其所做的更改不会以任何方式影响当前正在迭代的集合元素。


解决方案

从广义上讲,解决方案是充分了解 Go 在其实现的大部分内容中都非常简单的事实,因此range并不神奇:迭代变量只是一个变量,对它的赋值只是一个赋值。


至于解决特定问题,有多种方法。


通过索引引用集合元素


for i := range dbUpdate {

  dbUpdate[i].FieldName = value

}

对此的推论是,有时,当元素很复杂或者您想将其突变委托给某个函数时,您可以使用指向它的指针:


for i := range dbUpdate {

  p := &dbUpdate[i]


  mutateSubDB(p)

}


...


func mutateSubDB(p *subDB) {

  p.SomeField = someValue

}

将指针保留在切片中

如果你的切片像


var dbUpdates []*subDB

...并且您会保留指向(通常是堆分配的)SubDB值的指针,


for _, ptr := range dbUpdate { ... }

语句自然会将指向 SubDB(匿名)变量的指针复制到其中ptr,因为切片包含指针,因此赋值复制了一个指针。


由于所有包含相同地址的指针都指向相同的值,因此通过保存在迭代变量中的指针改变目标变量将改变切片元素所指向的相同事物。


选择哪种方法通常应该取决于考虑因素,而不是考虑如何迭代元素——因为一旦你理解了你的代码为什么不起作用,你就不再有这个问题了。


通常:如果您的值非常大,请考虑保留指向它们的指针。如果您的值需要同时从多个位置引用,请保留指向它们的指针。在其他情况下,直接保留这些值——这极大地提高了 CPU 数据缓存的局部性(简单地说,当您即将访问下一个元素时,它的内容很可能已经从内存中获取,当CPU 必须追逐一个指针以通过它访问某个任意内存位置)。


查看完整回答
反对 回复 2022-06-27
  • 1 回答
  • 0 关注
  • 153 浏览
慕课专栏
更多

添加回答

举报

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