Skip to content

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