1. 引言
在 Go 语言中,结构体(struct)是一种聚合数据类型,它可以将多个不同类型的字段组合成一个整体。结构体是 Go 语言实现面向对象编程的基础,也是日常开发中最常用的自定义类型之一。本文将详细介绍结构体的定义方式、实例化方法以及常见的使用技巧。
2. 结构体的定义
结构体通过 type 关键字和 struct 关键字来定义,基本语法如下:
go
type 结构体名 struct {
字段名1 类型1
字段名2 类型2
// ...
}
下面定义一个表示学生信息的结构体:
go
type Student struct {
Name string
Age int
Score float64
}
2.1 字段的可见性
Go 语言中,结构体字段的可见性由字段名的首字母大小写决定:
- 首字母大写:字段为导出字段(public),可在包外访问。
- 首字母小写:字段为非导出字段(private),只能在包内访问。
go
type Person struct {
Name string // 导出字段,包外可访问
age int // 非导出字段,仅包内可访问
}
2.2 结构体标签(Tag)
结构体字段可以附带标签(Tag),标签是字段的元信息,常用于 JSON 序列化、数据库映射等场景:
go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
标签通过反引号包裹,使用 reflect 包可以读取标签内容。
3. 结构体的实例化
定义结构体后,需要通过实例化来创建具体的对象。Go 语言提供了多种实例化方式。
3.1 零值实例化
使用 var 关键字声明结构体变量,此时所有字段为零值:
go
var stu Student
fmt.Println(stu.Name) // 输出空字符串 ""
fmt.Println(stu.Age) // 输出 0
fmt.Println(stu.Score) // 输出 0
3.2 字面量实例化
使用结构体字面量直接初始化,有两种写法:
方式一:按字段顺序赋值(不推荐)
go
stu := Student{"张三", 20, 95.5}
这种方式要求字段顺序与定义完全一致,一旦调整字段顺序就会出错,可读性较差。
方式二:按字段名赋值(推荐)
go
stu := Student{
Name: "张三",
Age: 20,
Score: 95.5,
}
按字段名赋值更清晰、更安全,即使调整字段顺序也不受影响。
3.3 使用 new 关键字
使用 new 关键字可以创建结构体指针,并返回指向零值结构体的指针:
go
stu := new(Student)
stu.Name = "李四"
fmt.Println(stu.Age) // 输出 0
3.4 取地址实例化
使用 & 操作符对结构体字面量取地址,返回结构体指针:
go
stu := &Student{
Name: "王五",
Age: 22,
}
这种方式与 new 的区别在于,& 方式可以在创建时直接初始化字段值。
4. 结构体的访问与修改
通过点号(.)操作符可以访问或修改结构体的字段:
go
stu := Student{Name: "赵六", Age: 21}
// 访问字段
fmt.Println(stu.Name)
// 修改字段
stu.Age = 23
fmt.Println(stu.Age) // 输出 23
对于结构体指针,Go 语言会自动解引用,因此可以直接使用点号操作符:
go
stu := &Student{Name: "孙七"}
stu.Age = 25 // 等价于 (*stu).Age = 25
5. 结构体作为函数参数
结构体可以作为函数参数传递,但需要注意值传递与指针传递的区别。
5.1 值传递
go
func updateName(s Student, name string) {
s.Name = name // 修改的是副本,不影响原结构体
}
stu := Student{Name: "张三"}
updateName(stu, "李四")
fmt.Println(stu.Name) // 输出 "张三"
5.2 指针传递
go
func updateName(s *Student, name string) {
s.Name = name // 修改的是原结构体
}
stu := Student{Name: "张三"}
updateName(&stu, "李四")
fmt.Println(stu.Name) // 输出 "李四"
当结构体较大或需要修改原值时,应优先使用指针传递,避免不必要的内存拷贝。
6. 结构体的方法
Go 语言允许为结构体定义方法,方法本质上是在函数名前加上接收者(receiver)。
go
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
}
调用方法:
go
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area()) // 输出 50
rect.Scale(2)
fmt.Println(rect.Width) // 输出 20
值接收者 不会修改原结构体,指针接收者可以修改原结构体。当方法需要修改结构体字段时,应使用指针接收者。
7. 结构体的嵌套
结构体可以嵌套其他结构体,形成更复杂的数据模型。
go
type Address struct {
City string
Street string
}
type Employee struct {
Name string
Address Address // 普通嵌套
}
访问嵌套字段:
go
emp := Employee{
Name: "张三",
Address: Address{
City: "北京",
Street: "中关村大街",
},
}
fmt.Println(emp.Address.City) // 输出 "北京"
7.1 匿名嵌套(嵌入)
Go 语言支持匿名嵌套,即只写类型名而不写字段名,此时可以直接访问内层字段:
go
type Employee struct {
Name string
Address // 匿名嵌套
}
emp := Employee{
Name: "李四",
Address: Address{
City: "上海",
},
}
fmt.Println(emp.City) // 直接访问,输出 "上海"
匿名嵌套实现了类似继承的效果,是 Go 语言实现组合的重要方式。
8. 完整示例
下面是一个综合示例,演示结构体的定义、实例化、方法调用和嵌套使用:
go
package main
import "fmt"
// 定义地址结构体
type Address struct {
City string
Street string
}
// 定义学生结构体
type Student struct {
Name string
Age int
Address Address
}
// 值接收者方法
func (s Student) Info() string {
return fmt.Sprintf("姓名:%s,年龄:%d,城市:%s", s.Name, s.Age, s.Address.City)
}
// 指针接收者方法
func (s *Student) SetAge(age int) {
s.Age = age
}
func main() {
// 实例化结构体
stu := Student{
Name: "张三",
Age: 20,
Address: Address{
City: "北京",
Street: "中关村大街",
},
}
// 调用方法
fmt.Println(stu.Info()) // 输出:姓名:张三,年龄:20,城市:北京
// 修改字段
stu.SetAge(21)
fmt.Println(stu.Info()) // 输出:姓名:张三,年龄:21,城市:北京
}
9. 总结
本文详细介绍了 Go 语言结构体的定义与实例化方法,包括:
- 结构体的定义语法和字段可见性规则。
- 四种实例化方式:零值、字面量、
new和取地址。 - 结构体字段的访问与修改。
- 值传递与指针传递的区别。
- 值接收者与指针接收者方法。
- 结构体的普通嵌套与匿名嵌套。
结构体是 Go 语言数据组织的核心工具,掌握其定义与实例化是编写高质量 Go 代码的基础。建议读者结合上述示例动手实践,加深理解。