GO语言学习(三)
GO语言学习(三)
GO语言的独特接口可以实现内容和面向对象组织的更加方便,我们从这里来详细的讲解接口,让大家感受一下interface的魅力
interface定义
首先接口是一组方法签名的组合,我们通过接口来实现定义对象的一组行为,从这里如果某个对象实现了某个接口所有方法,则称对象实现了此接口,代码如下:
type Human struct {name stringage intphone string
}type Student struct {Human //匿名字段Humanschool stringloan float32
}type Employee struct {Human //匿名字段Humancompany stringmoney float32
}//Human对象实现Sayhi方法
func (h *Human) SayHi() {fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}// Human对象实现Sing方法
func (h *Human) Sing(lyrics string) {fmt.Println("La la, la la la, la la la la la...", lyrics)
}//Human对象实现Guzzle方法
func (h *Human) Guzzle(beerStein string) {fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}// Employee重载Human的Sayhi方法
func (e *Employee) SayHi() {fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,e.company, e.phone) //此句可以分成多行
}//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {s.loan += amount // (again and again and...)
}//Employee实现SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {e.money -= amount // More vodka please!!! Get me through the day!
}
// 开始来实现一下接口
// 定义interface,开始讲解咯,大家请注意
type Men interface {SayHi()Sing(lyrics string)Guzzle(beerStein string)
}type YoungChap interface {SayHi()Sing(song string)BorrowMoney(amount float32)
}type ElderlyGent interface {SayHi()Sing(song string)SpendSalary(amount float32)
}
开始解释一下这段代码,友友们可以参考,也可以发评论讨论。
通过上面的代码我们可以知道,interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YoungChap两个interface。
最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。
interface的值
首先这个interface的值要想了解就必须得知道对象,可以存储实现这个interface的任意类型的对象,例如上面例子中,我们定义了一个Men interface类型的变量m,那么m里面可以存Human、Student或者Employee值,因为m能够持有这三种类型的对象,所以我们可以定义一个包含Men类型元素的slice,这个slice可以被赋予实现了Men接口的任意结构的对象,这个和我们传统意义上面的slice有所不同。
下面直接放代码:
package mainimport "fmt"type Human struct {name stringage intphone string
}type Student struct {Human //匿名字段school stringloan float32
}type Employee struct {Human //匿名字段company stringmoney float32
}//Human实现SayHi方法
func (h Human) SayHi() {fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}//Human实现Sing方法
func (h Human) Sing(lyrics string) {fmt.Println("La la la la...", lyrics)
}//Employee重载Human的SayHi方法
func (e Employee) SayHi() {fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,e.company, e.phone)}// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {SayHi()Sing(lyrics string)
}func main() {mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}//定义Men类型的变量ivar i Men//i能存储Studenti = mikefmt.Println("This is Mike, a Student:")i.SayHi()i.Sing("November rain")//i也能存储Employeei = tomfmt.Println("This is tom, an Employee:")i.SayHi()i.Sing("Born to be wild")//定义了slice Menfmt.Println("Let's use a slice of Men and see what happens")x := make([]Men, 3)//这三个都是不同类型的元素,但是他们实现了interface同一个接口x[0], x[1], x[2] = paul, sam, mikefor _, value := range x{value.SayHi()}
}
通过上面这段代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现,这点请诸位读者好好理解。
空interface
空interface(interface{})是不包含任何的method的接口,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
代码供参考:
// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s
这个interface中实现一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。
interface函数参数
interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数,同时你是否注意到它可以接受任意类型的数据,这个可以参考一些实际函数或对象的源文件(源代码)。
这里给个1参考代码:
type stringer_name interface{string() string
}
也就是实现一段代码我们可以调用所创建的接口来实现,这里我放一段代码,大家可以参考:
package main
import ("fmt""strconv"
)type Human struct {name stringage intphone string
}// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
}func main() {Bob := Human{"Bob", 39, "000-7777-XXX"}fmt.Println("This Human is : ", Bob)
}
[!IMPORTANT]
如果需要某个类型能被fmt包以特殊的格式输出,你就必须实现Stringer这个接口。如果没有实现这个接口,fmt将以默认的方式输出。
可以等效成这样子来实现相应功能:
//实现同样的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())
interface变量存储的类型
我们来讲解一下这个是到底如何知道这个变量里面保存了哪些类型的对象,一是Comma-ok断言,二是Switch测试。
Comma-ok断言
Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。
下面给代码实例:
package mainimport ("fmt""strconv")type Element interface{}type List [] Elementtype Person struct {name stringage int}//定义了String方法,实现了fmt.Stringerfunc (p Person) String() string {return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"}func main() {list := make(List, 3)list[0] = 1 // an intlist[1] = "Hello" // a stringlist[2] = Person{"Dennis", 70}for index, element := range list {if value, ok := element.(int); ok {fmt.Printf("list[%d] is an int and its value is %d\n", index, value)} else if value, ok := element.(string); ok {fmt.Printf("list[%d] is a string and its value is %s\n", index, value)} else if value, ok := element.(Person); ok {fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)} else {fmt.Printf("list[%d] is of a different type\n", index)}}}
看过之后感觉是不是很简单啊,同时你是否注意到了多个if里面,还记得我前面介绍流程时讲过,if里面允许初始化变量,也许你注意到了,我们断言的类型越多,那么if else也就越多,所以才引出了下面要介绍的switch。
switch测试
下面我来为大家呈现出完整实现代码,并为大家提供解释,大家有啥不会的可以在评论区评论,也可以私信我,让我们一起学习。
package mainimport ("fmt""strconv")type Element interface{}type List [] Elementtype Person struct {name stringage int}//打印func (p Person) String() string {return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"}func main() {list := make(List, 3)list[0] = 1 //an intlist[1] = "Hello" //a stringlist[2] = Person{"Dennis", 70}for index, element := range list{switch value := element.(type) {case int:fmt.Printf("list[%d] is an int and its value is %d\n", index, value)case string:fmt.Printf("list[%d] is a string and its value is %s\n", index, value)case Person:fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)default:fmt.Println("list[%d] is of a different type", index)}}}
这里有一点需要强调的是:element.(type)
语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用comma-ok
。
嵌入interface
下面我们来讲解一下interface的优美用法,让大家一起感受一下go语言的设计魅力,如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。
// 下面我们来看container/heap包的源码
type Interface interface {sort.Interface //嵌入字段sort.InterfacePush(x interface{}) //a Push method to push elements into the heapPop() interface{} //a Pop elements that pops elements from the heap
}
// 我们看到sort.Interface其实就是嵌入字段,把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法:
type Interface interface {// Len is the number of elements in the collection.Len() int// Less returns whether the element with index i should sort// before the element with index j.Less(i, j int) bool// Swap swaps the elements with indexes i and j.Swap(i, j int)
}
// io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface:
type ReadWriter interface {ReaderWriter
}
反射
所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包
了解reflect包我这里附上了实现reflect包的工作原理,文档参考
使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值// 转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值// 获取反射值能返回相应的类型和数值
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())// 最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)// 如果要修改相应的值,必须这样写
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)
这些只是反射的基本知识,也欢迎大家在评论中讨论共同学习,一起进步。