|
|
3 дней назад | |
|---|---|---|
| .. | ||
| README.md | 3 дней назад | |
| main.go | 3 дней назад | |
学过结构体和指针后,本课把函数关联到类型上,学习方法、值接收者、指针接收者,读懂 student.Birthday() 和测试中的 t.Fatalf(...)。
先手敲 main.go,按注释逐段理解。本课不引入接口、继承、匿名函数或复杂测试。共享根目录的 go.mod,无需第三方依赖。
普通函数把学生放在参数列表里:
func describe(student Student) string {
return student.Name
}
// 在 main 内调用:
text := describe(student)
方法在 func 与方法名之间增加一个接收者:
func (student Student) Summary() string {
return student.Name
}
// 在 main 内调用:
text := student.Summary()
逐项理解:
(student Student):接收者声明,student 是本方法内的变量名,Student 是类型。Summary:方法名。():普通参数列表,本方法没有额外参数。string:返回值类型。接收者名字不是关键字,也可以起名为 s;没有必须写 this 或 self 的要求。方法定义在包级,不写在 struct 的大括号里面,也不写在 main 内部。
方法让调用方式变成“这个学生执行某种操作”。Go 不需要先定义 class,也没有 C++ 风格的类继承。当前只需理解类型与方法的关联。
func (student Student) TryBirthday() {
student.Age++
}
student.TryBirthday() 修改的是方法内的结构体副本,原学生的 Age 不变。本课 Student 字段是 string、int、float64;如果字段包含切片或 map,值接收者仍可能修改共享底层数据,不能简单理解为“值接收者绝对无法影响外部”。
只读取这种简单结构体时,值接收者可以表达需求;如果要修改原值,使用指针接收者。
func (student *Student) Birthday() bool {
if student == nil {
return false
}
student.Age++
return true
}
*Student 是指针类型,不是此处执行解引用。方法内的 student.Age 等价于 (*student).Age。
接收者同样按值传递,只是复制了指针值,因此可以找到并修改原数据。函数和方法都没有改变 Go 的值传递规则。
student := Student{Name: "小朱", Age: 20}
student.Birthday()
student 是可取地址的变量,Go 会自动把这个调用理解为 (&student).Birthday(),不需要每次手动写 &。
但不能据此认为任何 Student 值都能自动取地址:例如 map 中的结构体值不可取地址,不能直接调用它的指针接收者方法;需先取到局部变量,修改后写回,或按需求设计成保存指针。
教学示例特意混用两种接收者作对比,不代表项目里必须混用。实际设计通常要考虑修改需求、复制成本和一致性;接口中的方法集规则留到接口课。
func (student *Student) SetScore(score float64) bool {
if student == nil || !(score >= 0 && score <= 100) {
return false
}
student.Score = score
return true
}
调用 student.SetScore(95) 会更新成绩;传入 120 返回 false,保留原成绩。
|| 会短路:student 是 nil 时,不必再判断后面的条件。范围条件写成“必须在 0~100 内”,也会拒绝特殊浮点值 NaN;现在先理解普通分数即可。
pointer := &student
text := pointer.Summary()
非 nil 的 *Student 也能调用值接收者方法,调用时会取得相应 Student 值。
nil 指针能否调用某个指针接收者方法,取决于实现是否能处理 nil。本课 Birthday 和 SetScore 主动检查,因此安全返回 false。
不能因此认为所有方法都支持 nil:对 nil 指针调用本课的值接收者 Summary,需要先取得 Student 值,会发生 panic。直接访问 nil 指针字段也不安全。
func TestAdd(t *testing.T) {
got := add(2, 3)
if got != 5 {
t.Fatalf("实际结果:%d,期望:5", got)
}
}
现在可以读懂:
t.Fatalf(...) 调用方法,传入格式字符串和数据。这里的点号不是读取普通数据字段,而是选择方法并调用。测试框架提供 t,不需要自己 new 一个 testing.T。本课示例不包含 testing 包,上述片段只用于解释上一课测试。
在仓库根目录执行:
New-Item -ItemType Directory -Force bin/10_methods | Out-Null
go build -o ./bin/10_methods/methods.exe ./lessons/10_methods
.\bin\10_methods\methods.exe
修改源码后需要重新编译。快速编译并运行:
go run ./lessons/10_methods
或从根目录进入本课:
Set-Location lessons/10_methods
go run .
=== 普通函数与方法 ===
普通函数: 小朱,年龄:20,成绩:85.0
方法调用: 小朱,年龄:20,成绩:85.0
=== 值接收者 ===
调用 TryBirthday 后,原年龄:20
=== 指针接收者 ===
调用 Birthday 后,年龄:21,成功:true
=== 方法参数与返回值 ===
设置 95 分:成功=true,当前成绩=95.0
设置 120 分:成功=false,当前成绩=95.0
=== 通过指针调用方法 ===
小朱,年龄:21,成绩:95.0
=== nil 指针接收者 ===
nil 调用 Birthday:false
nil 调用 SetScore:false
Passed() bool,根据 Score 是否大于等于 60 返回结果。若另外手敲一份,放进本课 practice/ 子目录,用 go run ./lessons/10_methods/practice 运行,不要在同一个包中重复声明 Student 或 main。
下一课学习 error 错误处理,让失败结果不只返回 false,还能说明具体原因。