Go 结构体与方法
结构体定义
基本语法
package main
import "fmt"
// 定义结构体
type Person struct {
Name string
Age int
City string
}
// 嵌套结构体
type Address struct {
Street string
City string
Zip string
}
type Employee struct {
Person // 匿名字段(嵌入)
Address // 匿名字段
EmployeeID int
Department string
}
func main() {
// 创建结构体实例
var p1 Person
p1.Name = "Alice"
p1.Age = 30
p1.City = "Beijing"
// 字面量创建
p2 := Person{
Name: "Bob",
Age: 25,
City: "Shanghai",
}
// 按顺序创建(不推荐,容易出错)
p3 := Person{"Charlie", 35, "Guangzhou"}
// 部分字段初始化
p4 := Person{
Name: "David",
// Age 和 City 使用零值
}
fmt.Printf("p1: %+v\n", p1)
fmt.Printf("p2: %+v\n", p2)
fmt.Printf("p3: %+v\n", p3)
fmt.Printf("p4: %+v\n", p4)
// 使用嵌套结构体
emp := Employee{
Person: Person{
Name: "Alice",
Age: 30,
},
Address: Address{
Street: "123 Main St",
City: "Beijing",
Zip: "100000",
},
EmployeeID: 1001,
Department: "Engineering",
}
// 通过嵌入字段访问
fmt.Printf("员工姓名: %s\n", emp.Name) // 直接访问嵌入的 Person.Name
fmt.Printf("员工城市: %s\n", emp.City) // 直接访问嵌入的 Address.City
fmt.Printf("员工ID: %d\n", emp.EmployeeID)
}方法定义
值接收者方法
package main
import "fmt"
type Rectangle struct {
Width float64
Height float64
}
// 值接收者方法:不会修改原始值
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 值接收者方法:计算周长
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 值接收者方法:不会修改接收者
func (r Rectangle) Scale(factor float64) Rectangle {
return Rectangle{
Width: r.Width * factor,
Height: r.Height * factor,
}
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
area := rect.Area()
fmt.Printf("面积: %.2f\n", area) // 50.00
perimeter := rect.Perimeter()
fmt.Printf("周长: %.2f\n", perimeter) // 30.00
// 值接收者方法不会修改原始值
scaled := rect.Scale(2)
fmt.Printf("原始: %+v\n", rect) // {Width:10 Height:5}
fmt.Printf("缩放后: %+v\n", scaled) // {Width:20 Height:10}
}指针接收者方法
package main
import "fmt"
type Point struct {
X, Y int
}
// 指针接收者方法:可以修改接收者
func (p *Point) Move(dx, dy int) {
p.X += dx
p.Y += dy
}
// 指针接收者方法:设置坐标
func (p *Point) SetPosition(x, y int) {
p.X = x
p.Y = y
}
func main() {
point := Point{X: 1, Y: 2}
fmt.Printf("原始: %+v\n", point) // {X:1 Y:2}
point.Move(10, 10)
fmt.Printf("移动后: %+v\n", point) // {X:11 Y:12}
point.SetPosition(100, 200)
fmt.Printf("设置后: %+v\n", point) // {X:100 Y:200}
}值接收者 vs 指针接收者
package main
import "fmt"
type Counter struct {
value int
}
// 值接收者:不会修改原始值
func (c Counter) IncrementByValue() {
c.value++ // 只修改副本
}
// 指针接收者:会修改原始值
func (c *Counter) IncrementByPointer() {
c.value++ // 修改原始值
}
// 选择原则:
// 1. 如果方法需要修改接收者,使用指针接收者
// 2. 如果结构体很大,使用指针接收者(避免复制)
// 3. 如果结构体包含 sync.Mutex 等字段,使用指针接收者
// 4. 其他情况,值接收者和指针接收者都可以,但建议保持一致
func main() {
counter := Counter{value: 0}
counter.IncrementByValue()
fmt.Printf("值接收者: %d\n", counter.value) // 0(未改变)
counter.IncrementByPointer()
fmt.Printf("指针接收者: %d\n", counter.value) // 1(已改变)
}方法集
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 值接收者方法
func (p Person) GetName() string {
return p.Name
}
// 指针接收者方法
func (p *Person) SetAge(age int) {
p.Age = age
}
func main() {
// 值类型可以调用值接收者方法
p1 := Person{Name: "Alice", Age: 30}
fmt.Println(p1.GetName()) // "Alice"
// 值类型也可以调用指针接收者方法(Go 自动转换)
p1.SetAge(31) // Go 自动转换为 (&p1).SetAge(31)
fmt.Printf("年龄: %d\n", p1.Age) // 31
// 指针类型可以调用指针接收者方法
p2 := &Person{Name: "Bob", Age: 25}
p2.SetAge(26)
fmt.Printf("年龄: %d\n", p2.Age) // 26
// 指针类型也可以调用值接收者方法(Go 自动解引用)
fmt.Println(p2.GetName()) // Go 自动转换为 (*p2).GetName()
}方法表达式和方法值
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}
// 方法值:将方法绑定到特定实例
areaFunc := rect.Area // 类型:func() float64
fmt.Println(areaFunc()) // 50
// 方法表达式:将方法作为函数
areaMethod := Rectangle.Area // 类型:func(Rectangle) float64
fmt.Println(areaMethod(rect)) // 50
// 指针接收者的方法表达式
scaleMethod := (*Rectangle).Scale // 类型:func(*Rectangle, float64)
scaleMethod(&rect, 2)
fmt.Printf("缩放后: %+v\n", rect) // {Width:20 Height:10}
}嵌入和方法提升
package main
import "fmt"
type Engine struct {
Power int
}
func (e *Engine) Start() {
fmt.Println("引擎启动")
}
func (e *Engine) Stop() {
fmt.Println("引擎停止")
}
type Car struct {
Engine // 嵌入 Engine(匿名字段)
Brand string
Model string
}
// Car 自动获得了 Engine 的方法
// 可以直接调用 car.Start() 和 car.Stop()
func main() {
car := Car{
Engine: Engine{Power: 200},
Brand: "Toyota",
Model: "Camry",
}
// 直接调用嵌入类型的方法
car.Start() // "引擎启动"
car.Stop() // "引擎停止"
// 也可以显式调用
car.Engine.Start()
// 访问嵌入类型的字段
fmt.Printf("功率: %d 马力\n", car.Power) // 直接访问
fmt.Printf("功率: %d 马力\n", car.Engine.Power) // 显式访问
}结构体标签(Tag)
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id" db:"user_id"` // 多个标签
Username string `json:"username" db:"username"`
Email string `json:"email" db:"email"`
Password string `json:"-" db:"password"` // json:"-" 表示不序列化
Age int `json:"age,omitempty" db:"age"` // omitempty: 零值时不序列化
}
func main() {
user := User{
ID: 1,
Username: "alice",
Email: "alice@example.com",
Password: "secret",
Age: 30,
}
// JSON 序列化(使用 json 标签)
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Printf("错误: %v\n", err)
return
}
fmt.Println(string(jsonData))
// 输出: {"id":1,"username":"alice","email":"alice@example.com","age":30}
// 注意:Password 字段被忽略(json:"-")
// JSON 反序列化
jsonStr := `{"id":2,"username":"bob","email":"bob@example.com"}`
var user2 User
err = json.Unmarshal([]byte(jsonStr), &user2)
if err != nil {
fmt.Printf("错误: %v\n", err)
return
}
fmt.Printf("用户: %+v\n", user2)
}结构体比较
package main
import "fmt"
type Point struct {
X, Y int
}
func main() {
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 1, Y: 2}
p3 := Point{X: 2, Y: 3}
// 结构体可以比较(如果所有字段都是可比较的)
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false
// 如果结构体包含不可比较的字段(如切片、map),则不能比较
type BadStruct struct {
X int
Y []int // 切片不可比较
}
// b1 := BadStruct{X: 1, Y: []int{1, 2}}
// b2 := BadStruct{X: 1, Y: []int{1, 2}}
// fmt.Println(b1 == b2) // 编译错误:不能比较
}空结构体
package main
import "fmt"
// 空结构体:不占用内存空间
type Empty struct{}
// 用途 1:作为 map 的 value(实现 Set)
func setExample() {
set := make(map[string]struct{})
set["apple"] = struct{}{}
set["banana"] = struct{}{}
if _, exists := set["apple"]; exists {
fmt.Println("apple 存在")
}
}
// 用途 2:作为 channel 的信号
func signalExample() {
done := make(chan struct{})
go func() {
// 做一些工作
fmt.Println("工作完成")
done <- struct{}{} // 发送信号
}()
<-done // 等待信号
fmt.Println("收到信号")
}
// 用途 3:实现方法(不需要状态)
type Counter struct{}
func (c Counter) Count() int {
return 0
}
func main() {
setExample()
signalExample()
}最佳实践
package main
import "fmt"
// 1. 使用构造函数
type User struct {
Name string
Email string
Age int
}
// NewUser 是构造函数(Go 的约定)
func NewUser(name, email string, age int) *User {
return &User{
Name: name,
Email: email,
Age: age,
}
}
// 2. 使用指针接收者当需要修改状态时
type BankAccount struct {
balance float64
}
func (b *BankAccount) Deposit(amount float64) {
b.balance += amount
}
func (b *BankAccount) Withdraw(amount float64) error {
if amount > b.balance {
return fmt.Errorf("余额不足")
}
b.balance -= amount
return nil
}
func (b *BankAccount) Balance() float64 {
return b.balance
}
// 3. 使用值接收者当不需要修改状态时
type Point struct {
X, Y int
}
func (p Point) DistanceTo(other Point) float64 {
dx := float64(p.X - other.X)
dy := float64(p.Y - other.Y)
return dx*dx + dy*dy // 简化版,实际应该开方
}
func main() {
// 使用构造函数
user := NewUser("Alice", "alice@example.com", 30)
fmt.Printf("用户: %+v\n", user)
// 使用银行账户
account := &BankAccount{balance: 1000}
account.Deposit(500)
fmt.Printf("余额: %.2f\n", account.Balance()) // 1500
err := account.Withdraw(2000)
if err != nil {
fmt.Printf("错误: %v\n", err) // 余额不足
}
}总结
Go 结构体和方法的特点:
- 结构体:自定义类型,可以包含多个字段
- 方法:可以为类型定义方法(值接收者或指针接收者)
- 嵌入:通过嵌入实现组合,方法自动提升
- 标签:使用标签进行元数据标注(JSON、数据库等)
- 构造函数:使用 NewXxx 函数创建实例
- 选择原则:需要修改状态用指针接收者,否则用值接收者
掌握结构体和方法后,就可以实现面向对象的编程了!