Go 结构体与指针接收者:修改任务而不是副本
为任务定义结构体,并用方法修改状态。通过原值与副本的对比理解为什么修改类操作常使用指针接收者。
本节目标
为任务定义结构体,并用方法修改状态。通过原值与副本的对比理解为什么修改类操作常使用指针接收者。
前置知识
了解函数和变量赋值。看到*Task时先理解为“指向Task的指针”,不需要涉及地址计算。
准备与运行
在一个新的练习目录执行以下命令。目录名可以修改;若该目录已经初始化过模块,不要重复执行go mod init。
mkdir go-lesson-06
cd go-lesson-06
go mod init example.com/devcn/lesson06将下方完整代码保存为当前目录的main.go,然后执行:
go fmt ./...
go run .先用go version确认工具链可用。本例在Go 1.25.7、macOS ARM64环境验证,只使用标准库;没有测试所有操作系统和Go版本。
核心概念
结构体把相关字段组织成一个值。Task包含名称和完成状态,零值情况下Name为空字符串、Done为false。创建任务时只填写Name,Done就保持false。
方法与函数相似,但在方法名之前多了接收者。func (t *Task) Complete()表示这个方法使用Task指针,因此可以修改调用方持有的任务。如果改为值接收者,方法内修改的是接收者副本,调用方的Done不会因此改变。
代码中original是可取地址的变量,所以可以直接调用original.Complete(),编译器会按方法需要处理取地址。学习时要把“方便的调用写法”和“接收者实际上是指针”分清。
完整示例
package main
import "fmt"
type Task struct {
Name string
Done bool
}
func (t *Task) Complete() { t.Done = true }
func main() {
original := Task{Name: "读文档"}
copied := original
original.Complete()
fmt.Println(original.Done, copied.Done)
}运行后应得到:
true false代码如何工作
先创建original,再执行copied := original,此时两个结构体的字段值相同。随后只调用original.Complete,方法把original的Done设为true。
最终输出true和false,说明copied没有跟着改变。这个小实验比只记“Go是值传递”更直观:要分析一次修改影响谁,需要看复制的值里是否包含指针、切片或Map等引用关系。
方法名首字母大写,意味着可以被其他包访问。本节所有代码在同一包中,大小写不会改变示例运行结果,但组织库代码时需要考虑导出范围。
常见错误与边界
- 不能因为结构体赋值产生副本,就认定其中所有关联对象也被复制。
- 对nil指针调用会解引用它的方法可能panic;本例传入的是已创建的任务。
- 修改状态的方法与只读方法应按用途设计接收者,避免仅凭代码长度选择。
- Go没有传统类继承语法。后续可学习嵌入与接口,但不要把字段嵌入理解成完整的类继承。
练习
增加Rename方法,把original的名称改为“完成接口练习”,同时确认copied的名称仍为“读文档”。
参考答案
在Task方法区域加入:
func (t *Task) Rename(name string) {
t.Name = name
}在main中调用original.Rename("完成接口练习")后打印两份名称,预期分别为新名称和“读文档”。使用指针接收者,是因为这个方法的目的就是更新调用方持有的任务。
文章回复
0 条公开回复