Go 语言类型与接口 | 青训营笔记

75 阅读4分钟

Go 语言中的类型

在 Go 语言中,每个值都属于一个类型。类型用于定义该值的内存布局和操作集。Go 语言中的类型包括基本类型和复合类型。

基本类型

Go 语言中的基本类型包括布尔类型、数字类型、字符类型和字符串类型。

  • 布尔类型只有两个取值 true 和 false

  • 数字类型包括整数类型和浮点数类型

  • 字符类型用单引号括起来的单个 Unicode 字符,字符串类型用双引号括起来的 Unicode 字符串。

示例代码:

package main

import "fmt"

func main() {
    // 布尔类型
    var b bool = true
    fmt.Println(b) // 输出 true

    // 数字类型
    var i int = 123
    var f float64 = 3.14
    var c complex128 = 1 + 2i
    fmt.Println(i, f, c) // 输出 123 3.14 (1+2i)

    // 字符类型
    var ch rune = '中'
    fmt.Println(ch) // 输出 20013

    // 字符串类型
    var str string = "Hello, 世界"
    fmt.Println(str) // 输出 Hello, 世界
}

复合类型

Go 语言中的复合类型包括数组、切片、映射、结构体和指针。

  • 数组是一种固定长度的数据结构,它由一系列相同类型的元素组成,这些元素在内存中是连续存储的。数组的长度是在定义数组时指定的,一旦定义了数组的长度,就不能再改变。

  • 切片是一种动态数组,它是一个引用类型,可以动态地增加或减少元素的数量。切片本身并不存储任何数据,它只是对底层数组的一个引用,相当于底层数组的一个视图。

  • 映射(map)是一种无序的键值对集合,其中每个键都是唯一的。映射的键和值可以是任意类型,只要它们支持相应的操作。映射是一种引用类型,可以用 make 函数创建。

  • 结构体是一种复合数据类型,它由一系列具有相同或不同类型的成员组成。每个成员可以是任何类型,包括基本类型、数组、切片、结构体、函数等等。

  • 指针是一种变量,它存储了另一个变量的内存地址。

示例代码:

package main

import "fmt"

func main() {
    // 数组类型
    var arr [3]int = [3]int{1, 2, 3}
    fmt.Println(arr) // 输出 [1 2 3]

    // 切片类型
    var slice []int = []int{1, 2, 3}
    slice = append(slice, 4)
    fmt.Println(slice) // 输出 [1 2 3 4]

    // 映射类型
    var m map[string]int = map[string]int{"apple": 1, "banana": 2}
    m["orange"] = 3
    fmt.Println(m) // 输出 map[apple:1 banana:2 orange:3]

    // 结构体类型
    type Fruit struct {
        Name  string
        Price float64
    }
    var apple Fruit = Fruit{Name: "apple", Price: 3.0}
    fmt.Println(apple) // 输出 {apple 3}

    // 指针类型
    var p *int = &arr[0]
    fmt.Println(*p) // 输出 1
}

Go 语言中的接口

在 Go 语言中,接口是一种类型,它定义了一组方法的签名。

接口类型没有实现,只有实现了接口的具体类型才能被赋值给接口类型变量。

一个类型只要实现了接口类型的所有方法,就被认为是该接口类型的实现类型。

接口可以被用来定义一种通用的类型,不同的具体类型可以实现同一个接口,从而实现多态性。

示例代码:

package main

import "fmt"

// 定义水果接口类型
type Fruit interface {
    GetName() string
    GetPrice() float64
}

// 定义苹果类型并实现 Fruit 接口
type Apple struct {
    name  string
    price float64
}

func (apple Apple) GetName() string {
    return a.name
}

func (a Apple) GetPrice() float64 {
    return a.price
}

// 定义芒果类型并实现 Fruit 接口
type Mango struct {
    name  string
    price float64
}

func (m Mango) GetName() string {
    return m.name
}

func (m Mango) GetPrice() float64 {
    return m.price
}

// 定义西瓜类型并实现 Fruit 接口
type Watermelon struct {
    name  string
    price float64
}

func (w Watermelon) GetName() string {
    return w.name
}

func (w Watermelon) GetPrice() float64 {
    return w.price
}

// 使用接口类型变量
func PrintFruit(f Fruit) {
    fmt.Println("Name:", f.GetName(), "Price:", f.GetPrice())
}

func main() {
    apple := Apple{name: "apple", price: 3.0}
    PrintFruit(apple) // 输出 Name: apple Price: 3

    mango := Mango{name: "mango", price: 4.5}
    PrintFruit(mango) // 输出 Name: mango Price: 4.5

    watermelon := Watermelon{name: "watermelon", price: 2.0}
    PrintFruit(watermelon) // 输出 Name: watermelon Price: 2
}

首先定义了一个 Fruit 接口类型,它包含两个方法 GetName() 和 GetPrice()。

然后是三个具体类型 Apple、Mango 和 Watermelon,它们都实现了 Fruit 接口的 GetName() 和 GetPrice() 方法。

最后使用接口类型变量 PrintFruit() 打印出三个具体类型的名称和价格。

需要注意的是,接口类型可以作为参数类型和返回类型,从而实现更为灵活的函数或方法,比如:

func GetFruit(name string) Fruit {
    switch name {
    case "apple":
        return Apple{name: "apple", price: 3.0}
    case "mango":
        return Mango{name: "mango", price: 4.5}
    case "watermelon":
        return Watermelon{name: "watermelon", price: 2.0}
    default:
        return nil
    }
}

上述 GetFruit() 函数根据名称返回相应的 Fruit 接口类型,这种方式可以隐藏具体类型的实现细节,提高代码的可维护性和可扩展性。

判断是否实现

除了使用接口类型作为参数类型和返回类型,还可以使用类型断言来判断一个接口类型是否实现了某个接口,比如:

func IsFruit(f Fruit) bool {
    _, ok := f.(Fruit)
    return ok
}

上述 IsFruit() 函数判断一个接口类型是否实现了 Fruit 接口,如果是则返回 true,否则返回 false。