|
|
3 дней назад | |
|---|---|---|
| .. | ||
| README.md | 3 дней назад | |
| main.go | 3 дней назад | |
把姓名、年龄、成绩等不同类型的数据组合成一条记录,理解上一课测试中的 []struct。
按 main.go 的 1~8 节逐段手敲。本课只学习普通结构体,不引入指针、方法、接口或匿名函数,也不新增复杂测试语法。共享根目录的 go.mod,无需第三方依赖。
type Student struct {
Name string
Age int
Score float64
}
逐项理解:
type:定义一个类型。Student:自己取的类型名,不是变量名。struct:这个类型是结构体。Name、Age、Score:字段名,后面是字段类型。定义类型只是规定“数据长什么样”,还没有创建某个具体学生。
与 C 类似,结构体用来组合不同类型的数据。Go 定义类型后,可直接写 Student,不必再加 struct Student,定义末尾也不需要 C 风格的分号。
student := Student{
Name: "小朱",
Age: 20,
Score: 90.5,
}
fmt.Println(student.Name)
student.Age = 21
Student{...}:创建一个 Student 类型的值,不是调用函数。Name: "小朱":给 Name 字段赋初始值。student.Name:通过点号访问字段。本课统一使用带字段名的初始化,避免把值的顺序写错。未列出的字段会使用零值;例如 Student{Name: "小朱"} 的年龄和成绩都是 0。
结构体字段名大小写决定跨包可见性:大写开头可从其他包访问,小写开头仅包内可访问。当前都在同一个包,先记住这条规则即可。
var empty Student
字段自动获得各自的零值:Name 是 "",Age 是 0,Score 是 0。
copied := student
copied.Name = "小李"
结构体赋值会复制结构体的值。本课字段都是 string、int、float64,修改 copied 的字段不会改变 student。
但这不是所有情况下的深拷贝:如果将来结构体里包含切片、map 或指针,复制后仍可能共享它们引用的数据。不要理解为“任何结构体赋值都会递归复制全部底层数据”。
func printStudent(student Student) {
fmt.Println(student.Name)
}
结构体也可以作为普通函数参数:变量名是 student,参数类型是 Student。按上一课的值传递规则,参数接收结构体的副本。本课函数只负责读取和打印。
students := []Student{
{Name: "小朱", Age: 21, Score: 90.5},
{Name: "小李", Age: 19, Score: 80},
}
[]Student 与 []int 一样都是切片,只是元素类型变成了 Student。内层省略 Student,是因为外层已经说明元素类型;每一对内层大括号代表一条记录。
访问第一个学生的姓名:
fmt.Println(students[0].Name)
遍历读取:
for _, item := range students {
fmt.Println(item.Name)
}
item 是当前元素的副本,给 item.Age 赋值不会直接更新原切片中的这个整数。修改原元素时使用索引:
for index := range students {
students[index].Age++
}
这里 range 只有一个接收变量,取得的是索引。代码中的成绩切片保证非空;如果改为可能为空的数据,计算平均值之前应先检查长度。
tests := []struct {
name string
a int
b int
want int
}{
{name: "正数相加", a: 2, b: 3, want: 5},
{name: "正负相加", a: -2, b: 3, want: 1},
}
拆成三层:
struct { ... }:定义一个没有单独类型名的结构体类型。[]struct { ... }:元素是这种结构体的切片。{ ... }:填入多条记录,每条记录包含名称、输入和期望结果。它不是 map,name: 是指定结构体字段。name、want、tests、tt 都是普通名称,不是 Go 关键字。
也可以先定义命名类型,再写同样的数据:
type AddCase struct {
name string
a int
b int
want int
}
tests := []AddCase{
{name: "正数相加", a: 2, b: 3, want: 5},
}
本课只循环计算并打印实际值与期望值,不用 t.Run。打印“通过=false”也不会自动让 go test 失败;正式测试还要使用 testing 包报告失败。匿名函数和 t.Run 留到后续专题。
在仓库根目录执行:
New-Item -ItemType Directory -Force bin/08_structs | Out-Null
go build -o ./bin/08_structs/structs.exe ./lessons/08_structs
.\bin\08_structs\structs.exe
修改源码后需要重新编译。快速编译并运行:
go run ./lessons/08_structs
或者从根目录进入本课:
Set-Location lessons/08_structs
go run .
=== 创建与访问字段 ===
姓名:小朱,年龄:20,成绩:90.5
修改后的年龄:21
=== 结构体零值 ===
姓名:"",年龄:0,成绩:0.0
=== 结构体赋值 ===
原姓名:小朱,副本姓名:小李
=== 结构体切片 ===
姓名:小朱,年龄:21,成绩:90.5
姓名:小李,年龄:19,成绩:80.0
姓名:小陈,年龄:22,成绩:75.5
总分:246.0,平均分:82.00
所有学生年龄加一后,第一位年龄:22
=== 理解测试数据表 ===
正数相加:实际=5,期望=5,通过=true
正负相加:实际=1,期望=1,通过=true
City string 字段,并打印城市。若另外手敲一份,放进本课 practice/ 子目录,用 go run ./lessons/08_structs/practice 运行,不要在同一个包里重复定义 Student、main 或其他同名函数。
下一课学习指针,解释 &、* 和怎样通过函数修改调用方的数据。