结构体(struct)是Go语言中组织数据的主要方式,类似于其他语言中的“类”(class),但不支持继承。结构体可以将多个任意类型的字段(field)组合成一个自定义类型,并可以为其定义方法(method),是实现面向对象编程的基础。
使用type关键字定义新类型,后面跟上struct关键字及字段列表。
package main
import "fmt"
// 定义一个 Person 结构体
type Person struct {
Name string
Age int
City string
}
func main() {
// 声明一个 Person 变量(零值)
var p Person
fmt.Printf("%#v\n", p) // main.Person{Name:"", Age:0, City:""}
}
字段命名通常采用驼峰式,首字母大写表示公开(可被其他包访问),小写表示私有。
p1 := Person{"Alice", 30, "New York"}
fmt.Println(p1)
缺点:依赖字段顺序,易出错。
p2 := Person{
Name: "Bob",
Age: 25,
City: "Los Angeles",
}
// 未指定的字段为零值
p3 := Person{Name: "Charlie"} // Age:0, City:""
p4 := new(Person) // 返回 *Person,字段为零值
p4.Name = "David"
p5 := &Person{Name: "Eve", Age: 28} // 直接创建指针
使用点号.访问或修改字段。
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name) // Alice
p.Age = 31
fmt.Println(p.Age) // 31
// 指针访问同样用点(自动解引用)
ptr := &p
ptr.City = "Boston" // 等价于 (*ptr).City = "Boston"
fmt.Println(p.City) // Boston
将一个结构体作为另一个结构体的字段,可以实现类似“组合”的效果。
type Address struct {
Street string
City string
}
type Employee struct {
Name string
Age int
Address Address // 嵌套结构体
}
func main() {
emp := Employee{
Name: "John",
Age: 35,
Address: Address{
Street: "123 Main St",
City: "Springfield",
},
}
fmt.Println(emp.Address.City)
}
如果只写类型名,不写字段名,称为匿名嵌套。外部结构体可以直接访问嵌套结构体的字段(提升)。
type Contact struct {
Email string
Phone string
}
type User struct {
Name string
Contact // 匿名嵌套
}
func main() {
u := User{
Name: "Tom",
Contact: Contact{
Email: "tom@example.com",
Phone: "123456",
},
}
// 可以直接访问提升的字段
fmt.Println(u.Email) // tom@example.com
fmt.Println(u.Phone) // 123456
}
结构体字段后可以添加反引号包裹的标签,常用于JSON序列化、ORM、表单验证等场景。
import "encoding/json"
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price,omitempty"`
Secret string `json:"-"` // 忽略此字段
}
func main() {
p := Product{ID: 1, Name: "Book", Price: 19.99}
jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData)) // {"id":1,"name":"Book","price":19.99}
}
常用的标签库:json、yaml、form、gorm等。
Go可以为任意自定义类型(包括结构体)定义方法。方法是带有接收者(receiver)的函数。
type Rectangle struct {
Width, Height float64
}
// 值接收者:计算面积(不会修改原结构体)
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 值接收者可以调用,但若要修改字段需使用指针接收者
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area()) // 50
}
指针接收者可以修改结构体字段,并且避免大结构体的拷贝。
// 指针接收者:放大矩形
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
rect := Rectangle{10, 5}
rect.Scale(2) // 自动取地址调用
fmt.Println(rect.Width) // 20
}
// 方法
func (r Rectangle) Area() float64 { ... }
// 普通函数
func Area(r Rectangle) float64 { ... }
Go没有内置构造函数,但通常使用New开头的函数来创建并初始化结构体,返回指针以避免大结构体拷贝。
func NewPerson(name string, age int) *Person {
return &Person{
Name: name,
Age: age,
City: "Unknown",
}
}
p := NewPerson("Alice", 30)
fmt.Println(p)
如果结构体所有字段都是可比较的,则结构体变量可以使用==和!=比较。
p1 := Person{Name: "Alice", Age: 30}
p2 := Person{Name: "Alice", Age: 30}
fmt.Println(p1 == p2) // true
// 不可比较的字段(如切片)会导致编译错误
package main
import "fmt"
type Student struct {
ID int
Name string
Scores []float64
}
// 计算平均分
func (s Student) Average() float64 {
if len(s.Scores) == 0 {
return 0
}
sum := 0.0
for _, score := range s.Scores {
sum += score
}
return sum / float64(len(s.Scores))
}
// 添加成绩(指针接收者,需要修改切片)
func (s *Student) AddScore(score float64) {
s.Scores = append(s.Scores, score)
}
func NewStudent(id int, name string) *Student {
return &Student{
ID: id,
Name: name,
Scores: []float64{},
}
}
func main() {
stu := NewStudent(1001, "张三")
stu.AddScore(85.5)
stu.AddScore(92.0)
stu.AddScore(78.0)
fmt.Printf("学生: %s (ID:%d), 平均分: %.2f\n", stu.Name, stu.ID, stu.Average())
}