主题
Go 结构体与接口
Go 没有传统面向对象语言中的「类」和「继承」,而是通过**结构体(struct)承载数据,通过接口(interface)定义行为,并以组合(composition)**实现复用。
结构体
定义与初始化
go
type Person struct {
Name string
Age int
}
// 方式一:字段顺序
p1 := Person{"张三", 25}
// 方式二:字段名(推荐,可读性好)
p2 := Person{Name: "李四", Age: 30}
// 方式三:new,返回指针
p3 := new(Person)
p3.Name = "王五"
// 取地址
p4 := &Person{Name: "赵六"}结构体标签(Tag)
标签常用于序列化(如 JSON):
go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
// 忽略该字段
Password string `json:"-"`
}
u := User{ID: 1, Name: "Tom", Password: "secret"}
data, _ := json.Marshal(u)
// {"id":1,"name":"Tom"}方法
方法是带有**接收者(receiver)**的函数。接收者可以是值或指针。
go
// 值接收者(只读,操作的是副本)
func (p Person) Greet() string {
return "Hello, I'm " + p.Name
}
// 指针接收者(可修改,避免大对象拷贝)
func (p *Person) Birthday() {
p.Age++
}
func main() {
p := Person{Name: "张三", Age: 25}
p.Birthday()
fmt.Println(p.Greet()) // Hello, I'm 张三
fmt.Println(p.Age) // 26
}规则:若方法需要修改接收者或接收者是大结构体,使用指针接收者;否则可用值接收者。Go 会自动处理
p.Method()与(&p).Method()的调用。
方法集(method set)
- 值类型
T的方法集:所有值接收者方法 - 指针类型
*T的方法集:值接收者 + 指针接收者方法
这直接影响接口实现(见下文)。
接口
Go 采用隐式实现(duck typing):类型只需实现接口的所有方法,即视为实现了该接口,无需 implements 声明。
go
// 定义接口
type Writer interface {
Write([]byte) (int, error)
}
// 任何实现了 Write 方法的类型都自动实现了 Writer 接口
type MyWriter struct{}
func (m MyWriter) Write(data []byte) (int, error) {
return len(data), nil
}
var w Writer = MyWriter{} // 合法接口组合
接口可通过嵌入其他接口来组合:
go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// 接口组合(等价于同时拥有 Read 和 Close)
type ReadWriteCloser interface {
Reader
Writer
Closer
}空接口与 any
空接口 interface{}(Go 1.18+ 起可用 any 别名)可以表示任意类型:
go
func describe(v any) {
fmt.Printf("value: %v, type: %T\n", v, v)
}
describe(42)
describe("hello")
describe([]int{1, 2, 3})类型断言与类型 switch
go
var w Writer = MyWriter{}
// 类型断言(带 ok 安全形式)
mw, ok := w.(MyWriter)
if ok {
fmt.Println("断言成功:", mw)
}
// 类型 switch
switch v := w.(type) {
case MyWriter:
fmt.Println("MyWriter")
case *os.File:
fmt.Println("*os.File")
default:
fmt.Printf("unknown type: %T\n", v)
}接口的值与 nil
接口的底层是 (类型, 值) 二元组。只有当类型和值都为 nil 时接口才等于 nil:
go
func doSomething() error {
var p *MyError = nil
// 返回的是 (*MyError)(nil),类型非 nil,接口不等于 nil!
return p
}
err := doSomething()
if err != nil { // 这里会进入,因为接口类型非 nil
fmt.Println("error is not nil")
}常见陷阱:返回具体类型的 nil 指针会被包装成非 nil 接口。返回错误时应直接
return nil。
组合优于继承
Go 通过结构体嵌入实现组合复用:
go
type Engine struct {
Power int
}
func (e Engine) Start() string {
return "engine started"
}
// 嵌入 Engine,UserCar 自动获得其字段和方法
type Car struct {
Engine // 匿名嵌入
Brand string
}
c := Car{Engine: Engine{Power: 100}, Brand: "Toyota"}
c.Start() // 提升(promoted)方法,可直接调用
fmt.Println(c.Power) // 提升字段作者:yanshaodong