Go 结构体指针与返回值类型 知识点汇总
1. 函数返回指针 *T vs 返回值 T
🔹 返回指针 *Employee — 可以直接修改原始数据
1 2 3 4 5 6 7
| func EmployeeByID(id int) *Employee { }
fmt.Println(EmployeeByID(id).Position)
EmployeeByID(id).Salary = 0
|
内存模型:
1 2
| EmployeeByID(id) → 指针 → 内存中的 Employee ↑ 修改 Salary,改的是它
|
💡 返回指针时,你拿到的是原始数据的地址,通过指针修改就是直接改原始数据。
🔹 返回值 Employee — 不能直接修改
1 2 3 4 5 6 7
| func EmployeeByID(id int) Employee { }
fmt.Println(EmployeeByID(id).Position)
EmployeeByID(id).Salary = 0
|
原因分析:
1 2
| EmployeeByID(id) → 临时副本(不可寻址) ↑ 赋值毫无意义,编译器直接报错
|
⚠️ 函数返回的值类型是一个临时副本,不是变量,不可寻址,Go 禁止对它赋值。
🔹 用变量接收返回值 — 可以修改,但改的是副本
1 2 3 4 5
| func EmployeeByID(id int) Employee { }
e := EmployeeByID(id) e.Salary = 0
|
🔹 对比总结
| 场景 |
能否编译 |
修改的是谁? |
返回 *Employee,直接 .Salary = 0 |
✅ |
原始数据 |
返回 Employee,直接 .Salary = 0 |
❌ 编译错误 |
— |
返回 Employee,用变量接收后修改 |
✅ |
副本,原始数据不变 |
返回 *Employee,用变量接收后修改 |
✅ |
原始数据 |
🔹 形象类比
1 2 3 4 5
| 🎯 返回指针 → 给你一张纸条:“员工档案在 3 号柜子“ → 你去柜子里改档案 ✅
🎯 返回值 → 给你一份档案的复印件 → 你在复印件上改,原件不变 ❌
|
2. 赋值语句左边必须是可寻址的变量
🔹 核心原则
赋值语句左边必须是一个可寻址的变量(addressable)。函数直接返回的值类型是临时的、不可寻址的。
🔹 代码示例
1 2 3 4 5 6 7 8 9 10
| var e Employee e.Salary = 100
p := &e p.Salary = 200
getEmployee().Salary = 300
|
🔹 什么是“可寻址“?
| 表达式 |
是否可寻址 |
说明 |
var e Employee |
✅ |
变量本身有内存地址 |
p := &e |
✅ |
指针指向真实变量 |
e.Field |
✅ |
结构体字段可寻址(当 e 可寻址时) |
p.Field |
✅ |
指针字段可寻址(自动解引用) |
getEmployee() |
❌ |
函数返回值是临时副本 |
getEmployee().Field |
❌ |
临时副本的字段也不可寻址 |
map[key] |
❌ |
map 元素不可寻址(特殊限制) |
3. 指针访问成员的语法糖
🔹 Go 中 p.Salary 和 (*p).Salary 完全等价
1 2 3 4
| p := &e
(*p).Salary = 200 p.Salary = 200
|
💡 编译器看到 p 是指针类型,会自动将 p.Salary 翻译成 (*p).Salary。
🔹 与 C 语言的对比
▎ Go 追求简洁,不区分 . 和 ->,全部用 .,编译器自动处理。实际开发中没有人写 (*p).Salary。
🔑 关键要点速记
| 要点 |
说明 |
| 返回指针 |
可修改原始数据,适合大结构体或需要修改的场景 |
| 返回值 |
返回副本,修改不影响原始数据,适合小结构体或只读场景 |
| 不可寻址 |
函数返回的值类型是临时副本,不能直接赋值 |
| 变量接收 |
用变量接收返回值后可修改,但改的是副本 |
| 语法糖 |
p.Field 等价于 (*p).Field,推荐用简写 |
| 统一运算符 |
Go 中指针和值都用 . 访问字段,无需区分 -> |
🎯 实践建议
✅ 什么时候返回指针?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func UpdateSalary(id int, newSalary int) { e := EmployeeByID(id) e.Salary = newSalary }
type LargeStruct struct { data [1024]byte } func GetLarge() *LargeStruct { }
func FindUser(id int) *User { if notFound { return nil } return &user }
|
✅ 什么时候返回值?
1 2 3 4 5 6 7 8 9 10 11 12
| type Point struct{ X, Y int } func Origin() Point { return Point{0, 0} }
func WithName(e Employee, name string) Employee { e.Name = name return e }
|
📌 一句话总结:理解“可寻址“是掌握 Go 指针赋值的关键;返回指针改原件,返回值改副本;指针访问字段用 . 简写,编译器会自动解引用。