golang range内修改struct字段数据的坑
前提知识
golang中struct和slice为值赋值,map为引用赋值。
range语句中 for k,v := range val 这里面的v是只被声明一次,每次迭代只会更新至
需求
开发中普遍会存在这么一种需求,批量或者通过查找找到列表(list, set,数组)中的对象,修改其中的一个字段的值。
java以及其他语言的做法
static class Person {
public Person(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
Person[] persons = new Person[5];
for (int i = 0; i < 5; i++) {
persons[i] = new Person("person" + (i + 1), 0);
}
Arrays.stream(persons).forEach(e -> e.age = 10);
for (Person person : persons) {
System.out.println(person);
}
}
对于java而言,因为所有传递都是指针传递,所以这样做是没有问题的,修改是肯定可以成功的
使用golang的range
package main
import (
"fmt"
"strconv"
)
type Person struct {
Name string
Age int
}
func main() {
persons := make([]Person, 5)
for i := 0; i < 5; i++ {
persons[i] = Person{
Name: "name_" + strconv.Itoa(i+1),
Age: 0,
}
}
for i, person := range persons {
person.Age = i + 1
}
for _, person := range persons {
fmt.Println(person)
}
}
输出结果:
{name_1 0}
{name_2 0}
{name_3 0}
{name_4 0}
{name_5 0}
很明显,对应着前提知识,每次的person的指针地址都是新的,因为是值传递,指针指向的数据区域也是新的,所以更改知识针对一个临时的变量person,而非真实的信息,而且不能直接使用&v去作为指向原始元素的地址,此时如果像得到原始元素的地址,就要找到原始元素。可以对上述方案进行微调:
package main
import (
"fmt"
"strconv"
)
type Person struct {
Name string
Age int
}
func main() {
persons := make([]Person, 5)
for i := 0; i < 5; i++ {
persons[i] = Person{
Name: "name_" + strconv.Itoa(i+1),
Age: 0,
}
}
for i := range persons {
persons[i].Age = i + 1
}
for _, person := range persons {
fmt.Println(person)
}
}
输出结果:
{name_1 1}
{name_2 2}
{name_3 3}
{name_4 4}
{name_5 5}
最终还是通过索引i获取到了原始元素的只是,更改就成功了。
总结
其实大家一开始接触golang的时候也没有注意为啥提供了两种range的遍历方案,而不是像java一样只提供了一种增强for循环的方案,这和golang在range循环的时候的引用传递策略有关,只读的情况下可以用 for _,v,如果有修改需求,切记要使用 for i 或者for i,v,使用slice[i]来操作原数据。