接口
接口的基本概念和使用
- Go语言中的接口和现实生活中的USB3.0接口很像, 用于定义某种规范
现实生活中, 只要生产厂家按照USB3.0接口的标准来生产, 那么无论什么品牌的电脑, 都可以使用
Go语言中, 只要使用者按照我们接口定义的标准来实现, 那么无论谁实现的我们都可以使用
- 接口的作用?
- 在Go语言中, 接口专门用于定义函数的声明,也就是规定函数的形参, 函数的返回值, 函数的名称
- 接口的定义格式
type 接口名称 interface{ 函数的声明 }
-
接口的注意点
- 接口是虚的, 只有方法的声明, 没有方法的实现 (就和制定USB3.0标准的组织一样, 只负责制定标准, 不负责生产)
- 接口中声明的方法, 只能通过和某种数据类型绑定的方法来实现, 不能通过函数来实现
- 在Go语言中, 只要某个数据类型实现了接口中声明的
所有
方法, 那么就说这个数据类型实现了这个接口
- 只要一个数据类型实现了某个接口, 那么就可以使用这个接口类型的变量来保存这个类型的数据
- 只要一个数据类型实现了某个接口, 那么保存这个类型的数据之后, 就可以使用接口类型变量调用接口中的方法
package main
import (
"fmt"
)
//1.定义一个接口
type USB interface {
//制定方法标准
//名称叫做start,必须传入string类型的参数,并且没有返回值
start(name string)
//名称叫做end,必须传入string类型的参数,并且没有返回值
end(name string)
}
//2.定义一个结构体
type Computer struct {
}
//定义一个手机结构体
type Phone struct {
}
//3.实现接口中声明的方法
//Computer数据类型实现了USB接口,因为Computer实现了接口中所有的方法
func (Computer)start(name string) {
fmt.Println(name,"启动了")
}
func (Computer)end(name string) {
fmt.Println(name, "关闭了")
}
//实现接口中声明的方法
func (Phone)start(name string) {
fmt.Println(name, "启动了")
}
func (Phone)end(name string) {
fmt.Println(name, "关闭了")
}
func main() {
/*
1.什么是接口?
Go语言中的接口和现实生活中的USB3.0接口很像, 用于定义某种规范
现实生活中, 只要生产厂家按照USB3.0接口的标准来生产, 那么无论什么品牌的电脑, 都可以使用
Go语言中, 只要使用者按照我们接口定义的标准来实现, 那么无论谁实现的我们都可以使用
2.接口的作用?
在Go语言中, 接口专门用于定义函数的声明
也就是规定函数的形参, 函数的返回值, 函数的名称
3.接口的定义格式
type 接口名称 interface{
函数的声明
}
4.注意点:
4.1接口是虚的, 只有方法的声明, 没有方法的实现 (就和制定USB3.0标准的组织一样, 只负责制定标准, 不负责生产)
4.2接口中声明的方法, 只能通过和某种数据类型绑定的方法来实现, 不能通过函数来实现 (就和现实生活中只能由富士康, 等具备生产能力的公司来生成一样, 这里和和某种数据类型绑定的方法就是局部生产能力的公司, 函数就是不具备生产能力的公司)
4.3在Go语言中, 只要某个数据类型实现了接口中声明的`所有`方法, 那么就说这个数据类型实现了这个接口
4.4只要一个数据类型实现了某个接口, 那么就可以使用这个接口类型的变量来保存这个类型的数据
4.5只要一个数据类型实现了某个接口, 那么保存这个类型的数据之后, 就可以使用接口类型变量调用接口中的方法
*/
//定义一个结构体变量
var comp = Computer{}
//定义接口类型变量
var in USB
in = comp
in.start("苹果电脑")
in.end("苹果电脑")
//使用结构体变量保存Phone的数据
in = Phone{}
in.start("小米手机")
in.end("小米手机")
//接口是函数更通用
//不使用接口每次想开关一种电器都要重新定义一个函数,使用接口只需要定义一个函数即可
comp := Computer{}
ph := Phone{}
change(comp,"惠普电脑")
change(ph, "苹果手机")
}
//定义一个函数开关某种电器
func change(in USB,name string) {
in.start(name)
in.end(name)
}
接口的好处
- 接口的重点知识点
- 如何定义一个接口
type USB interface { 函数名称(形参列表)(返回值列表) 函数名称(形参列表)(返回值列表) }
- 如何实现一个接口
- Go语言中实现一个接口, 不需要做额外的声明, 只要某种数据类型绑定了所有接口中声明的方法就是实现了这个接口
- 实现接口之后的特性
- 只要某种数据类型实现了接口, 那么就可以使用接口变量保存这种数据类型
- 只要某种数据类型实现了接口, 那么就可以使用接口变量调用接口中声明的方法
package main
import "fmt"
//1.定义一个接口
type USB interface {
start(name string)
end(name string)
}
//2.定义结构体
type Computer struct {
}
//手机的结构体类型
type Phone struct {
}
func (Computer)start(name string) {
fmt.Println(name, "开启了")
}
func (Computer)end(name string) {
fmt.Println(name, "关闭了")
}
func (Phone)start(name string) {
fmt.Println(name, "开启了")
}
func (Phone)end(name string) {
fmt.Println(name, "关闭了")
}
func main() {
/*
接口重点掌握的知识:
1.如何定义一个接口
type USB interface {
函数名称(形参列表)(返回值列表)
函数名称(形参列表)(返回值列表)
}
2.如何实现一个接口
Go语言中实现一个接口, 不需要做额外的声明, 只要某种数据类型绑定了所有接口中声明的方法就是实现了这个接口
3.实现接口之后的特性
只要某种数据类型实现了接口, 那么就可以使用接口变量保存这种数据类型
只要某种数据类型实现了接口, 那么就可以使用接口变量调用接口中声明的方法
*/
//定义接口类型变量
var in USB
in = Computer{}
in.start("惠普电脑")
in.end("惠普电脑")
com := Computer{}
open(com,"小米电脑")
comp := Computer{}
ph := Phone{}
open(comp, "小米")
open(ph, "苹果手机")
}
//只要有一个类型的电器就要封装一个函数,十分麻烦
func open(comp Computer, name string) {
comp.start(name)
comp.end(name)
}
//定义一个函数可以开启和关闭所有电器
func open(in USB, name string) {
in.start(name)
in.end(name)
}
接口的注意点
- 接口中只能有方法的声明, 不能有方法的实现
- 接口中只能有方法的声明, 不能有变量的声明
- 只有实现了接口中声明的所有方法, 才算实现了接口, 才能使用接口变量保存
- 在实现接口的时候, 方法名称,形参列表,返回值列表必须一模一样
- 接口和结构体一样, 可以嵌套
- 接口和结构体一样, 嵌套时不能嵌套自己(自己搞自己)
- 可以将超集接口变量赋值给子集接口变量,不可以将子集接口变量赋值给超集接口变量(无论实际的数据类型是否已经实现了超集的所有方法)
- 接口中不能出现同名的方法声明
package main
import "fmt"
//type USB interface {
// start(name string)
// //num := 20 //不能有变量定义
// end(name string)
//}
//子集接口
type OPEN interface {
start(name string)
}
//超集接口
type END interface {
OPEN //接口嵌套
end(name string)
}
type Computer struct {
}
//只实现接口中一个方法
func (Computer)start(name string) {
fmt.Println(name, "开启了")
}
//会报错形参和接口声明的形参类型不一致
func (Computer)end(name string) {
fmt.Println(name, "关闭了")
}
func main() {
/*
注意点:
1.接口中只能有方法的声明, 不能有方法的实现
2.接口中只能有方法的声明, 不能有变量的声明
3.只有实现了接口中声明的所有方法, 才算实现了接口, 才能使用接口变量保存
4.在实现接口的时候, 方法名称,形参列表,返回值列表必须一模一样
5.接口和结构体一样, 可以嵌套
6.接口和结构体一样, 嵌套时不能嵌套自己(自己搞自己)
7.可以将超集接口变量赋值给子集接口变量,不可以将子集接口变量赋值给超集接口变量(无论实际的数据类型是否已经实现了超集的所有方法)
8.接口中不能出现同名的方法声明
*/
//var in USB
//in = Computer{} 会报错
//in.start("电脑")
//子集接口和超集接口变量相互赋值
//定义子集接口变量
var op OPEN
//定义超集接口变量
var en END = Computer{}
//将超集接口变量赋值给子集接口变量
op = en
op.start("电脑")
//将子集接口变量赋值给超集接口变量
//定义子集接口变量
var op OPEN = Computer{}
//定义超集接口变量
var en END
en = op //会报错
}
空接口
- Go语言中的空接口, 相当于其他语言的Object类型
Go语言中的空接口, 可以充当任何类型
- 空接口类型的格式: `interface{}
- 正是因为有了空接口类型, 所以也可以让数组和字典保存不同类型的数据
面试回答:
数组和字典一般用于保存相同类型的数据, 而结构体用于保存不同类型的数据
但是也可以通过空接口类型来实现让数组和字典保存不同类型的数据`
-
注意点
: 空接口类型和普通的数据类型, 在使用的时候还是有很大区别的
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
/*
1.什么是空接口?
Go语言中的空接口, 相当于其他语言的Object类型
Go语言中的空接口, 可以充当任何类型
2.空接口类型的格式
interface{}
3.正是因为有了空接口类型, 所以也可以让数组和字典保存不同类型的数据
面试回答: 数组和字典一般用于保存相同类型的数据, 而结构体用于保存不同类型的数据
但是也可以通过空接口类型来实现让数组和字典保存不同类型的数据
注意点: 空接口类型和普通的数据类型, 在使用的时候还是有很大区别的
*/
//1.空接口保存不同的数据类型
//定义一个空接口
var value interface{}
//保存整数
value = 10
fmt.Println(value)
//保存浮点数
value = 3.15
fmt.Println(value)
//保存布尔类型
value = false
fmt.Println(value)
//保存字符类型
value = 'T'
fmt.Println(value)
//保存字符串类型
value = "wjh"
fmt.Println(value)
//保存数组类型
value = [3]int{1,3,5}
fmt.Println(value)
//保存切片类型
value = []int{1,3,5}
fmt.Println(value)
//保存字典类型
value = map[string]string{"name" : "lnj"}
fmt.Println(value)
//保存结构体类型
value = Person{"niuzi", 20}
fmt.Println(value)
//2.空接口使数组保存不同的数据类型
var arr [3]interface{}
arr[0] = 10
arr[1] = "lnj"
arr[2] = 30.5
fmt.Println(arr)
//3.空接口使切片保存不同的数据类型
var sce []interface{}
//初始化切片
sce = make([]interface{}, 3)
sce[0] = 10
sce[1] = "wjh"
sce[2] = false
fmt.Println(sce)
//4.空接口使字典类型保存不同的数据类型的数据
dict := map[interface{}]interface{}{10 : "lnj", false : 'T'}
fmt.Println(dict)
}
接口的转换
- 如果结构体实现了某个接口, 那么就可以使用接口类型来保存结构体变量
- 如果利用接口类型变量保存了实现了接口的结构体, 那么该变量只能访问接口中的方法, 不能访问结构体中的特有方法, 以及结构体中的属性
var in USB
in = Computer{"电脑"}
//如果结构体实现了某个接口, 那么就可以使用接口类型来保存结构体变量
in.start()
//如果利用接口类型变量保存了实现了接口的结构体, 那么该变量只能访问接口中的方法,
//不能访问结构体中的特有方法, 以及结构体中的属性
//in.end()
//fmt.Println(in.name)
//var in USB
//in = Computer{"电脑"}
- 如果利用接口类型变量保存了实现了接口的结构体,想要访问结构体中特有的方法和属性, 那么必须进行类型转换, 将接口类型转换为结构体类型
//将接口类型转换为结构体类型
//第一种方式: 类型断言
//格式:结构体变量名称, ok := 接口类型变量名.(结构体类型)
//com,ok := in.(Computer)
//fmt.Println(com)
//fmt.Println(ok)
//fmt.Printf("%T\n", com)
if com,ok := in.(Computer);ok {
fmt.Println(ok)
fmt.Println(com.name) //可以访问结构体自身的属性
com.end() //可以访问结构体特有的方法
}
//第二种方式: 类型断言
//会将接口类型变量转换为对应的原始类型之后赋值给结构体变量
//格式: 结构体变量 := 接口变量名.(type)
//使用switch语句判断
switch com := in.(type){
case Computer:
fmt.Println(com.name)
com.end()
default:
fmt.Println("不是Computer类型")
}
//这种方式一般的应用场景
cm := Computer{"笔记本"}
sce := []interface{}{10, "haha", cm,3.14,false}
for key, value := range sce {
switch v := value.(type) {
case int:
fmt.Println("第",key,"个","是int数据类型")
case string:
fmt.Println("第",key,"个","是string数据类型")
case float64:
fmt.Println("第",key,"个","是float64数据类型")
case bool:
fmt.Println("第",key,"个","是bool数据类型")
case Computer:
fmt.Println("第",key,"个","是Computer数据类型")
v.end()
}
}
- 除了可以将接口类型转换为具体类型以外, 还可以将抽象接口类型转换为具体的接口类型
//定义一个抽象接口
var in interface{}
//将结构体保存在抽象接口中
in = Computer{"电脑"}
//抽象接口不能调用具体接口中的方法
//in.start()
//想要调用具体接口中的方法需要将抽象接口转换成具体接口
//if value,ok := in.(USB); ok{
// value.start()
//}
//还可以将抽象接口转换成结构体类型
if v,ok := in.(Computer); ok {
v.start()
v.end()
fmt.Println(v.name)
}