建站都需要什么,石家庄规划,华为手表网站,网站策划书的撰写流程是什么文章目录 接口接口接口类型为什么要使用接口接口的定义实现接口的条件接口类型变量值接收者和指针接收者实现接口的区别值接收者实现接口指针接收者实现接口下面的代码是一个比较好的面试题 类型与接口的关系一个类型实现多个接口多个类型实现同一接口接口嵌套 空接口空接口的定… 文章目录 接口接口接口类型为什么要使用接口接口的定义实现接口的条件接口类型变量值接收者和指针接收者实现接口的区别值接收者实现接口指针接收者实现接口下面的代码是一个比较好的面试题 类型与接口的关系一个类型实现多个接口多个类型实现同一接口接口嵌套 空接口空接口的定义空接口的应用空接口作为函数的参数空接口作为map的值 类型断言接口值 接口
接口interface定义了一个对象的行为规范只定义规范不实现由具体的对象来实现规范的细节。
接口
接口类型
在Go语言中接口interface是一种类型一种抽象的类型。
interface是一组method的集合是duck-type programming的一种体现。接口做的事情就像是定义一个协议规则只要一台机器有洗衣服和甩干的功能我就称它为洗衣机。不关心属性数据只关心行为方法。
为了保护你的Go语言职业生涯请牢记接口interface是一种类型。
为什么要使用接口
type Cat struct{}func (c Cat) Say() string { return 喵喵喵 }type Dog struct{}func (d Dog) Say() string { return 汪汪汪 }func main() {c : Cat{}fmt.Println(猫:, c.Say())d : Dog{}fmt.Println(狗:, d.Say())
}上面的代码中定义了猫和狗然后它们都会叫你会发现main函数中明显有重复的代码如果我们后续再加上猪、青蛙等动物的话我们的代码还会一直重复下去。那我们能不能把它们当成“能叫的动物”来处理呢
像类似的例子在我们编程过程中会经常遇到
比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付我们能不能把它们当成“支付方式”来处理呢
比如三角形四边形圆形都能计算周长和面积我们能不能把它们当成“图形”来处理呢
比如销售、行政、程序员都能计算月薪我们能不能把他们当成“员工”来处理呢
Go语言中为了解决类似上面的问题就设计了接口这个概念。接口区别于我们之前所有的具体类型接口是一种抽象的类型。当你看到一个接口类型的值时你不知道它是什么唯一知道的是通过它的方法能做什么。
接口的定义
Go语言提倡面向接口编程。 接口是一个或多个方法签名的集合。任何类型的方法集中只要拥有该接口对应的全部方法签名。就表示它 实现 了该接口无须在该类型上显式声明实现了哪个接口。这称为Structural Typing。所谓对应方法是指有相同名称、参数列表 (不包括参数名) 以及返回值。当然该类型还可以有其他方法。接口只有方法声明没有实现没有数据字段。接口可以匿名嵌入其他接口或嵌入到结构中。对象赋值给接口时会发生拷贝而接口内部存储的是指向这个复制品的指针既无法修改复制品的状态也无法获取指针。只有当接口存储的类型和对象都为nil时接口才等于nil。接口调用不会做receiver的自动转换。接口同样支持匿名字段方法。接口也可实现类似OOP中的多态。空接口可以作为任何类型数据的容器。一个类型可实现多个接口。接口命名习惯以 er 结尾。 每个接口由数个方法组成接口的定义格式如下 type 接口类型名 interface{方法名1( 参数列表1 ) 返回值列表1方法名2( 参数列表2 ) 返回值列表2…} 其中 1.接口名使用type将接口定义为自定义的类型名。Go语言的接口在命名时一般会在单词后面添加er如有写操作的接口叫Writer有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。2.方法名当方法名首字母是大写且这个接口类型名首字母也是大写时这个方法可以被接口所在的包package之外的代码访问。3.参数列表、返回值列表参数列表和返回值列表中的参数变量名可以省略。 举个例子
type writer interface{Write([]byte) error
} 当你看到这个接口类型的值时你不知道它是什么唯一知道的就是可以通过它的Write方法来做一些事情。
实现接口的条件
一个对象只要全部实现了接口中的方法那么就实现了这个接口。换句话说接口就是一个需要实现的方法列表。
我们来定义一个Sayer接口
// Sayer 接口
type Sayer interface {say()
} 定义dog和cat两个结构体
type dog struct {}type cat struct {} 因为Sayer接口里只有一个say方法所以我们只需要给dog和cat 分别实现say方法就可以实现Sayer接口了。
// dog实现了Sayer接口
func (d dog) say() {fmt.Println(汪汪汪)
}// cat实现了Sayer接口
func (c cat) say() {fmt.Println(喵喵喵)
} 接口的实现就是这么简单只要实现了接口中的所有方法就实现了这个接口。
接口类型变量
那实现了接口有什么用呢
接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中Sayer类型的变量能够存储dog和cat类型的变量。
func main() {var x Sayer // 声明一个Sayer类型的变量xa : cat{} // 实例化一个catb : dog{} // 实例化一个dogx a // 可以把cat实例直接赋值给xx.say() // 喵喵喵x b // 可以把dog实例直接赋值给xx.say() // 汪汪汪
} 值接收者和指针接收者实现接口的区别
使用值接收者实现接口和使用指针接收者实现接口有什么区别呢接下来我们通过一个例子看一下其中的区别。
我们有一个Mover接口和一个dog结构体。
type Mover interface {move()
}type dog struct {} 值接收者实现接口
func (d dog) move() {fmt.Println(狗会动)
} 此时实现接口的是dog类型
func main() {var x Movervar wangcai dog{} // 旺财是dog类型x wangcai // x可以接收dog类型var fugui dog{} // 富贵是*dog类型x fugui // x可以接收*dog类型x.move()
} 从上面的代码中我们可以发现使用值接收者实现接口之后不管是dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖dog指针fugui内部会自动求值*fugui。
指针接收者实现接口
同样的代码我们再来测试一下使用指针接收者有什么区别
func (d *dog) move() {fmt.Println(狗会动)
}
func main() {var x Movervar wangcai dog{} // 旺财是dog类型x wangcai // x不可以接收dog类型var fugui dog{} // 富贵是*dog类型x fugui // x可以接收*dog类型
} 此时实现Mover接口的是*dog类型所以不能给x传入dog类型的wangcai此时x只能存储*dog类型的值。
下面的代码是一个比较好的面试题
请问下面的代码是否能通过编译
type People interface {Speak(string) string
}type Student struct{}func (stu *Student) Speak(think string) (talk string) {if think sb {talk 你是个大帅比} else {talk 您好}return
}func main() {var peo People Student{}think : bitchfmt.Println(peo.Speak(think))
}类型与接口的关系
一个类型实现多个接口
一个类型可以同时实现多个接口而接口间彼此独立不知道对方的实现。 例如狗可以叫也可以动。我们就分别定义Sayer接口和Mover接口如下 Mover接口。
// Sayer 接口
type Sayer interface {say()
}// Mover 接口
type Mover interface {move()
} dog既可以实现Sayer接口也可以实现Mover接口。
type dog struct {name string
}// 实现Sayer接口
func (d dog) say() {fmt.Printf(%s会叫汪汪汪\n, d.name)
}// 实现Mover接口
func (d dog) move() {fmt.Printf(%s会动\n, d.name)
}func main() {var x Sayervar y Movervar a dog{name: 旺财}x ay ax.say()y.move()
} 多个类型实现同一接口
Go语言中不同的类型还可以实现同一接口 首先我们定义一个Mover接口它要求必须由一个move方法。
// Mover 接口
type Mover interface {move()
} 例如狗可以动汽车也可以动可以使用如下代码实现这个关系
type dog struct {name string
}type car struct {brand string
}// dog类型实现Mover接口
func (d dog) move() {fmt.Printf(%s会跑\n, d.name)
}// car类型实现Mover接口
func (c car) move() {fmt.Printf(%s速度70迈\n, c.brand)
}这个时候我们在代码中就可以把狗和汽车当成一个会动的物体来处理了不再需要关注它们具体是什么只需要调用它们的move方法就可以了。
func main() {var x Movervar a dog{name: 旺财}var b car{brand: 保时捷}x ax.move()x bx.move()
} 上面的代码执行结果如下 旺财会跑保时捷速度70迈 并且一个接口的方法不一定需要由一个类型完全实现接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
// 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(洗刷刷)
}接口嵌套
接口与接口间可以通过嵌套创造出新的接口。
// Sayer 接口
type Sayer interface {say()
}// Mover 接口
type Mover interface {move()
}// 接口嵌套
type animal interface {SayerMover
} 嵌套得到的接口的使用与普通接口一样这里我们让cat实现animal接口
type cat struct {name string
}func (c cat) say() {fmt.Println(喵喵喵)
}func (c cat) move() {fmt.Println(猫会动)
}func main() {var x animalx cat{name: 花花}x.move()x.say()
} 空接口
空接口的定义
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() {// 定义一个空接口xvar x interface{}s : pprof.cnx sfmt.Printf(type:%T value:%v\n, x, x)i : 100x ifmt.Printf(type:%T value:%v\n, x, x)b : truex bfmt.Printf(type:%T value:%v\n, x, x)
}空接口的应用
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数
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] 18studentInfo[married] falsefmt.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.cnv, 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 stringvalue 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语言中的使用十分广泛。
关于接口需要注意的是只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口那样只会增加不必要的抽象导致不必要的运行时损耗。