Go语言 接口 II


导言

  • 原文链接: Part 19: Interfaces - II
  • If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.

接口

接口与接收器

Go语言 接口Ⅰ 部分,我们都是使用值接收器来实现接口。其实,通过指针接收器,我们也可以实现接口。

通过下面的程序,我们来理解一下。

package main

import "fmt"

type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}

func (p Person) Describe() { //implemented using value receiver  
    fmt.Printf("%s is %d years old\n", p.name, p.age)
}

type Address struct {  
    state   string
    country string
}

func (a *Address) Describe() { //implemented using pointer receiver  
    fmt.Printf("State %s Country %s", a.state, a.country)
}

func main() {  
    var d1 Describer
    p1 := Person{"Sam", 25}
    d1 = p1
    d1.Describe()
    p2 := Person{"James", 32}
    d1 = &p2
    d1.Describe()

    var d2 Describer
    a := Address{"Washington", "USA"}

    /* compilation error if the following line is
       uncommented
       cannot use a (type Address) as type Describer
       in assignment: Address does not implement
       Describer (Describe method has pointer
       receiver)
    */
    //d2 = a

    d2 = &a //This works since Describer interface
    //is implemented by Address pointer in line 22
    d2.Describe()
}

在上面程序的第 13 行,Person结构 使用 值接收器,实现了 Describer接口。

在第 19 行,p1 被分配给了 d1。因为值接收器能接收 值类型变量,所以 d1.Describe() 能正常运行,该语句会输出:Sam is 25 years old

在第 32 行,&p2 被分配给了 d1。因为值接收器能接收 指针类型变量,所以 d1.Describe() 能正常运行,该语句会输出:James is 32 years old

Go语言 方法 这一部分我们说过:值接收器 能接收 值类型变量、指针类型变量。

在第 22 行,Address结构 使用 指针接收器,实现了 Describer接口。

如果解除第 45 行的注释,编译器将输出错误:

main.go:42: cannot use a (type Address) as type Describer in assignment: Address does not implement Describer (Describe method has pointer receiver).

报错的原因在于:通过指针接收器,Address结构 实现了 Describer接口,即 *Address类型 实现了 Describer接口。 a 的类型是 Address,而不是 *AddressAddress类型 并没有实现 Describer接口。

这可能让你大吃一惊,因为在 Go语言 方法 部分,我们说过:指针接收器 能接收 指针类型变量、值类型变量。

那为什么第 45 行的代码不适用呢?
原因就是:指针方法调用的前提是 — 被调用者是指针,或者它的地址能被获取。因为存储在接口下的具体对象不可寻址,所以在第 45 行,编译器并不能自动获取到 a 的地址,从而引发错误。

47 行的代码能正常运行,因为我们将 &a 分配给了 d2

程序输出如下:

Sam is 25 years old  
James is 32 years old  
State Washington Country USA  

实现多个接口

同一类型可以实现多个接口。

来看代码:

package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    DisplaySalary()
}

type LeaveCalculator interface {  
    CalculateLeavesLeft() int
}

type Employee struct {  
    firstName string
    lastName string
    basicPay int
    pf int
    totalLeaves int
    leavesTaken int
}

func (e Employee) DisplaySalary() {  
    fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}

func (e Employee) CalculateLeavesLeft() int {  
    return e.totalLeaves - e.leavesTaken
}

func main() {  
    e := Employee {
        firstName: "Naveen",
        lastName: "Ramanathan",
        basicPay: 5000,
        pf: 200,
        totalLeaves: 30,
        leavesTaken: 5,
    }
    var s SalaryCalculator = e
    s.DisplaySalary()
    var l LeaveCalculator = e
    fmt.Println("\nLeaves left =", l.CalculateLeavesLeft())
}

在第 711 行,我们分别声明了 SalaryCalculatorLeaveCalculator接口,它们分别具有 DisplaySalaryCalculateLeavesLeft方法。

在第 15 行,我们定义了 Employee结构。

在第 2428 行,我们为Employee结构,分别添加了 DisplaySalaryCalculateLeavesLeft方法。此时 Employee 实现了 SalaryCalculatorLeaveCalculator接口。

在第 41 行,我们将 e 分配给了 SalaryCalculator接口类型 的 变量s。在第 43 行,我们将 e 分配给了 LeaveCalculator接口类型 的 变量l。由于 e 的类型是 Employee,该类型实现了 SalaryCalculatorLeaveCalculator接口,所以上面的操作都是合法的。

程序输出如下:

Naveen Ramanathan has salary $5200  
Leaves left = 25  

接口嵌入

通过嵌入接口,我们可以创建一个新接口。

来看看怎么做:

package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    DisplaySalary()
}

type LeaveCalculator interface {  
    CalculateLeavesLeft() int
}

type EmployeeOperations interface {  
    SalaryCalculator
    LeaveCalculator
}

type Employee struct {  
    firstName string
    lastName string
    basicPay int
    pf int
    totalLeaves int
    leavesTaken int
}

func (e Employee) DisplaySalary() {  
    fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}

func (e Employee) CalculateLeavesLeft() int {  
    return e.totalLeaves - e.leavesTaken
}

func main() {  
    e := Employee {
        firstName: "Naveen",
        lastName: "Ramanathan",
        basicPay: 5000,
        pf: 200,
        totalLeaves: 30,
        leavesTaken: 5,
    }
    var empOp EmployeeOperations = e
    empOp.DisplaySalary()
    fmt.Println("\nLeaves left =", empOp.CalculateLeavesLeft())
}

在上面程序的第 15 行,通过内嵌 SalaryCalculatorLeaveCalculator接口,我们创建了 EmployeeOperations接口 。

任何实现了 SalaryCalculatorLeaveCalculator接口 的类型,都实现了 EmployeeOperations接口。

因为 Employee结构 实现了 SalaryCalculatorLeaveCalculator接口,所以它实现了 EmployeeOperations接口。

在第 46 行,变量e 被分配给了 变量empOp,其中 e 的类型为 Emoloyee结构,empOp 的类型为 EmployeeOperations接口。

在第 4748 行,我们分别调用了 empOp变量 的 DisplaySalaryCalculateLeavesLeft方法。

程序输出如下:

Naveen Ramanathan has salary $5200  
Leaves left = 25  

接口的零值

接口的零值为 nilnil接口 的 具体类型、底层数值 都为 nil

package main

import "fmt"

type Describer interface {  
    Describe()
}

func main() {  
    var d1 Describer
    if d1 == nil {
        fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1)
    }
}

在上面的程序中,d1nil

程序输出如下:

d1 is nil and has type <nil> value <nil>  

调用 nil接口 的方法,程序会奔溃,因为 nil接口 没有底层类型和数值。

package main

type Describer interface {  
    Describe()
}

func main() {  
    var d1 Describer
    d1.Describe()
}

运行这个程序,程序会奔溃,并输出以下信息:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527]"

这就是接口了~

祝你天天有好吃的~


原作者留言

优质内容来之不易,您可以通过该 链接 为我捐赠。


最后

感谢原作者的优质内容。

欢迎指出文中的任何错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值