目录

Go 语言结构体指针与值详解

在 Go 中,结构体是一种常用的数据类型,用于表示复杂数据结构。在使用结构体时,理解结构体指针和结构体值之间的区别非常重要。

结构体值

结构体值是结构体类型的实例,它直接存储其数据。当你创建一个结构体值时,它会在堆栈上分配内存来存储其字段。下面是一个示例

1
2
3
4
5
6
type Person struct {
    name string
    age  int
}

p := Person{name: "John", age: 30}

在这个示例中,p 是一个结构体值,它将数据直接存储在堆栈上分配的内存中。

结构体指针

另一方面,结构体指针是结构体实例的指针。它存储结构体实例的内存地址,而不是实例本身。下面是 一个示例

1
2
3
4
5
6
type Person struct {
    name string
    age  int
}

p := &Person{name: "John", age: 30}

在这个示例中,p 是一个结构体指针,它存储Person实例的内存地址。

主要区别

以下是结构体指针和值之间的主要区别:

内存分配

  • 结构体值在堆栈上分配内存。
  • 结构体指针在堆上分配内存。

数据存储

  • 结构体值将数据直接存储在分配的内存中。
  • 结构体指针存储指向结构体实例的内存地址。

修改

  • 对结构体值的更改仅影响局部副本。
  • 对结构体指针的更改会影响原始实例。

使用场景

以下是选择使用结构体指针或值的一些场景:

使用结构体值

  • 处理小型、不可变的数据结构时。
  • 当性能至关重要,并且要避免动态内存分配时。
  • 当需要创建结构体实例的副本时。

使用结构体指针

  • 处理大型、可变的数据结构时。
  • 当需要从多个位置修改原始实例时。
  • 当要使用nil值来表示空或未初始化的结构体实例时。

最佳实践

对可变数据使用结构体指针

如果结构体具有可变字段,请考虑使用结构体指针,以确保更改正确传播。

对不可变数据使用结构体值

如果结构体不可变,请使用结构体值以避免不必要的动态内存分配。

避免不必要的指针解引用

最小化代码中指针解引用的数量,以提高性能和可读性。

将方法定义在值还是指针上?

1
2
func (s *MyStruct) pointerMethod() { } // 指针方法
func (s MyStruct)  valueMethod()   { } // 值方法

在定义结构体方法时,接收者(上例中的 s)的行为就像方法的一个参数一样。因此,是将接收者定义为值还是指针,就等同于函数的参数是值还是指针的问题。这里有一些需要考虑的因素:

  • 修改接收者: 最重要的一点是这个方法是否需要修改接收者?如果是,那么接收者必须是一个指针。(切片和 map 本身就是引用类型,因此它们的情况稍微有点特殊,但是例如要在方法中改变切片的长度,接收者仍然必须是一个指针。)在上例中,如果 pointerMethod 修改了 s 的字段,调用者将会看到这些改变,但是 valueMethod 会使用调用者参数的一个副本(这就是按值传递的定义),因此它所做的修改对调用者来说是不可见的。

  • 效率: 如果接收者是一个大的结构体,那么使用指针接收者可能会更省内存。

  • 一致性: 如果类型的某些方法必须使用指针接收者,那么其他方法也应该使用,这样方法集无论如何使用该类型都是一致的。

  • 小型值类型: 对于基本类型、切片和小结构体等类型,值接收者是非常高效的,所以除非方法的语义要求使用指针,否则值接收者既高效又清晰。

结论

理解 Go 语言结构体指针和值之间的区别对于编写高效、有效且可伸缩的 Go 程序至关重要。根据应用场景选择正确的方法,可以确保结构体按预期工作,并优化代码的性能。