DevCN
菜单
全部文章快讯开发深度热点

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("完成接口练习")后打印两份名称,预期分别为新名称和“读文档”。使用指针接收者,是因为这个方法的目的就是更新调用方持有的任务。

DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。