
在 go 中,只有可寻址的变量才能作为赋值语句的左值;函数返回非指针结构体时产生临时值(不可寻址),无法直接对其字段赋值;而返回指针则提供对底层变量的可寻址引用。
在 go 中,只有可寻址的变量才能作为赋值语句的左值;函数返回非指针结构体时产生临时值(不可寻址),无法直接对其字段赋值;而返回指针则提供对底层变量的可寻址引用。
Go 的赋值操作要求左操作数(LHS)必须是一个可寻址的变量(addressable value),即该值必须有明确的内存地址,能被取址(&x 合法)。这是语言规范的硬性约束,目的是确保赋值具有实际可观测的副作用——否则修改一个转瞬即逝的临时对象毫无意义。
考虑如下简化示例:
package main
type Employee struct {
ID int
Name string
Salary float64
}
func EmployeeByID(id int) *Employee {
// 返回指向某个真实 Employee 实例的指针(例如从 map 或数据库查得)
return &Employee{ID: id, Salary: 50000}
}
func EmployeeByIDValue(id int) Employee {
// 返回一个全新的、匿名的结构体值(临时对象)
return Employee{ID: id, Salary: 50000}
}
func main() {
// ✅ 合法:*Employee 是可寻址的(指针解引用后是变量)
EmployeeByID(123).Salary = 0 // 修改的是底层 Employee 实例的字段
// ❌ 编译错误:EmployeeByIDValue(123) 是一个临时值(rvalue),不可寻址
// EmployeeByIDValue(123).Salary = 0 // error: cannot assign to EmployeeByIDValue(123).Salary
// ✅ 合法的替代写法:先绑定到局部变量(使其可寻址)
e := EmployeeByIDValue(123)
e.Salary = 0 // 修改的是变量 e 的副本,不影响原数据
}关键在于 Go 对“可寻址性”的定义:
- &T{}、&x、x.field(当 x 是可寻址的结构体变量时)、slice[i] 等表达式产生可寻址值;
- 字面量(如 T{})、函数调用返回的非指针值(如 f() 返回 T)、类型转换结果(如 T(x))等属于不可寻址的临时值(rvalue),不能出现在赋值左侧。
因此,EmployeeByID(id).salary = 0 能通过编译,是因为 EmployeeByID(id) 返回 *Employee,其解引用 (*Employee).salary 实际等价于 (*ptr).salary —— 这里 ptr 指向一个真实存在的、可寻址的 Employee 实例(可能是全局变量、堆上对象或闭包捕获的变量)。而若改为返回 Employee 值类型,EmployeeByIDValue(id).salary 就是在尝试给一个仅存在于当前表达式生命周期内的临时副本赋值,Go 编译器会拒绝该操作以防止逻辑错误和资源浪费。
? 注意事项:
- 此规则不仅适用于结构体字段,也适用于数组元素(arr[i] 可寻址,但 f()[i] 不可,除非 f() 返回指针);
- 使用指针接收者方法时同理:(*T).Method() 可修改 receiver,而 (T).Method() 只能修改副本;
- 若需修改返回的结构体值,务必先将其赋给局部变量(e := f(); e.Field = ...),但注意这修改的是副本,不会影响原始数据源。
总之,理解 Go 中“可寻址性”是掌握结构体赋值、方法集和内存模型的核心前提——它不是语法糖,而是保障程序语义清晰与内存安全的语言基石。


















