结构体定义
本教程共 80 篇 · 第 37 篇 · 更新于 2026-07-27 · 约 7 分钟阅读
37. 结构体定义
本节目标:学会用 struct 定义自己的数据类型,理解字段、标签、匿名字段的概念。
什么是结构体
结构体(Struct)就是把多个不同类型的变量打包在一起,形成一个新的类型。比如一个「人」有名字、年龄、身高,你就可以用结构体把它们组织起来。
type Person struct {
Name string
Age int
}
这样你就定义了一个 Person 类型,它有两个字段:Name 是字符串,Age 是整数。
定义和使用
基本用法
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 创建结构体
p := Person{
Name: "Alice",
Age: 30,
}
fmt.Println(p) // {Alice 30}
fmt.Println(p.Name) // Alice 用 . 访问字段
fmt.Println(p.Age) // 30
// 修改字段
p.Age = 31
fmt.Println(p.Age) // 31
}
用 . 来访问和修改字段,和大多数语言一样。
省略字段名
如果你不写字段名,按顺序赋值也行:
p := Person{"Alice", 30}
Warning不推荐这种写法。一旦结构体加了新字段,顺序就全乱了。养成用字段名的习惯,代码更安全。
结构体是值类型
和数组一样,结构体是值类型。赋值时会拷贝一份完整的数据:
p1 := Person{"Alice", 30}
p2 := p1
p2.Name = "Bob"
fmt.Println(p1.Name) // Alice p1 没变
fmt.Println(p2.Name) // Bob 只有 p2 变了
如果你想让两个变量共享同一份数据,用指针:
p1 := &Person{"Alice", 30}
p2 := p1
p2.Name = "Bob"
fmt.Println(p1.Name) // Bob p1 也变了,因为指向同一个
字段标签
结构体字段可以加标签(tag),这是一段附加的元信息,用反引号包裹:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
标签最常见的用途是 JSON 序列化。上面的标签告诉 encoding/json 包:序列化时把 Name 输出为 "name",Email 为空时省略。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
func main() {
u := User{Name: "Alice", Age: 30}
data, _ := json.Marshal(u)
fmt.Println(string(data))
// {"name":"Alice","age":30} email为空被省略了
u.Email = "alice@example.com"
data, _ = json.Marshal(u)
fmt.Println(string(data))
// {"name":"Alice","age":30,"email":"alice@example.com"}
}
Note标签的格式是
key:"value",可以写多组,用空格分隔,比如`json:"name" xml:"name" bson:"name"`。
常见的标签类型:
| 标签前缀 | 用途 |
|---|---|
json | JSON 序列化/反序列化 |
xml | XML 编解码 |
db | 数据库映射 |
yaml | YAML 配置文件 |
validate | 数据校验 |
匿名字段(嵌入类型)
结构体字段可以只有类型名,不写字段名,这叫匿名字段:
type Animal struct {
Name string
}
type Dog struct {
Animal // 匿名字段,嵌入了 Animal 类型
Breed string
}
嵌入 Animal 后,Dog 可以直接访问 Animal 的字段,这叫「字段提升」:
package main
import "fmt"
type Animal struct {
Name string
}
type Dog struct {
Animal
Breed string
}
func main() {
d := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Labrador",
}
// 直接访问提升的字段
fmt.Println(d.Name) // Rex 等价于 d.Animal.Name
fmt.Println(d.Breed) // Labrador
// 也可以通过完整路径访问
fmt.Println(d.Animal.Name) // Rex
}
匿名字段实现了 Go 的「组合优于继承」理念。后面的章节会详细讲嵌入和方法提升。
Tip匿名字段不限于结构体,也可以嵌入基础类型。比如
type MyType struct { int },这样MyType就有一个匿名的int字段。
结构体比较
如果结构体的所有字段都是可比较的,结构体本身也可以用 == 比较:
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{1, 3}
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false
但如果字段里有切片、map 等不可比较的类型,就不能用 == 了,编译会报错。
小结
结构体是 Go 组织数据的核心方式:
- 用
type Name struct { ... }定义 - 字段标签给序列化框架提供元信息
- 匿名字段实现类型嵌入和字段提升
- 结构体是值类型,赋值会拷贝
下一章讲结构体的初始化方式和匿名嵌入的深入用法。