Go 语言结构体
目录
结构体(struct)是 Go 语言中自定义复合类型的核心机制,用于将多个不同类型的字段组合成一个整体。Go 没有类(class),但通过结构体和方法(method)的组合,可以实现面向对象编程的大部分特性。
结构体定义
使用 type ... struct 语法定义结构体。
package main
import "fmt"
// 定义一个 Person 结构体
type Person struct {
Name string
Age int
Email string
Address string
}
func main() {
// 声明结构体变量,字段为零值
var p1 Person
fmt.Println(p1) // { 0 }
// 使用字面量初始化(按顺序赋值)
p2 := Person{"张三", 25, "zhangsan@example.com", "北京"}
fmt.Println(p2)
// 使用字段名初始化(推荐,顺序无关)
p3 := Person{
Name: "李四",
Age: 30,
Email: "lisi@example.com",
Address: "上海",
}
fmt.Println(p3)
// 部分字段初始化,未指定的字段为零值
p4 := Person{Name: "王五"}
fmt.Println(p4) // {王五 0 }
}字段初始化
使用 new 创建
new 返回结构体的指针,所有字段为零值。
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := new(Person)
fmt.Printf("类型: %T\n", p) // *main.Person
fmt.Println(p) // &{ 0}
// 通过指针访问字段(Go 自动解引用)
p.Name = "张三"
p.Age = 25
fmt.Println(*p) // {张三 25}
}使用取地址符 &
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 创建结构体并获取指针
p := &Person{Name: "李四", Age: 30}
fmt.Printf("类型: %T\n", p) // *main.Person
fmt.Println(p.Name) // 李四
}结构体方法
结构体可以定义方法,方法接收者(receiver)可以是值类型或指针类型。
package main
import "fmt"
type Rectangle struct {
Width float64
Height float64
}
// 值接收者方法:不会修改原始结构体
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 指针接收者方法:可以修改原始结构体
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Printf("面积: %.2f\n", rect.Area()) // 面积: 50.00
rect.Scale(2)
fmt.Printf("缩放后: %.2f x %.2f\n", rect.Width, rect.Height) // 20.00 x 10.00
fmt.Printf("新面积: %.2f\n", rect.Area()) // 新面积: 200.00
// 指针也可以调用值接收者方法
pRect := &Rectangle{Width: 3, Height: 4}
fmt.Printf("面积: %.2f\n", pRect.Area()) // 面积: 12.00
}值接收者 vs 指针接收者的选择:
- 需要修改结构体字段时使用指针接收者
- 结构体较大时使用指针接收者(避免拷贝)
- 否则使用值接收者
匿名字段(嵌入)
Go 支持匿名字段,实现类似"继承"的效果。
package main
import "fmt"
// 基础结构体
type Animal struct {
Name string
Age int
}
// 方法
func (a Animal) Speak() string {
return "..."
}
// 嵌入 Animal
type Dog struct {
Animal // 匿名字段
Breed string // 具名字段
}
func main() {
dog := Dog{
Animal: Animal{Name: "旺财", Age: 3},
Breed: "金毛",
}
// 可以直接访问嵌入结构体的字段
fmt.Println(dog.Name) // 旺财
fmt.Println(dog.Age) // 3
fmt.Println(dog.Breed) // 金毛
// 也可以显式通过类型名访问
fmt.Println(dog.Animal.Name) // 旺财
// 调用嵌入结构体的方法
fmt.Println(dog.Speak()) // ...
}字段标签(Struct Tag)
结构体字段可以附带标签(tag),标签在运行时通过反射获取,常用于 JSON 序列化、ORM 映射等。
package main
import (
"fmt"
"reflect"
)
type User struct {
ID int `json:"id" db:"user_id"`
Username string `json:"username" db:"user_name"`
Email string `json:"email,omitempty"`
Password string `json:"-"` // 序列化时忽略此字段
}
func main() {
u := User{ID: 1, Username: "admin", Email: "admin@example.com", Password: "secret"}
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段: %-10s json: %s\n", field.Name, field.Tag.Get("json"))
}
}标签不会在运行时影响程序逻辑,它只是一种元数据,供外部库(如 encoding/json、gorm)使用。
结构体比较
结构体可以使用 == 比较,前提是所有字段都是可比较类型。
package main
import "fmt"
type Point struct {
X int
Y int
}
type Circle struct {
Center Point
Radius float64
}
func main() {
p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{3, 4}
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false
c1 := Circle{Center: Point{0, 0}, Radius: 5}
c2 := Circle{Center: Point{0, 0}, Radius: 5}
fmt.Println(c1 == c2) // true
}包含不可比较字段(如切片、map)的结构体不能使用 ==:
package main
import "fmt"
type Data struct {
Values []int // 切片不可比较
}
func main() {
d1 := Data{Values: []int{1, 2, 3}}
d2 := Data{Values: []int{1, 2, 3}}
// fmt.Println(d1 == d2) // 编译错误:invalid operation
_ = d1
_ = d2
}结构体嵌套
结构体可以包含其他结构体作为字段。
package main
import "fmt"
type Address struct {
City string
District string
Street string
}
type Employee struct {
Name string
Age int
Address Address // 嵌套结构体
}
func main() {
emp := Employee{
Name: "张三",
Age: 28,
Address: Address{
City: "北京",
District: "朝阳区",
Street: "建国路",
},
}
fmt.Println(emp.Name) // 张三
fmt.Println(emp.Address.City) // 北京
fmt.Println(emp.Address.District) // 朝阳区
}JSON 序列化与结构体标签
encoding/json 包可以将结构体序列化为 JSON,通过标签控制字段名称和行为。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // 零值时省略
Password string `json:"-"` // 忽略此字段
Tags []string `json:"tags,omitempty"`
}
func main() {
u := User{
ID: 1,
Name: "admin",
Email: "admin@example.com",
Password: "secret",
Tags: []string{"root", "admin"},
}
// 序列化
data, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(data))
// 反序列化
jsonStr := `{"id": 2, "name": "user"}`
var u2 User
json.Unmarshal([]byte(jsonStr), &u2)
fmt.Printf("%+v\n", u2)
}常用 JSON 标签选项:
json:"field_name"— 指定 JSON 字段名json:"-"— 序列化时忽略json:",omitempty"— 零值时省略该字段
工厂函数模式(NewXxx)
Go 没有构造函数,但通常使用 NewXxx 工厂函数来初始化结构体。
package main
import (
"errors"
"fmt"
)
type User struct {
ID int
Name string
Email string
}
// NewUser 是工厂函数,返回指针
func NewUser(id int, name, email string) (*User, error) {
if name == "" {
return nil, errors.New("name cannot be empty")
}
return &User{ID: id, Name: name, Email: email}, nil
}
func main() {
u1, err := NewUser(1, "张三", "zhangsan@example.com")
if err != nil {
fmt.Println("创建失败:", err)
}
fmt.Printf("%+v\n", u1)
_, err = NewUser(2, "", "test@example.com")
fmt.Println(err) // name cannot be empty
}工厂函数的优势:可以在创建时执行验证逻辑、设置默认值、返回指针避免大结构体拷贝,符合 Go 的惯用写法。
总结
本篇全面介绍了 Go 语言结构体的核心知识:结构体的定义与初始化、值接收者与指针接收者方法、匿名字段(嵌入)实现组合复用、字段标签(struct tag)的用途、结构体比较的条件、结构体嵌套、JSON 序列化与标签配合,以及工厂函数模式。结构体是 Go 语言组织数据的基础,掌握结构体的使用是学习 Go 接口、方法等高级特性的前提。