Golang基础笔记九之方法与接口

本文首发于公众号:Hunter后端

原文链接:Golang基础笔记九之方法与接口

本篇笔记介绍 Golang 里方法和接口,以下是本篇笔记目录:

  1. 方法
  2. 接口
  3. 用结构体实现类的功能

1、方法

首先介绍一下方法。

方法是与特定类型关联的函数,我们在实现一个函数前,绑定一个类型,就实现了这个类型的方法。

比如我们想实现一个结构体的方法,可以如下操作:

go 复制代码
type Person struct {
    Name string
    Age  int
}
func (person Person) fmtPersonInfo() {
    fmt.Printf("person name is %s, age is %d\n", person.Name, person.Age)
}

在上面的操作中,我们就为 Person 这个结构体绑定了一个方法,而其调用也很简单,就是实例化一个 Person 结构体后,就可以对其进行调用:

go 复制代码
person := Person{Name: "Hunter", Age: 28}
person.fmtPersonInfo()

方法支持的类型

方法支持绑定的类型有结构体、指针类型、接口类型以及自定义类型,但是不支持绑定 Golang 内置的类型包括 int、slice、map 等。

方法绑定到指针类型

前面介绍了方法绑定到结构体上,这里再介绍一个绑定到指针类型上,还是前面的 Person 结构体,绑定到其指针上,来实现更改 Age 字段的操作:

go 复制代码
func (person *Person) ChangeAge(age int) {
    person.Age = age
}
person := Person{Name: "Hunter", Age: 28}
person.fmtPersonInfo()
person.ChangeAge(18)
person.fmtPersonInfo()

第二次打印信息就可以看到 person 的 age 已经发生了变化。

这里需要注意一点,Person 结构体是值类型,如果绑定的是其结构体本身,而非其指针类型,在方法中对其更改后,并不会影响结构体本身,比如下面的操作:

go 复制代码
func (person Person) NewChangeAge(age int) {
    person.Age = age
}
person := Person{Name: "Hunter", Age: 28}
person.fmtPersonInfo()
person.NewChangeAge(18)
person.fmtPersonInfo()

可以看到,这里调用 NewChangeAge() 方法后,没有对 person 这个结构体本身进行更改。

方法绑定到自定义类型

而如果想要绑定 int、slice、map 等内置类型,可以通过方法支持的自定义类型来绑定。

比如我们想要实现使用 slice 绑定一个打印其长度的方法,可以通过自定义类型设置一个别名,通过别名来绑定一个方法:

go 复制代码
type MySlce []int
func (mySlice MySlce) printSliceLength() {
    fmt.Printf("mySliceLength is %d\n", len(mySlice))
}
slice := MySlce{1, 2, 3}
slice.printSliceLength()

2、接口

1. 接口的定义和实现

接口是一组方法签名的集合,任何类型只要实现了接口中的所有方法,就被认为实现了该接口。

比如下面我们定义了一个形状的接口,内部有面积和周长两个空方法:

go 复制代码
type Shape interface {
    Area() float64
    Perimeter() float64
}

这样我们就定义了一个接口。

而如果我们要实现这个接口,只需要实现这个接口里的两个方法 Area() 和 Perimeter() 就是实现了这个接口,这个过程是隐式的,不需要显式声明或者绑定。

接下来我们定义 Rectangle 和 Circle 两个结构体,并且实现 Area() 和 Perimeter() 两个方法:

go 复制代码
type Rectangle struct {
    Width, Height float64
}
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}
type Circle struct {
    radius float64
}
func (c Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
    return 2 * 3.14 * c.radius
}

我们已经分别用 Rectangle 和 Circle 这两个结构体实现了 Shape 接口。

那么这个接口在这里有什么作用呢,我们可以实现一个函数,接收接口类型的参数,那么实现了这个接口的结构体都可以作为传入:

go 复制代码
func PrintShapeInfo(s Shape) {
    fmt.Println("Area: ", s.Area())
    fmt.Println("Perimeter: ", s.Perimeter())
}
func main() {
    r := Rectangle{Width: 2, Height: 3}
    PrintShapeInfo(r)
    c := Circle{Radius: 5}
    PrintShapeInfo(c)
}

2. 类型断言

类型断言用于检查接口值的底层具体类型,并提取该类型的值,其用法示例如下:

go 复制代码
value, ok := interfaceValue.(ConcreteType)
  1. value 是转换后的具体类型值
  2. ok 是一个布尔值,表示是否断言成功
  3. interfaceValue 是接口类型的变量
  4. ConcreteType 是目标具体类型。
    比如我们可以修改 PrintShapeInfo 函数,在内部对其进行类型断言:
go 复制代码
func PrintShapeInfo(s Shape) {
    circle, ok := s.(Circle)
    if ok {
        fmt.Println("ths shape is circle, the area is: ", circle.Area())
    } else {
        fmt.Println("this shape is not circle")
    }
    fmt.Println("Area: ", s.Area())
    fmt.Println("Perimeter: ", s.Perimeter())
}

3. 空接口

空接口(interface{}) 可以表示任何类型的值,常用于处理不确定类型的数据。

比如我们想打印一个输入的变量,但是这个变量的类型不确定,我们可以使用空接口来处理这种情况。

go 复制代码
func PrintType(a interface{}) {
    switch v := a.(type) {
    case int:
        fmt.Println("this is int: ", v)
    case float64:
        fmt.Println("this is float64: ", v)
    case string:
        fmt.Println("this is string: ", v)
    default:
        fmt.Println("this is other type: ", v)
    }
}

func main() {
    PrintType(1)
    PrintType(3.4)
    PrintType("abc")
}

3、用结构体实现类的功能

在 Golang 里没有类的相关定义,但是我们可以使用结构体和方法的组合来实现类的相关特性。

1. 封装

我们可以通过结构体字段的首字母大小写控制访问权限,然后提供公共方法来操作私有字段。

在结构体中,大写开头的字段为公开字段,小写开头的字段为私有字段。

我们用下面的示例来展示一下用结构体和方法来实现封装功能。

文件目录如下:

shell 复制代码
.
├── main.go
├── service
│   └── person_operation.go

其中 person_operation.go 的内容如下:

go 复制代码
package service

type Person struct {
    Name   string
    Age    int
    gender string
}

func (p *Person) SetGender(gender string) {
    p.gender = gender
}

func (p *Person) GetGender() string {
    return p.gender
}

其中,Person 这个结构体的 Name 和 Age 字段首字母都为大写,为公共字段,而 gender 首字母为小写,在 main.go 里不能直接引用,所以下面定义了两个公有接口提供设置和访问。

以下是 main.go 里的内容:

go 复制代码
package main

import (
    "fmt"
    "go_proj/service"
)

func main() {
    person := service.Person{
        Name: "张三",
        Age:  18,
        // gender: "男",  // gender是私有属性,不能直接访问
    }
    // fmt.Println(person.gender) // gender是私有属性,不能直接访问
    fmt.Println(person.GetGender())
    person.SetGender("男")
    fmt.Println(person.GetGender())
}

在这里,gender 字段在 Person 定义和访问的时候都不能直接操作,需要通过设置的方法来进行定义以及访问。

2. 继承

我们可以通过结构体的嵌套来实现继承,比如下面新建一个 Chinese 结构体:

go 复制代码
type Chinese struct {
    Person
}

然后我们定义的 Chinese 实例可以调用 Person 结构体的方法:

go 复制代码
chinese := service.Chinese{
    Person: service.Person{
        Name: "张三",
        Age:  18,
    },
}
chinese.SetGender("男")
fmt.Println(chinese.GetGender())

3. 多态

多态则是同一方法名在不同的类型中有不同的实现,这个操作在前面介绍接口的就已经实现过了,这里不再做赘述。

相关推荐
XHunter1 天前
Golang基础笔记十五之sync
golang基础笔记
XHunter6 天前
Golang基础笔记十四之文件操作
golang基础笔记
XHunter8 天前
Golang基础笔记十三之context
golang基础笔记
XHunter13 天前
Golang基础笔记十二之defer、panic、error
golang基础笔记
XHunter15 天前
Golang基础笔记十一之日期与时间处理
golang基础笔记
XHunter20 天前
Golang基础笔记十之goroutine和channel
golang基础笔记
XHunter1 个月前
Golang基础笔记三之数组和切片
golang基础笔记
XHunter1 个月前
Golang基础笔记二之字符串及其操作
golang基础笔记