导言
- 原文链接: 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
,而不是 *Address
,Address
类型 并没有实现 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())
}
在第 7
、11
行,我们分别声明了 SalaryCalculator
、LeaveCalculator
接口,它们分别具有 DisplaySalary
、CalculateLeavesLeft
方法。
在第 15
行,我们定义了 Employee
结构。
在第 24
、28
行,我们为Employee
结构,分别添加了 DisplaySalary
、CalculateLeavesLeft
方法。此时 Employee
实现了 SalaryCalculator
、LeaveCalculator
接口。
在第 41
行,我们将 e
分配给了 SalaryCalculator
接口类型 的 变量s
。在第 43
行,我们将 e
分配给了 LeaveCalculator
接口类型 的 变量l
。由于 e
的类型是 Employee
,该类型实现了 SalaryCalculator
、LeaveCalculator
接口,所以上面的操作都是合法的。
程序输出如下:
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
行,通过内嵌 SalaryCalculator
、LeaveCalculator
接口,我们创建了 EmployeeOperations
接口 。
任何实现了 SalaryCalculator
、LeaveCalculator
接口 的类型,都实现了 EmployeeOperations
接口。
因为 Employee
结构 实现了 SalaryCalculator
、LeaveCalculator
接口,所以它实现了 EmployeeOperations
接口。
在第 46
行,变量e
被分配给了 变量empOp
,其中 e
的类型为 Emoloyee
结构,empOp
的类型为 EmployeeOperations
接口。
在第 47
、48
行,我们分别调用了 empOp
变量 的 DisplaySalary
、CalculateLeavesLeft
方法。
程序输出如下:
Naveen Ramanathan has salary $5200
Leaves left = 25
接口的零值
接口的零值为 nil
。nil
接口 的 具体类型、底层数值 都为 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)
}
}
在上面的程序中,d1
为 nil
。
程序输出如下:
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]"
这就是接口了~
祝你天天有好吃的~
原作者留言
优质内容来之不易,您可以通过该 链接 为我捐赠。
最后
感谢原作者的优质内容。
欢迎指出文中的任何错误。