在Golang编程中,当我们把结构体作为函数参数或者方法接收者时,有两种常见的传递方式,一种是传递结构体的值副本,另一种是传递结构体的指针。不同的传递方式会对内存使用产生明显的影响,理解其中的原理可以帮助我们写出更高效的代码。

结构体值传递与指针传递的本质区别
Golang中所有函数参数的传递默认都是值传递,也就是说传递结构体值时,会把整个结构体的内容复制一份传给函数。如果结构体体积较大,这个拷贝过程会占用额外的内存,还会消耗CPU资源。而传递结构体指针时,只复制一个内存地址,这个地址的大小通常是固定的8字节(64位系统),无论结构体本身有多大,传递的开销都很小。
我们可以通过一个简单的示例来观察两种传递方式的内存差异,首先定义一个体积较大的结构体:
package main
import (
"fmt"
"unsafe"
)
// 定义一个包含多个字段的大结构体
type BigStruct struct {
Field1 [1024]byte // 1024字节的数组
Field2 int64
Field3 string
Field4 [512]byte // 512字节的数组
}
func main() {
var s BigStruct
// 输出结构体本身的大小
fmt.Println("结构体大小:", unsafe.Sizeof(s))
// 输出结构体指针的大小
fmt.Println("结构体指针大小:", unsafe.Sizeof(&s))
}
运行上述代码可以看到,BigStruct本身的大小远超过指针的大小,当传递这个结构体的值时,每次调用函数都会拷贝整个结构体的内容,而传递指针只需要拷贝一个地址。
传递结构体指针提升内存效率的场景
1. 结构体体积较大时
当结构体的字段较多,或者包含数组、切片等占用空间较大的元素时,传递指针可以显著减少内存拷贝的开销。比如一个存储用户完整信息的结构体,包含昵称、简介、头像地址等多个字段,传递指针比传递值更节省内存。
2. 需要修改结构体内容时
如果我们需要在函数内部修改结构体的字段值,并且希望修改能反映到原结构体上,就必须传递指针。值传递的情况下,函数内修改的是副本的内容,不会影响原来的结构体。
下面是一个修改结构体字段的示例:
package main
import "fmt"
type User struct {
Name string
Age int
}
// 值传递,修改不会影响原结构体
func updateUserByValue(u User) {
u.Age = 20
u.Name = "李四"
}
// 指针传递,修改会影响原结构体
func updateUserByPointer(u *User) {
u.Age = 20
u.Name = "李四"
}
func main() {
user := User{Name: "张三", Age: 18}
fmt.Println("修改前:", user)
updateUserByValue(user)
fmt.Println("值传递修改后:", user)
updateUserByPointer(&user)
fmt.Println("指针传递修改后:", user)
}
3. 结构体作为方法接收者时
给结构体定义方法时,如果方法需要修改结构体的状态,或者结构体较大,应该把接收者定义为指针类型。如果方法只是读取结构体的内容,且结构体较小,也可以用值接收者。
方法接收者的使用示例:
package main
import "fmt"
type Rectangle struct {
Width float64
Height float64
}
// 值接收者方法,只读取结构体内容,不修改
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 指针接收者方法,修改结构体的字段
func (r *Rectangle) Scale(factor float64) {
r.Width = r.Width * factor
r.Height = r.Height * factor
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Println("原始面积:", rect.Area())
rect.Scale(2)
fmt.Println("缩放后面积:", rect.Area())
}
传递结构体指针的注意事项
- 指针传递虽然节省内存,但需要注意并发安全的问题。如果多个 goroutine 同时修改同一个指针指向的结构体,可能会引发数据竞争,需要加锁保护。
- 不要返回局部结构体的指针,因为局部结构体在函数执行结束后会被销毁,返回的指针会变成野指针,访问时会引发未知错误。
- 对于体积很小的结构体,比如只有两三个基本类型字段的结构体,传递值的开销和传递指针的开销差异很小,此时可以根据是否需要修改结构体来选择传递方式,不需要刻意使用指针。
两种传递方式的适用场景对比
我们可以通过下面的表格快速判断什么时候该用值传递,什么时候该用指针传递:
| 场景 | 推荐传递方式 | 原因 |
|---|---|---|
| 结构体体积较大 | 指针传递 | 减少内存拷贝,降低内存占用 |
| 需要修改原结构体内容 | 指针传递 | 值传递的修改只作用于副本,不影响原结构体 |
| 结构体体积很小(如2-3个基本字段) | 值传递 | 拷贝开销可忽略,且避免指针带来的额外复杂度 |
| 不需要修改结构体,且结构体较小 | 值传递 | 简单直接,无需考虑指针的并发安全问题 |
在实际开发中,我们可以根据结构体的大小、是否需要修改内容、是否有并发访问的需求来选择合适的传递方式,合理地使用结构体指针传递,能够有效提升程序的内存使用效率,减少不必要的性能损耗。