package main
import "fmt"
type Test struct {
Id int64
Name string
}
func main() {
var list = []Test{
Test{Id: 1, Name: "1XX"},
Test{Id: 2, Name: "2XX"},
Test{Id: 3, Name: "3XX"},
}
for _, v := range list {
v.Name = "888"
}
fmt.Println(list)
}
为啥修改 Name 貌似没生效
1
rrfeng 2021-11-25 17:15:09 +08:00 via Android 2
for range 是 copy 一份对象,所以没改原始的。
用索引访问数组元素就可以了。 |
2
yin1999 2021-11-25 17:15:19 +08:00 via Android 1
因为你获取的是一个拷贝,而不是 slice 中的原始对象
|
3
helone 2021-11-25 17:22:20 +08:00 1
```
for k, _ := range list { list[k].Name = "888" } ``` 这样就行了 |
4
superfatboy OP |
5
superfatboy OP @helone 感谢
|
6
corningsun 2021-11-25 17:26:51 +08:00
Javaer 震怒 ~
|
7
wunonglin 2021-11-25 17:36:25 +08:00 1
```
func main() { var list = []*Test{ {Id: 1, Name: "1XX"}, {Id: 2, Name: "2XX"}, {Id: 3, Name: "3XX"}, } for _, v := range list { v.Name = "888" } for _, v := range list { fmt.Printf("%+v\n", v) } } ``` 用指针就好了 |
8
Rooger 2021-11-25 17:42:41 +08:00 1
原因:for range 的方式其实一种 copy 了 list 。
两种修改方式,一种就是 `list[k].Name = 888`,另一种是将 slice 修改为存储指针 var list = []*Test 。 同样的坑你也应该注意,不能使用 k 和 v 的地址。 |
9
whyso 2021-11-25 17:47:36 +08:00
@corningsun 为啥
|
10
superfatboy OP @wunonglin 这个方法不错
|
11
ClarkAbe 2021-11-25 20:31:35 +08:00 via Android
用指针,和 C 系一样
|
12
ClarkAbe 2021-11-25 20:32:23 +08:00 via Android
不过 js 那边不是有句名言不建议在循环里面修改元素嘛
|
13
corningsun 2021-11-25 21:14:50 +08:00
@whyso
Java 和 Go 的 for 循环 默认策略完全相反。 Java 必须要主动拷贝才会新建对象。这种 List 结构还得深拷贝,不然用的还是同一个对象。 Go 和 Java stream() 比较像了。 |
14
superfatboy OP @ClarkAbe 哈哈,怎么顺手怎么来,不过确实不推荐
|
15
yrj 2021-11-25 23:12:41 +08:00 via iPad
你这么理解,range 的时候,其实是 copy 了一份新的数据给 for 所以你在 for 里修改的是 copy 的新数据。可以按照楼上指针的写法,原理是直接操作了指针
|
16
mmuggle 2021-11-26 00:03:21 +08:00
虽然 for range 确实是循环的副本,但是这个是循环的切片,切片结构是指向一个底层数组的指针
所以我觉得原因是 v 是在 for 循环的时候初始化的,循环体中修改的是 v 的 Name ,而不是切片元素的 Name |
17
SimbaPeng 2021-11-26 01:31:48 +08:00 10
我真的怀疑楼上的回复者,真的会 Golang ?
什么 range copy 了 list ,range 策略,深拷贝浅拷贝?能不能不要在这里误人子弟? 这里的关键就是 v := range list 是个赋值操作,将 list 元素赋值给 v ,golang 里只有值传递,所以 list 元素在复制给 v 的时候必然产生了拷贝。而结构体是值类型,没有引用的对象,所以是拷贝整个结构体,直接修改 v 的属性就是修改拷贝的结构体,所以原结构体属性并不会改变。 同理,但凡你将一个结构体赋值给一个新变量,然后修改新变量的字段,都不会对原结构体有任何影响。 这跟你用没用 range 一点关系都没有。 如果你的 list 里存的是结构体的指针,range 的时候一样会产生拷贝,不过拷贝的是指针结构,底层引用的结构体不会产生拷贝,所以你解指针修改字段的时候,修改的是同一个结构体。但如果将一个新的指针赋值给 v ,原 list 的元素依然不会被改变。 最后总结一句话,golang 只有值传递。 |
18
SimbaPeng 2021-11-26 02:02:01 +08:00
```
func main() { a := []int{1, 2, 3} for i, v := range a { if i == 0 { a[2] = 4 } fmt.Println(v) } } ``` 那些说是因为 range 的是 list 的副本的,自己运行一下这段代码。copy 的是切片,又不是底层数组。 |
19
ila 2021-11-26 07:27:25 +08:00 via Android
range map 时可以修改 map 的 value ,倒推过来看看
|
20
cassyfar 2021-11-26 07:43:53 +08:00
|
21
mmuggle 2021-11-26 09:31:04 +08:00 1
代码其实相当于下面这种
``` package main import "fmt" type Test struct { Id int64 Name string } func main() { var list = []Test{ Test{Id: 1, Name: "1XX"}, Test{Id: 2, Name: "2XX"}, Test{Id: 3, Name: "3XX"}, } v := Test{} for _, v = range list { v.Name = "888" } fmt.Println(list) } ``` |
22
superfatboy OP @mmuggle 你这种写法就容易理解了
|
24
whyso 2021-11-26 11:50:48 +08:00
@SimbaPeng 真相了,LZ 的问题在于 V 而不是 range ,循环修改值我一般用 for i:=0; i<len();i++ {}这种,不修改用 range
|
25
SimbaPeng 2021-11-26 12:23:38 +08:00 1
@Mitt 在我看来你所谓的“两种表达”,描述的本质是不同,楼上有些回复的本意是:range 会 产生迭代对象的副本,所以无法修改原对象。事实是 range 确实会产生 迭代对象的副本,但这不是 LZ 不能用 v 来修改原对象中元素的原因。
有的甚至扯到深浅拷贝,认为 range 产生了深拷贝,但其实 range 只会 产生 切片结构的拷贝,底层引用的数组还是同一个。 |
26
oluoluo 2021-11-26 15:12:11 +08:00 1
```
package main import "fmt" type Item struct { Id uint64 Name string } func main() { list := []Item{ {Id: 1, Name: "1xxx"}, {Id: 2, Name: "2xxx"}, {Id: 3, Name: "3xxx"}, } for i := 0; i < len(list); i++ { fmt.Printf("item %d address: %p\n", i, &list[i]) } for _, v := range list { v.Name = "888" fmt.Printf("list item address: %p\n", &v) } fmt.Println("---------------") fmt.Println(list) } ``` item 0 address: 0x14000060050 item 1 address: 0x14000060068 item 2 address: 0x14000060080 list item address: 0x1400000c030 list item address: 0x1400000c030 list item address: 0x1400000c030 --------------- [{1 1xxx} {2 2xxx} {3 3xxx}] |
27
wangritian 2021-11-26 18:11:17 +08:00
|