为什么在Go中传递指针 解引用赋值后修改属性不改变原值?

指针传参,如果通过解引用获得原值后直接修改属性,则外层属性也会修改。这个很好理解。但为什么再赋值一次,就不是这样的结果呢?

有如下代码

package main

func main() {
    person := Person{age: 10}

    person2 := *(&person)
    person2.age = 30
    println(person.age)
    println(person2.age)
    println("======")

    setAgeByPointer(&person)
    println(person.age)

    println("======")
    setAge(&person)
    println(person.age)

}

type Person struct {
    age int
}

func setAgeByPointer(person *Person) {
    person2 := *person
    person2.age = 55
}

func setAge(person *Person) {
    (*person).age = 55
}

最后的输出结果是这样

10
30
======
10
======
55

一个是
person2 := *person
person2.age = 55

另一个是 (*person).age = 55

为什么上面那个没有修改原值,下面修改了原值,我理解此时都直接修改了原内存地址里的值呀

阅读 5.1k
5 个回答

person2 := *person这行代码通过解引用person得到一个新的Person值,并赋给person2。此时person2person的一个副本,任何对person2的修改都不会影响person
(*person).age = 55这行代码通过指针修改了personage。由于这里是直接操作指向原始数据的内存地址,因此修改会反映到原始对象上。


在 Go 语言中,所有的参数传递都是通过值传递的方式进行的,都是传递副本。
关于指针和解引用赋值的行为不要混淆。

代码分析

main 函数

func main() {
    person := Person{age: 10}
    person2 := *(&person) // 创建 person 的副本
    person2.age = 30 // 修改 person2 的 age 属性
    fmt.Println(person.age) // 打印 person.age,结果为 10
    fmt.Println(person2.age) // 打印 person2.age,结果为 30
    
    setAgeByPointer(&person) // 传递 person 的指针
    fmt.Println(person.age) // 打印 person.age,结果为 10
    
    setAge(&person) // 传递 person 的指针
    fmt.Println(person.age) // 打印 person.age,结果为 55
}

setAgeByPointer 函数

func setAgeByPointer(person *Person) {
    person2 := *person // 创建 person 的副本
    person2.age = 55 // 修改 person2 的 age 属性
}

setAge 函数

func setAge(person *Person) {
    (*person).age = 55 // 直接修改 person 的 age 属性
}

执行流程图

graph TD;
    A[main 函数开始] --> B[创建 person: Person{age: 10}]
    B --> C[person2 := *(&person)]
    C --> D[person2.age = 30]
    D --> E[打印 person.age: 10]
    D --> F[打印 person2.age: 30]
    E --> G[调用 setAgeByPointer(&person)]
    G --> H[person2 := *person]
    H --> I[person2.age = 55]
    I --> J[打印 person.age: 10]
    J --> K[调用 setAge(&person)]
    K --> L[(*person).age = 55]
    L --> M[打印 person.age: 55]

解释

  • 创建副本:当使用 person2 := *(&person) 时,person2person 的一个副本。修改 person2 不会影响 person 的值。
  • setAgeByPointer:在 setAgeByPointer 函数中,person2 := *person 同样是创建了 person 的一个副本。因此,修改 person2 不会影响原始的 person
  • setAge:在 setAge 函数中,(*person).age = 55 直接修改了 personage 属性,因为 person 是通过指针传递的,修改指针指向的值会影响原始变量。
  1. person2 := *person中是先取出person的值然后赋给person2,在Go中struct是值传递的。
  2. age属性是基本类型int,它也是值传递的,所以此处person2.age和person.age是完全独立不相关的,修改不会相互影响。
  3. 如果使用person := &Person{age:10}创建则它是指针类型,此时person2 := person,然后修改person2.age会让person.age一起改变。
  4. 引用传递本质上是通过指针实现的,而在Go中同时存在指针和引用传递(map、slice、chan类型),关于引用传递可以查询深拷贝浅拷贝进行了解。

第一个和第二个的区别是,第一个是被赋值了,但是被赋值的是person的副本,而不是地址引用-指针,所以被赋值的那个person2修改age,只会影响到person2而已,不会影响person本身。

第二个的虽然也用了&*去折腾了一圈,但是还是本身person,所以你修改age,其实还是对person的修改。

给你增加个例子,这个其实虽然 person3 也是被赋值,但是被赋的是person的指针,也就是内存地址。所以你对person3的修改,其实就是对person本身的修改。

person3 := &person
person3.age = 1000
println(person3.age, person.age)
  • 是值 &是地址呀!
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏