问题历史:
Go被设计成一个具有明显值语义的语言:几乎所有在传递时都按值复制,包括结构体(struct),但指针和切片(slice)除外。这简化了推理并提高了安全性,但也带来了许多"陷阱"。
问题:
开发人员常常期待在函数中传递结构时,所做的修改可以被"外部"看到。但实际上是复制了整个内容(包括嵌套字段——按值!)。对于切片和map——有不同的行为,复制的是"容器",而不是"内容"。
解决方案:
如果期望修改,应该通过指针传递大型结构。对于切片,只复制描述符(长度、容量、指针),而不复制内容——原始切片的修改(通过索引)将在外部可见。对于结构体——复制所有内容:
type Point struct { X, Y int } func move(p Point) { p.X = 100 } func movePtr(p *Point) { p.X = 100 } func demo() { pt := Point{10, 10} move(pt) fmt.Println(pt.X) // 10 movePtr(&pt) fmt.Println(pt.X) // 100 }
关键特点:
如果在函数内修改结构,原始值会改变吗?
不会,如果结构是按值传递——修改是局部的。
type User struct {Name string} func f(u User) {u.Name = "Ann"}
如果在函数内修改切片的元素,原始值会改变吗?
会。切片是对共享数组的"视图"。改变元素,同时改变原始数据。
func f(s []int) {s[0] = 99}
如果返回一个在函数内创建的切片,会发生什么?
切片的"头部"被复制,但基础数组仍然可用。如果不在外部保存引用,数据可能被GC回收。
在处理User结构时,函数按值处理——修改未返回到原始结构,难以捕捉到bug。
优点:
缺点:
大型结构明确按指针传递,而对切片的行为始终进行注释或检查。没有混淆,所有人都期待值语义。
优点:
缺点: