golang 接口
接口
学习 Go 语言中的接口时,以下是你需要关注的主要概念和知识点:
在Go语言中使用隐式声明的方式实现接口。只要一个类型实现了接口中规定的所有方法,那么它就实现了这个接口
1. 接口定义
接口是一种类型,定义了一组方法的集合。接口定义的方法不包含实现,只有方法签名。
示例:
Go
type Animal interface {
Eat()
Speak()
}
解释:
Animal
是一个接口类型,定义了两个方法:Eat()
和Speak()
。- 接口中的方法没有具体实现,只有方法签名。
2. 接口实现
类型通过实现接口中的所有方法来满足接口。一个类型可以实现多个接口。
示例:
Go
type Dog struct {
Name string
}
func (d *Dog) Eat() {
fmt.Println("Dog", d.Name, "is eating.")
}
func (d *Dog) Speak() {
fmt.Println("Dog", d.Name, "says woof!")
}
func main() {
d := Dog{Name: "Fido"}
d.Eat()
d.Speak()
}
解释:
Dog
类型实现了Animal
接口中的两个方法:Eat()
和Speak()
。- 因此,
Dog
类型的值可以赋值给Animal
类型变量。
3. 接口嵌套
接口可以嵌套,形成新的接口。
示例:
Go
type Bird interface {
Fly()
}
type Animal interface {
Eat()
Speak()
}
type FlyingAnimal interface {
Animal
Bird
}
解释:
FlyingAnimal
接口嵌套了Animal
和Bird
接口。- 因此,
FlyingAnimal
接口包含了Animal
和Bird
接口中的所有方法。
4. 空接口 interface{}
空接口没有任何方法,因此可以用来表示任意类型。空接口在处理未知类型的值时很有用。
示例:
Go
var x interface{}
x = 10
fmt.Println(x) // 输出: 10
x = "hello"
fmt.Println(x) // 输出: hello
解释:
x
是一个空接口类型的变量。- 可以将任何类型的值赋值给
x
变量。
5. 类型断言和类型切换
类型断言
使用类型断言检查一个接口值的具体类型。
示例:
Go
x := 10
if v, ok := x.(int); ok {
fmt.Println("x is an int with value", v)
} else {
fmt.Println("x is not an int")
}
解释:
- 使用
type assertion
语句检查x
的类型是否为int
。 - 如果类型断言成功,则
v
变量会存储x
的值,ok
变量会设置为true
。
类型切换
使用类型切换进行多个类型的匹配。
示例:
Go
x := 10
switch x.(type) {
case int:
fmt.Println("x is an int")
case string:
fmt.Println("x is a string")
default:
fmt.Println("x is of unknown type")
}
解释:
- 使用
switch
语句根据x
的类型进行不同的处理。
6. 接口值
接口值是接口类型的变量。接口值有两个组成部分:动态类型和动态值。
- 动态类型:接口值实际所持有的值的类型。
- 动态值:接口值实际所持有的值。
示例:
Go
var x interface{}
x = 10
// 动态类型为 int
fmt.Println(reflect.TypeOf(x)) // 输出: int
// 动态值为 10
fmt.Println(x) // 输出: 10
解释:
x
是一个空接口类型的变量。- 将
10
赋值给x
后,x
的动态类型为int
,动态值为10
。
7. 空接口和类型断言的应用
空接口常用于处理未知类型的数据。使用类型断言将接口值转换为具体类型。
示例:
Go
func printValue(x interface{}) {
switch x.(type
8. 错误接口 error
error
是一个内置的接口类型,用于表示错误。自定义类型可以实现 error
接口来表示特定类型的错误。
示例:
Go
type MyError struct {
Message string
}
func (e *MyError) Error() string {
return e.Message
}
func main() {
err := MyError{Message: "This is an error"}
fmt.Println(err) // 输出: This is an error
}
解释:
MyError
类型实现了error
接口。Error()
方法返回错误信息。
9. 接口的重点
- 掌握如何定义接口及其方法。
- 了解接口的实现和类型之间的关系。
- 掌握类型断言和类型切换的使用。
10. 接口的高级应用
- 空接口的应用场景,如函数接受任意类型的参数。
- 类型断言和类型切换的实际应用。
面向接口编程
例子
下面是一个简单的 Go 语言面向接口编程的例子。在这个例子中,我们定义了一个接口 Shape
,并实现了两个结构体 Circle
和 Rectangle
,它们都实现了 Shape
接口。然后,通过一个通用的函数 printArea
,我们可以打印不同形状的面积,而无需关心具体的类型。
package main
import (
"fmt"
"math"
)
// Shape 接口定义了计算面积的方法
type Shape interface {
Area() float64
}
// Circle 结构体实现了 Shape 接口
type Circle struct {
Radius float64
}
// Rectangle 结构体实现了 Shape 接口
type Rectangle struct {
Width float64
Height float64
}
// Area 计算圆的面积
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// Area 计算矩形的面积
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// printArea 打印任何实现了 Shape 接口的类型的面积
func printArea(s Shape) {
fmt.Printf("Shape area: %v\n", s.Area())
}
func main() {
// 创建圆和矩形实例
circle := Circle{Radius: 5}
rectangle := Rectangle{Width: 4, Height: 6}
// 通过接口调用 printArea 函数,无需关心具体类型
printArea(circle)
printArea(rectangle)
}
这个例子演示了如何使用接口来实现面向接口编程。通过定义一个通用的接口和使用这个接口的通用函数,我们可以在不修改函数的情况下轻松地扩展支持新的形状。这种方式提高了代码的灵活性和可扩展性。
1. 接收者类型对比
特性 | 值接收者(Value Receiver) | 指针接收者(Pointer Receiver) |
---|---|---|
修改接收者中的字段 | 不会修改原始值 | 会修改原始值 |
对象复制 | 会发生对象复制 | 不发生对象复制 |
传递对象 | 传递对象的副本 | 传递对象的引用 |
使用场景 | 适用于简单对象或不需要修改对象状态 | 适用于复杂对象或需要修改对象状态 |
下面是一个简单的示例,演示了值接收者和指针接收者的异同:
package main
import "fmt"
// Point 结构体定义
type Point struct {
X, Y int
}
// MethodWithPointerReceiver 使用指针接收者的方法
func (p *Point) MethodWithPointerReceiver() {
p.X = 10
p.Y = 20
}
// MethodWithValueReceiver 使用值接收者的方法
func (p Point) MethodWithValueReceiver() {
p.X = 30
p.Y = 40
}
func main() {
// 创建 Point 结构体实例
point := Point{X: 5, Y: 15}
// 使用指针接收者的方法,修改原始值
point.MethodWithPointerReceiver()
fmt.Println("After MethodWithPointerReceiver:", point)
// 使用值接收者的方法,不修改原始值
point.MethodWithValueReceiver()
fmt.Println("After MethodWithValueReceiver:", point)
}
在上面的例子中,MethodWithPointerReceiver
使用了指针接收者,它能够修改原始值;而 MethodWithValueReceiver
使用了值接收者,它只修改了接收者的副本而不影响原始值。
一个接口的所有方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
// WashingMachine 洗衣机
type WashingMachine interface {
wash()
dry()
}
// 甩干器
type dryer struct{}
// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {
fmt.Println("甩一甩")
}
// 海尔洗衣机
type haier struct {
dryer //嵌入甩干器
}
// 实现WashingMachine接口的wash()方法
func (h haier) wash() {
fmt.Println("洗刷刷")
}
2. 接口组合
接口与接口之间可以通过互相嵌套形成新的接口类型,例如Go标准库io
源码中就有很多接口之间互相组合的示例。
// src/io/io.go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// ReadWriter 是组合Reader接口和Writer接口形成的新接口类型
type ReadWriter interface {
Reader
Writer
}
// ReadCloser 是组合Reader接口和Closer接口形成的新接口类型
type ReadCloser interface {
Reader
Closer
}
// WriteCloser 是组合Writer接口和Closer接口形成的新接口类型
type WriteCloser interface {
Writer
Closer
}
对于这种由多个接口类型组合形成的新接口类型,同样只需要实现新接口类型中规定的所有方法就算实现了该接口类型。
3. 间接实现接口
在 Go 语言中,是不支持直接嵌入(Embed)接口到结构体中的。与嵌入结构体不同,Go 语言不允许将接口直接嵌入到结构体。
但是,你可以在结构体中嵌入实现了某个接口的类型的匿名字段。这样,结构体就继承了该类型的方法,相当于间接实现了接口。让我们通过一个示例来说明:
package main
import "fmt"
// 定义一个接口
type MyInterface interface {
Method1() int
}
// 实现接口的类型
type MyType struct {
// 类型的字段
}
// MyType 实现 MyInterface 接口的方法
func (mt MyType) Method1() int {
// 具体实现
return 42
}
// 结构体中嵌入实现了接口的类型的匿名字段
type MyStruct struct {
MyType // 匿名字段,类型为 MyType
// 结构体的其他字段
}
func main() {
// 创建结构体实例
myStruct := MyStruct{}
// 调用结构体中继承的接口方法
result := myStruct.Method1()
fmt.Println(result) // 输出:42
}
在上述示例中,MyStruct
结构体嵌入了 MyType
类型的匿名字段,而 MyType
实现了 MyInterface
接口的方法。因此,MyStruct
结构体间接实现了 MyInterface
接口,并可以调用接口中定义的方法。
4. 空接口
类似于泛型
空接口的应用
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
空接口作为map的值
使用空接口实现可以保存任意值的字典。
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "沙河娜扎"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
本文来自博客园,作者:{zhongweiLeex},转载请注明原文链接:{https://www.cnblogs.com/lzw6/}