深入理解Golang中结构体指针的函数调用

深入理解Golang中结构体指针的函数调用 我记得几年前在C或C++中理解过这个问题(或类似问题)。 如果可能的话,我希望通过内存地址等实例获得一些实用的解释。

我指的是 https://play.golang.org/p/J_4aG29-jDa

如果u位于内存地址1000,v位于2000,y位于3000,那么在*Modify{1,2,3}*函数内部会发生什么?

5 回复

你将一个新的 &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代码中,函数 Modify1Modify2Modify3 分别接收结构体指针。当调用这些函数时,会发生以下情况:

  1. Modify1(u):传递 u 的地址(1000)。函数内部通过指针直接修改 u 的字段。例如,执行 u.Name = "Jerry" 会改变地址 1000 处的 Name 字段。
  2. Modify2(&v):传递 v 的地址(2000)。函数内部修改 v 的字段,例如 v.Age = 40 更新地址 2000 处的 Age 值。
  3. 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)
}

运行此代码,输出将显示指针地址和内存变化:

  • Modify1Modify2 会修改原始结构体,因为操作的是同一内存地址。
  • Modify3 中,指针 y 在函数内部被重新指向新地址,但外部的 y 保持不变,因为函数接收的是指针的副本。

关键点:

  • 在Go中,所有函数参数都是按值传递,包括指针。传递指针时,函数获得指针的副本,但副本指向相同内存地址。
  • 通过指针修改字段会影响原始数据,但重新分配指针(如 Modify3)只影响函数内的副本。
  • 使用 %p 格式化符号可以打印地址,帮助理解内存布局。
回到顶部