结构体与接口
structs、methods、interfaces、embedding、type assertion。
结构体与接口
这一课你将能做到
- 定义结构体和方法,说清值接收者与指针接收者的区别与选择标准
- 定义并实现接口,理解 Go「隐式实现」与鸭子类型的含义
- 用 embedding 完成结构复用,用类型断言 / type switch 还原接口背后的具体类型
为什么要学
Go 没有 class、没有继承、没有构造函数重载,组织代码全靠三样东西:结构体(数据)、方法(行为)、接口(约定)。其中接口是 Go 语言灵魂——标准库 io.Reader、error、http.Handler 全是只有一两个方法的极小接口,正是这种设计让 Go 代码组合起来毫不费力。
这一课学完,你就具备了读懂任何 Go 项目主干结构的能力;后面的错误处理、泛型、Web 框架,全建立在这三个概念上。
先建立图像
Java 的继承是「血缘关系」,Go 的接口是「能力认证」:
Java: class Dog extends Animal ← 先有家谱,再有能力
Go: type Dog struct{...} ← 先有具体类型
type Speaker interface{ ← 任何人都可以定义「会说话」认证
Speak() string
}
func (d Dog) Speak() string ← Dog 实现了 Speak,自动获得认证
不需要 implements 声明
接口变量内部存的是「动态类型 + 动态值」这对组合:
var s Speaker = Dog{Name: "旺财"}
┌─────────────┬──────────────┐
│ 动态类型 Dog │ 动态值 {旺财} │
└─────────────┴──────────────┘
跟着做
步骤 1:定义结构体与创建实例
package main
import "fmt"
type User struct {
Name string
Age int
Email string // 未导出字段写成 email,本包外不可见
}
func main() {
// 方式一:字段名齐全(推荐,可读且耐字段增删)
u1 := User{Name: "张三", Age: 20, Email: "z@x.com"}
// 方式二:new 得到指针,字段全是零值
u2 := new(User)
u2.Name = "李四"
// 方式三:&User{} 同样得到指针
u3 := &User{Name: "王五"}
fmt.Println(u1, u2, u3)
fmt.Println(u1.Name, u2.Age)
// 零值结构体声明即可用
var zero User
fmt.Printf("%+v\n", zero) // {Name: Age:0 Email:}
}
步骤 2:方法——值接收者 vs 指针接收者
package main
import "fmt"
type Counter struct {
n int
}
// 值接收者:拿到的是副本,改 n 不影响原对象
func (c Counter) BrokenInc() {
c.n++
}
// 指针接收者:操作原对象
func (c *Counter) Inc() {
c.n++
}
func (c Counter) Get() int {
return c.n
}
func main() {
var c Counter
c.BrokenInc()
c.Inc()
c.Inc()
fmt.Println(c.Get()) // 2,BrokenInc 白改了
}
规则:要修改接收者状态 → 指针接收者;只读、且结构体很小 → 值接收者。拿不准就统一用指针接收者,保持一致。
步骤 3:构造函数惯例 NewXxx
Go 没有构造函数,惯例是写一个 NewXxx 函数做初始化和校验:
package main
import (
"errors"
"fmt"
)
type Account struct {
balance int
}
func NewAccount(init int) (*Account, error) {
if init < 0 {
return nil, errors.New("初始金额不能为负")
}
return &Account{balance: init}, nil
}
func (a *Account) Deposit(v int) { a.balance += v }
func (a Account) Balance() int { return a.balance }
func main() {
a, err := NewAccount(100)
if err != nil {
panic(err)
}
a.Deposit(50)
fmt.Println(a.Balance()) // 150
_, err = NewAccount(-1)
fmt.Println(err)
}