深入理解Golang中结构体指针的函数调用
深入理解Golang中结构体指针的函数调用 我记得几年前在C或C++中理解过这个问题(或类似问题)。 如果可能的话,我希望通过内存地址等实例获得一些实用的解释。
我指的是 https://play.golang.org/p/J_4aG29-jDa
如果u位于内存地址1000,v位于2000,y位于3000,那么在*Modify{1,2,3}*函数内部会发生什么?
你将一个新的 &User 指针赋值给 u。这并不会改变原先由 u 引用的 User。
更多关于深入理解Golang中结构体指针的函数调用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
好的,我明白了。
我覆盖了 u。
谢谢你 @lutzhorn
我能够理解Modify2。
至于Modify1,u作为局部变量没问题。它的作用域是局部的,但它是指向*User还是指向它的副本呢?
Modify1 函数不会修改传入的 User。参数 u 在函数内部是局部变量。因此将新的 User 指针赋值给它只会产生局部效果。
func Modify1(u *User) {
u = &User{Name: "Paul"}
fmt.Println("\t", u.Name)
}
Modify2 的情况也是如此。由于 User 是通过值传递的,在局部修改它不会对函数外部产生任何影响。
func Modify2(u User) {
u.Name = "Duncan"
}
如果想要修改传入的 User 使其在函数外部生效,需要解引用传入的指针并修改所引用的 User。
package main
import "fmt"
type User struct {
Name string
}
func main() {
z := &User{Name: "Leto"}
fmt.Println(z.Name)
Modify4(z)
fmt.Println(z.Name)
}
func Modify4(u *User) {
u.Name = "Bob"
}
输出:
Leto
Bob
https://play.golang.org/p/dEzmV6xmw9P
也可以参考 Go 语言之旅:https://tour.golang.org/methods/5
在Go语言中,当结构体指针作为函数参数传递时,函数内部操作的是原始结构体的内存地址,而不是副本。这允许直接修改原始数据。以下通过内存地址示例详细解释:
假设内存布局如下:
- 变量
u位于地址 1000,存储结构体User{Name: "Tom", Age: 30} - 变量
v位于地址 2000,存储结构体User{Name: "Bob", Age: 25} - 变量
y位于地址 3000,存储结构体User{Name: "Alice", Age: 28}
在提供的Playground代码中,函数 Modify1、Modify2 和 Modify3 分别接收结构体指针。当调用这些函数时,会发生以下情况:
- Modify1(u):传递
u的地址(1000)。函数内部通过指针直接修改u的字段。例如,执行u.Name = "Jerry"会改变地址 1000 处的Name字段。 - Modify2(&v):传递
v的地址(2000)。函数内部修改v的字段,例如v.Age = 40更新地址 2000 处的Age值。 - Modify3(&y):传递
y的地址(3000)。函数内部重新分配指针到新结构体(例如地址 4000),但这不影响原始变量y,因为函数内修改的是指针副本的指向。
以下是示例代码,展示内存地址操作:
package main
import (
"fmt"
"unsafe"
)
type User struct {
Name string
Age int
}
// Modify1 通过指针修改结构体字段
func Modify1(u *User) {
fmt.Printf("Modify1: u 指针地址 = %p, 指向的内存地址 = %p\n", &u, u)
u.Name = "Jerry" // 直接修改原始结构体的字段
u.Age = 35
}
// Modify2 类似 Modify1,但演示不同变量
func Modify2(v *User) {
fmt.Printf("Modify2: v 指针地址 = %p, 指向的内存地址 = %p\n", &v, v)
v.Age = 40 // 修改原始结构体的 Age
}
// Modify3 在函数内重新分配指针,不影响外部
func Modify3(y *User) {
fmt.Printf("Modify3: y 指针地址 = %p, 指向的内存地址 = %p\n", &y, y)
newUser := &User{Name: "Charlie", Age: 50}
y = newUser // 只修改函数内部的指针副本,外部 y 不变
fmt.Printf("Modify3: 修改后 y 指针地址 = %p, 指向的新地址 = %p\n", &y, y)
}
func main() {
u := User{Name: "Tom", Age: 30}
v := User{Name: "Bob", Age: 25}
y := User{Name: "Alice", Age: 28}
// 打印原始结构体的内存地址
fmt.Printf("u 变量地址 = %p, v 变量地址 = %p, y 变量地址 = %p\n", &u, &v, &y)
// 调用 Modify1,传递 u 的地址
Modify1(&u)
fmt.Printf("After Modify1: u = %+v\n", u)
// 调用 Modify2,传递 v 的地址
Modify2(&v)
fmt.Printf("After Modify2: v = %+v\n", v)
// 调用 Modify3,传递 y 的地址
Modify3(&y)
fmt.Printf("After Modify3: y = %+v (未改变,因为函数内重新分配了指针)\n", y)
}
运行此代码,输出将显示指针地址和内存变化:
Modify1和Modify2会修改原始结构体,因为操作的是同一内存地址。Modify3中,指针y在函数内部被重新指向新地址,但外部的y保持不变,因为函数接收的是指针的副本。
关键点:
- 在Go中,所有函数参数都是按值传递,包括指针。传递指针时,函数获得指针的副本,但副本指向相同内存地址。
- 通过指针修改字段会影响原始数据,但重新分配指针(如
Modify3)只影响函数内的副本。 - 使用
%p格式化符号可以打印地址,帮助理解内存布局。

