dede手机网站模板修改,我的世界怎么做神器官方网站,建设银行官方个人网站,用什么做php网站Interface整理 文章目录 Interface整理接口嵌套接口类型断言类型判断 type-switch使用方法集与接口空接口实例 接口赋值给接口 接口是一种契约#xff0c;实现类型必须满足它#xff0c;它描述了类型的行为#xff0c;规定类型可以做什么。接口彻底将类型能做什么#xff0…Interface整理 文章目录 Interface整理接口嵌套接口类型断言类型判断 type-switch使用方法集与接口空接口实例 接口赋值给接口 接口是一种契约实现类型必须满足它它描述了类型的行为规定类型可以做什么。接口彻底将类型能做什么以及如何做分离开来使得相同接口的变量在不同的时刻表现出不同的行为这就是多态的本质。 编写参数是接口变量的函数这使得它们更具有一般性。 使用接口使代码更具有普适性。 最近在学Go当中的接口学的有点云里雾里 这个interface和Java的也太不像了我们先来看看Java当中的接口是怎么用的
首先我们先定义一个接口
public interface Study { //使用interface表示这是一个接口void study(); //接口中只能定义访问权限为public抽象方法其中public和abstract关键字可以省略
}之后我们用关键字继承
public class Student extends Person implements Study { //使用implements关键字来实现接口public Student(String name, int age, String sex) {super(name, age, sex, 学生);}Overridepublic void study() { //实现接口时同样需要将接口中所有的抽象方法全部实现System.out.println(我会学习);}
}public class Teacher extends Person implements Study {protected Teacher(String name, int age, String sex) {super(name, age, sex, 教师);}Overridepublic void study() {System.out.println(我会加倍学习);}这样一个显示继承的方式非常清晰明了接下来看看Go里面的接口
type Namer interface {Method1(param_list) return_typeMethod2(param_list) return_type...
}这样一看没有什么很大的区别都需要先声明一个接口但是不使用接下来看看接口的实现
package mainimport fmttype Shaper interface {Area() float32
}type Square struct {side float32
}func (sq *Square) Area() float32 {return sq.side * sq.side
}func main() {sq1 : new(Square)sq1.side 5var areaIntf ShaperareaIntf sq1// shorter,without separate declaration:// areaIntf : Shaper(sq1)// or even:// areaIntf : sq1fmt.Printf(The square has area: %f\n, areaIntf.Area())
}这样就会发现如下几个区别
并没有显式继承接口能声明变量并通过该变量指向方法实现方法中的参数为自定义的结构体
一个接口类型的变量或一个 接口值
首先我们来看第一点关于为什么不显示继承这一点我在网上搜过观点基本是Go强调的是组合而非继承并没有一个很确切的理论那暂且不议
第二点areaIntf是一个多字multiword数据结构它的值是 nil。接口变量里包含了接收者实例的值和指向对应方法表的指针。
在Go中我们自定义的结构体就像Java中的类一样可以实现接口中的方法。我们可以同一个接口被实现多次。当时就有了点疑问不是不允许函数重载吗后来发现方法和函数是完全不同的概念
Go中不允许函数function重载是为了提高效率而方法method的可多次实现则体现了Go的多态也就是根据场景选择。 接下来我们看一些进阶功能
接口嵌套接口
在Java 和go当中我们都倡导一个接口的简洁明了。比如说先定义一个结构体为综测综测又是由考试成绩、竞赛、体育等等组成考试成绩里面又有不同科体育里面也有不同科这个时候我们就应该分开定义之后进行嵌套。我个人的理解的理解就是类似于树一样的存在而一个结构体就是一个父节点。这里还是放一个实例
type ReadSeeker interface {ReaderSeeker
}type Reader interface {Read(p []byte) (n int, err error)
}type Seeker interface {Seek(offset int64, whence int) (int64, error)
}类型断言
我们通常会想知道一个接口变量里面是什么类型这个时候我们就会用到类型断言通用格式为
typeA : var1.(T)var1为接口变量T是想知道的类型。如果转换合法typeA 是 var1转换到类型 T 的值
如果在判断式中使用则是这样的
if t, ok : areaIntf.(*Square); ok {fmt.Printf(The type of areaIntf is: %T\n, t)
}如果转换合法t 是 转换到类型的值ok 会是 true否则 t是类型的零值ok 是 false也没有运行时错误发生。
注意如果忽略 areaIntf.(*Square) 中的 * 号会导致编译错误impossible type assertion: Square does not implement Shaper (Area method has pointer receiver)。
同理我们也可以判断他是否属于该接口
type Stringer interface {String() string
}if sv, ok : v.(Stringer); ok {fmt.Printf(v implements String(): %s\n, sv.String()) // note: sv, not v
}类型判断 type-switch
个人认为如果说类型断言是只想知道值是不是某个类型那么此语句则是想知道究竟是哪个重要的类型或者不需要知道的类型常见用法如下
func classifier(items ...interface{}) {for i, x : range items {switch x.(type) {case bool:fmt.Printf(Param #%d is a bool\n, i)case float64:fmt.Printf(Param #%d is a float64\n, i)case int, int64:fmt.Printf(Param #%d is a int\n, i)case nil:fmt.Printf(Param #%d is a nil\n, i)case string:fmt.Printf(Param #%d is a string\n, i)default:fmt.Printf(Param #%d is unknown\n, i)}}
}可以用 type-switch 进行运行时类型分析但是在 type-switch 不允许有 fallthrough 。
使用方法集与接口
作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时这会变得有点复杂原因是接口变量中存储的具体值是不可寻址的
package mainimport (fmt
)type List []intfunc (l List) Len() int {return len(l)
}func (l *List) Append(val int) {*l append(*l, val)
}type Appender interface {Append(int)
}func CountInto(a Appender, start, end int) {for i : start; i end; i {a.Append(i)}
}type Lener interface {Len() int
}func LongEnough(l Lener) bool {return l.Len()*10 42
}func main() {// A bare valuevar lst List// compiler error:// cannot use lst (type List) as type Appender in argument to CountInto:// List does not implement Appender (Append method has pointer receiver)CountInto(lst, 1, 10) //错误代码 if LongEnough(lst) { // VALID: Identical receiver typefmt.Printf(- lst is long enough\n)}// A pointer valueplst : new(List)CountInto(plst, 1, 10) // VALID: Identical receiver typeif LongEnough(plst) {// VALID: a *List can be dereferenced for the receiverfmt.Printf(- plst is long enough\n)}
}输出 讨论
在 lst 上调用 CountInto 时会导致一个编译器错误因为 CountInto 需要一个 Appender而它的方法 Append 只定义在指针上。 在 lst 上调用 LongEnough 是可以的因为 Len 定义在值上。
在 plst 上调用 CountInto 是可以的因为 CountInto 需要一个 Appender并且它的方法 Append 定义在指针上。 在 plst 上调用 LongEnough 也是可以的因为指针会被自动解引用。
总结
在接口上调用方法时必须有和方法定义时相同的接收者类型或者是可以根据具体类型 P 直接辨识的
指针方法可以通过指针调用值方法可以通过值调用接收者是值的方法可以通过指针调用因为指针会首先被解引用接收者是指针的方法不可以通过值调用因为存储在接口中的值没有地址
将一个值赋值给一个接口时编译器会确保所有可能的接口方法都可以在此值上被调用因此不正确的赋值在编译期就会失败。
译注
Go 语言规范定义了接口方法集的调用规则
类型 T 的可调用方法集包含接受者为 T或 T 的所有方法集类型 T 的可调用方法集包含接受者为 T的所有方法类型 T 的可调用方法集不包含接受者为 T 的方法
接下来我们讨论下空接口
空接口
定义不包含任何方法对实现没有要求
空接口类似 Java/C# 中所有类的基类 Object 类二者的目标也很相近。
可以给一个空接口类型的变量 var val interface {} 赋任何类型的值
每个 interface {} 变量在内存中占据两个字长一个用来存储它包含的类型另一个用来存储它包含的数据或者指向数据的指针。
这样光看似乎觉得没什么大不了的我们举个例子比如说创建树或者其他数据结构如果我们要根据每个数据类型来定义不同的方法那无疑是很浪费时间的这时候就可以用到空接口实现一键通用
package mainimport (fmt
)type Node struct {le *Nodedata interface{}rl *Node
}func NewNode(left, right *Node) *Node {return Node{left, nil, right}
}func (n *Node) setData(data interface{}) {n.data data
}func main() {root : NewNode(nil, nil)root.setData(root node)a : NewNode(nil, nil)a.setData(left node)b : NewNode(nil, nil)b.setData(1)root.le aroot.rl bfmt.Printf(%v\n, root)
}实例
我们来看一些实际应用在GORM框架中我们创建对象可以使用map的数据结构导入但是我们无法保证数据都是一个类型所以就需要一个空接口来帮我们接住所有类型
db.Model(User{}).Create([]map[string]interface{}{{Name: jinzhu_1, Age: 18},{Name: jinzhu_2, Age: 20},
})接口赋值给接口
一个接口的值可以赋值给另一个接口变量前提是底层类型实现了必要的方法此转换是在运行时检查的转换失败的时候会导致一个运行时错误这也是GO的动态的一点
比如此代码
package mainimport fmttype Shaper interface {Area() float64
}type Square struct {side float64
}func (s Square) Area() float64 {return s.side * s.side
}type Circle struct {radius float64
}func main() {var s Shaperc : Circle{radius: 5.0}// 错误的示例将接口 Shaper 赋值给接口 Shaper但底层类型 Circle 并没有实现 Area() 方法s cfmt.Printf(Area of the shape: %f\n, s.Area())
}错误显示