接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。
为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
Go 中的接口是满足隐式实现的(这一点是非常坑人的,建议阅读 Go语言令人厌恶的设计)。 Go 不提供用于实现接口的关键字。
package main
import (
"fmt"
"math"
)
func main() {
var s Shape = Square{3}
printInformation(s)
c := Circle{6}
printInformation(c)
}
//打印面积和周长
func printInformation(s Shape) {
fmt.Printf("%T\n", s)
fmt.Println("Area: ", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
fmt.Println()
}
//---Shape 形状---
type Shape interface {
//周长
Perimeter() float64
//面积
Area() float64
}
//---Square 正方形---
type Square struct {
size float64
}
func (s Square) Area() float64 {
return s.size * s.size
}
func (s Square) Perimeter() float64 {
return s.size * 4
}
//---Circle 圆形---
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.radius
}
输出
main.Square
Area: 9
Perimeter: 12
main.Circle
Area: 113.0973
Perimeter: 37.6991
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() {
// 定义一个空接口x
var x interface{}
s := "pprof.cn"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
使用空接口实现可以保存任意值的字典。
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "李白"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
我们来看一个具体的例子:
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
请看下图分解:
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
x:表示类型为interface{}的变量
T:表示断言x可能是的类型。
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。
举个例子:
func main() {
var x interface{}
x = "pprof.cn"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}
上面的示例中如果要断言多次就需要写多个if判断,这个时候我们可以使用switch语句来实现:
func justifyType(x interface{}) {
switch v := x.(type) {
case string:
fmt.Printf("x is a string,value is %v\n", v)
case int:
fmt.Printf("x is a int is %v\n", v)
case bool:
fmt.Printf("x is a bool is %v\n", v)
default:
fmt.Println("unsupport type!")
}
}
因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。
关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。
类型断言也可以判断指针类型
package main
import "fmt"
func main() {
var n1 int32 = 30
var n2 string = "tomcat"
n3 := false
stu1 := Student{}
stu2 := &Student{}
justifyType(n1,n2,n3,stu1,stu2)
}
type Student struct {
}
func justifyType(items ...interface{}) {
for index, x := range items {
switch v := x.(type) {
case string:
fmt.Printf("第%v个参数是 string 类型,值是 %v\n",index,v)
case int:
fmt.Printf("第%v个参数是 int 类型,值是 %v\n",index,v)
case bool:
fmt.Printf("第%v个参数是 bool 类型,值是 %v\n",index,v)
case Student:
fmt.Printf("第%v个参数是 Student 类型,值是 %v\n",index,v)
case *Student:
fmt.Printf("第%v个参数是 *Student 类型,值是 %v\n",index,v)
default:
fmt.Println("unsupport type!")
}
}
}
package main
import "fmt"
func main() {
var i interface{} = "Hello, World"
str, ok := i.(string)
if ok {
fmt.Printf("'%s' is a string\n", str)
} else {
fmt.Println("conversion failed")
}
}