跳到主要内容

Go 带 和 不带 的结构体的区别

· 阅读需 3 分钟
素明诚
Full stack development

不带*(直接使用结构体实例)

当你直接使用结构体实例时,每次你将结构体赋值给另一个变量或将其作为参数传递给函数时,Go 都会创建该结构体的一个副本。

type Person struct {
Name string
Age int
}

func incrementAge(p Person) {
p.Age += 1 // 只修改了副本的Age
}

func main() {
person := Person{Name: "John", Age: 30}
incrementAge(person)
fmt.Println(person.Age) // 输出: 30,因为incrementAge中修改的是person的副本
}

在这个例子中,即使incrementAge函数尝试修改PersonAge,主函数中的person变量也不会受到影响,因为它操作的是person的一个副本。

带*(使用指向结构体的指针)

当你使用指向结构体的指针时,你可以直接修改原始结构体实例中的数据,因为指针存储的是结构体数据的内存地址。

func incrementAge(p *Person) {
p.Age += 1 // 直接修改原始结构体实例的Age
}

func main() {
person := Person{Name: "John", Age: 30}
incrementAge(&person)
fmt.Println(person.Age) // 输出: 31,因为incrementAge中修改的是原始person实例
}

在这个例子中,incrementAge函数接收一个指向Person的指针,这意味着它可以直接修改传递给它的实例。当我们传递&personperson的地址)给incrementAge,函数中的任何修改都会反映在main函数中的person上。

那修改的是哪个实例?

修改的是在函数incrementAge调用时传入的person实例的副本。这个副本只在incrementAge函数的作用域内存在,一旦incrementAge函数执行完毕,这个副本就会被丢弃(如果没有其他引用指向它,它会被垃圾回收器回收)。因此,incrementAge函数内部对p.Age的修改不会影响到main函数中的person实例。

为什么设计成这样?

  1. Go 语言在函数调用时通过值传递的方式避免对原始数据的不必要修改,从而保持数据的安全性和一致性。同时,这种方式也避免了在函数调用时不断修改原始数据可能导致的错误和混乱。
  2. 通过值传递,函数接收的是数据副本,这确保了函数的纯粹性(尤其是对于简单函数而言),函数的行为不会因为外部变量的改变而改变,使得函数更易于理解和预测。
  3. 比如对于大型结构体,复制整个结构体可能会导致额外的内存和性能开销。因此,在处理大型数据或需要直接修改原始数据时,使用指针传递会更高效。所以,遇事不决就使用指针就对了。