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

Posted

技术标签:

【中文标题】如何修改切片中结构的字段?【英文标题】:How to modify field of a struct in a slice? 【发布时间】:2020-12-31 07:44:39 【问题描述】:

我有一个名为 test.json 的 JSON 文件,其中包含:

[
    
        "name" : "john",
        "interests" : ["hockey", "jockey"]
    ,
    
        "name" : "lima",
        "interests" : ["eating", "poker"]
    
]

现在我已经编写了一个 golang 脚本,它将 JSON 文件读取到一个结构切片中,然后在进行条件检查时,通过迭代切片来修改一个结构字段。

这是我迄今为止尝试过的:

package main

import (
    "log"
    "strings"
    "io/ioutil"
    "encoding/json"
)

type subDB struct 
    Name       string   `json:"name"`
    Interests  []string `json:"interests"`


var dbUpdate []subDB

func 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([]stringnewinterest) // 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()既然传了一个指针,应该直接修改字段。谁能指出我做错了什么?

【问题讨论】:

在切片中使用指针,即dbUpdate []*subDB 或使用索引dbUpdate[i].FieldToChange = value 我的猜测是,否决票主要是因为这种问题通常可以通过阅读文档/实验来解决(例如,使用一些“printf-style 调试”,你可以自己很容易看到改变迭代变量不会影响切片,这可能会引导你走上正确的轨道)。这个问题也不是真的最小,如果你想试试to create one你可能也已经自己找到了解决方案。 不过,我个人认为这个问题那么不好以至于值得否决,而且我认为我可以看到您在寻找解决方案方面的诚实尝试——如果不是太坚持的话。所以我没有否决它;-) @kostix 我尽力创建最少的代码,因为我处理的原始代码涉及 HTTP 请求和额外的解析。无论如何,感谢您的 cmets,我会在问下一个问题时记住这一点。 【参考方案1】:

问题

我们来引用一下语言规范says on the for ... range loops:

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

所以,在

for _, item := range dbUpdate  ... 

整个语句形成一个范围,其中声明了一个名为item变量,它被分配一个值 dbUpdate 的每个元素,依次,从第一个到最后一个——语句执行它的迭代。

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

所以,当你有

type subDB struct 
    Name       string   `json:"name"`
    Interests  []string `json:"interests"`


var dbUpdate []subDB

您有一个切片,其后备数组包含一组元素,每个元素的类型为 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 数据cache locality(简而言之,当您即将访问下一个元素时,它的内容很可能已经从内存中获取,当CPU 必须追逐一个指针以通过它访问某个任意内存位置)。

【讨论】:

以上是关于如何修改切片中结构的字段?的主要内容,如果未能解决你的问题,请参考以下文章

go语言切片作为函数参数的研究

go 学习 3

如何使用 Go 反射 pkg 获取切片结构字段的类型?

使用切片!在变量上正在修改填充变量的节点属性

go切片遍历

sqlserver如何修改表某个字段的属性?