编程后端开发者

在Go中,按引用传递和按值传递有什么区别?为什么这一点对结构体和切片至关重要?

用 Hintsage AI 助手通过面试

答案

在Go中,默认情况下,所有函数参数都是按值传递的:复制变量的值。但是某些类型(例如切片、地图、通道)是对内部结构(指针)的“包装”。按值传递切片仅复制切片描述符,而不是数据;两个变量引用同一个数组。在结构体的情况下——整个结构体被复制。

如果需要避免复制并与原始结构体进行交互,则使用指针传递 (*Struct)。

示例:

type User struct { Name string Age int } func updateUser(u User) { u.Age = 30 // 只会改变副本 } func updateUserPtr(u *User) { u.Age = 30 // 会改变原始值 } func main() { u := User{"Ivan", 25} updateUser(u) fmt.Println(u.Age) // 25 updateUserPtr(&u) fmt.Println(u.Age) // 30 }

隐晦的问题

传递给函数的切片中的更改是否总是可以在函数外部看到?

不!

  • 如果切片的内容被修改 (slice[i] = ...),则在外部是可见的。
  • 如果切片本身被修改(例如,slice = append(slice, ...)),而结果没有从函数中返回——新元素将出现在局部副本中,你将失去它们。

示例:

func addElem(s []int) { s = append(s, 100) } func main() { arr := []int{1,2,3} addElem(arr) fmt.Println(arr) // [1 2 3] — 100没有添加 }

由于对主题细节的无知而导致的实际错误示例


故事

在一个项目中,具有大结构字段(200+字节)的数据结构通过通道按值传递给goroutines,这导致了巨大的复制开销和性能损失。在切换到按指针传递后,延迟减少了一个数量级。


故事

在审计日志服务中,开发人员在函数之间传递地图(map),而没有显式克隆(copy)。一个函数中的更改意外地更改了程序另一部分的数据,导致日志混乱。


故事

在动态增加切片的函数中,忘记将新的切片返回。结果,修改没有反映在调用代码中,导致部分事务丢失。决定从函数中返回新的切片。