GO语言基础:接口和结构体

2 阅读5分钟

二、接口(Interface)

接口定义了一组方法签名,它只描述“能做什么”,而不关心具体的类型。在 Go 中,接口是隐式实现的:如果一个类型实现了接口中定义的所有方法,那么它就自动实现了该接口,无需显式声明。

1. 接口的定义

使用 typeinterface 关键字定义接口:

type Speaker interface {
    Speak() string
}

接口 Speaker 要求任何实现它的类型必须有一个 Speak() string 方法。

2. 接口的实现

假设我们有两个结构体:PersonDog,它们都实现了 Speak 方法:

type Person struct {
    Name string
}

func (p Person) Speak() string {
    return "你好,我是 " + p.Name
}

type Dog struct {
    Breed string
}

func (d Dog) Speak() string {
    return "汪汪!我是 " + d.Breed
}

现在,PersonDog 都隐式地实现了 Speaker 接口。

3. 使用接口实现多态

我们可以声明一个接口类型的变量,它可以保存任何实现了该接口的值:

func main() {
    var s Speaker

    s = Person{Name: "小明"}
    fmt.Println(s.Speak()) // 输出:你好,我是 小明

    s = Dog{Breed: "金毛"}
    fmt.Println(s.Speak()) // 输出:汪汪!我是 金毛
}

同一个接口变量,根据其底层具体类型的不同,表现出不同的行为,这就是多态

接口还可以作为函数参数,接受任何实现了该接口的类型:

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    MakeSound(Person{Name: "小红"})
    MakeSound(Dog{Breed: "柯基"})
}

4. 空接口 interface{}any

空接口 interface{} 没有定义任何方法,因此所有类型都实现了空接口。它可以用来表示任意类型的值。从 Go 1.18 开始,anyinterface{} 的别名,含义相同。

var i interface{}
i = 42
i = "hello"
i = Person{Name: "测试"}

// 使用 any
var a any = 3.14

空接口经常用于需要处理未知类型的情况,但使用时需要配合类型断言来恢复具体类型。

5. 类型断言

类型断言用于提取接口值中的具体类型。语法:value, ok := 接口变量.(具体类型)ok 表示断言是否成功,避免 panic。

var s Speaker = Person{Name: "小明"}

// 类型断言
p, ok := s.(Person)
if ok {
    fmt.Println("s 是 Person 类型,姓名:", p.Name)
} else {
    fmt.Println("s 不是 Person 类型")
}

// 直接断言,不检查会 panic
// d := s.(Dog)  // panic

也可以使用 type switch 进行多种类型判断:

switch v := s.(type) {
case Person:
    fmt.Println("Person:", v.Name)
case Dog:
    fmt.Println("Dog:", v.Breed)
default:
    fmt.Println("未知类型")
}

6. 接口组合

接口可以通过嵌套其他接口来创建新接口,类似于结构体嵌套。例如:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// 组合接口
type ReadWriter interface {
    Reader
    Writer
}

任何类型只要同时实现了 ReaderWriter 接口的方法,就自动实现了 ReadWriter

7. 常用接口示例

Go 标准库中有许多有用的接口,例如 fmt.Stringer

type Stringer interface {
    String() string
}

任何实现了 String() string 方法的类型,在使用 fmt.Print 等函数时会自动调用该方法获取字符串表示。

func (p Person) String() string {
    return fmt.Sprintf("Person(Name=%s, Age=%d)", p.Name, p.Age)
}

func main() {
    p := Person{Name: "小明", Age: 20}
    fmt.Println(p) // 输出:Person(Name=小明, Age=20)
}

三、总结

  • 结构体:用于定义复杂数据类型,支持嵌套和方法,是数据组织的基石。
  • 接口:定义行为规范,支持隐式实现,是实现多态和解耦的关键工具。
  • 结构体和接口结合使用,可以构建出灵活、可扩展的程序。