Go 结构体 Struct — 把数据打包成对象
Go 没有"类"(class)这个关键字,但它有结构体(struct)——把几个相关字段打包在一起,组成一个新的类型。可以把它理解成一张"表格的字段定义":比如一个人有姓名、年龄、身高,定义一个 Person 结构体就能把它们装在一起。结构体是 Go 组织数据的核心,后面的"方法"和"接口"都建立在它之上。
一、定义结构体
用 type 名字 struct 来定义,后面跟一对花括号 { },里面列出每个字段的"名字 类型"。一行写一个字段,同类型的字段也可以合并在一行,比如 X, Y int。注意 Go 社区约定:字段名首字母大写表示对外公开(其它包能访问),小写则是私有的——这取代了 public/private 关键字。
package main
import "fmt"
// 定义结构体:把多个字段组合成一个新类型
type Person struct {
Name string
Age int
}
func main() {
// 三种常见实例化方式
p1 := Person{Name: "小明", Age: 20} // 按字段名赋值(推荐,顺序随意)
p2 := Person{"小红", 18} // 按声明顺序赋值(所有字段都要写)
p3 := new(Person) // 返回 *Person,字段先初始化为零值
p3.Name = "小刚" // 再单独赋值
fmt.Println(p1, p2, p3)
// {小明 20} {小红 18} &{小刚 0}
// 字段访问与修改:点号
p1.Age = 21
fmt.Println(p1.Name, p1.Age) // 小明 21
}实例化主要有三种方式。按字段名 Person{Name:"小明", Age:20} 最推荐,清晰且不依赖顺序;按顺序 Person{"小红", 18} 简洁但所有字段都必须写、还得和声明顺序一致,容易出错;new(Person) 返回的是指针 *Person,字段先全部置为零值,再按需赋值。
二、字段访问与值类型
访问和修改字段都用点号 p.Name、p.Age = 21。需要特别强调:和数组一样,结构体本身是值类型——把一个结构体赋值给另一个变量、或者当作参数传给函数,都会整体拷贝一份。修改副本不会影响原对象。如果你希望"传递的是引用",就用指针 *Person。
三、嵌套与组合:Go 的"继承"
Go 刻意没有 class 继承,但它用组合达到了类似效果。做法很优雅:在一个结构体里,把另一个结构体作为匿名字段嵌进来(只写类型名、不写字段名)。这时内嵌结构体的字段会被自动"提升"——你可以像访问外层自己的字段一样,直接 u.City,而不用写 u.Address.City。这就是 Go 社区常说的"组合优于继承"。
package main
import "fmt"
// 内嵌基础结构
type Address struct {
City string
Street string
}
// 用"匿名字段"把 Address 嵌进来,实现组合
type User struct {
Name string
Address // 只写类型名,不写字段名
}
func main() {
u := User{
Name: "小明",
Address: Address{City: "北京", Street: "长安街"},
}
// 内嵌字段会被"提升":可以像自己的一样直接访问
fmt.Println(u.City) // 北京(等价于 u.Address.City)
fmt.Println(u.Address.City) // 北京
// 修改也一样
u.Street = "建国路"
fmt.Println(u.Address.Street) // 建国路
}组合的强大之处在于可以多层嵌套,内嵌结构体的方法也会被提升(下一篇讲方法时会看到,内嵌字段的方法能直接在外层结构体上调用)。这让 Go 在没有继承体系的情况下,依然能优雅地复用代码。除了结构体,基础类型(如 int、string)也能当匿名字段嵌入,可以用它给基础类型"起别名"并挂自定义方法。
四、结构体标签 tag
每个字段后面可以跟一个用反引号包裹的字符串,叫"标签"(tag)。标签本身不影响代码逻辑,但能被反射(reflect)读取,最经典的用途是控制 JSON 序列化:指定字段在 JSON 里的名字、是否在零值时省略、是否完全隐藏。
package main
import (
"encoding/json"
"fmt"
)
// 结构体标签:反引号包裹的元信息,常用于 JSON 序列化
type Product struct {
Name string `json:"name"` // 序列化时键变成小写 name
Price int `json:"price,omitempty"` // omitempty:零值则省略
Code string `json:"-"` // 永远不输出
}
func main() {
p := Product{Name: "手机", Price: 4999, Code: "X123"}
b, _ := json.Marshal(p)
fmt.Println(string(b))
// 输出:{"name":"手机","price":4999}
// 匿名结构体:临时用,不必先 type 定义
point := struct {
X, Y int
}{X: 3, Y: 4}
fmt.Println(point) // {3 4}
}看 json:"name" 把字段在输出 JSON 里改名为小写;omitempty 表示如果是零值就别出现在 JSON 里;json:"-" 则是"绝不输出",常用于密码、内部 ID 等敏感字段。除了 json,数据库 ORM(如 GORM)、表单校验(如 validator)也都用自己的 tag,套路一致。
五、匿名结构体
如果某个结构体只用一次,没必要预先 type 定义,可以就地写一个匿名结构体——直接 struct { X, Y int } 作为类型,后面紧跟花括号赋值。上面代码末尾的 point 就是这种用法,适合临时组装一点数据,比如解析一段只在此处用的 JSON。
六、值类型与零值
结构体是值类型,意味着:声明 var p Person 后即使没赋值,每个字段也已经有对应类型的零值——字符串是空串、数字是 0、指针是 nil。Go 没有"构造函数"的概念,但社区约定俗成写一个 NewPerson(...) 工厂函数返回初始化好的实例,既清楚又避免零值带来的意外。
小结
- 结构体把多个字段组合成新类型,是 Go 组织数据的核心。
- 首字母大小写决定字段是否对外公开,取代 public/private。
- 匿名嵌入实现组合,内嵌字段会被提升,达到类似继承的效果。
- 结构体标签用反引号,配合反射控制 JSON、数据库等行为。
结构体本身只是"数据",下一篇 Go 方法 Method 给它装上"行为"。上一篇 Go Map 映射。