
go 接口
1.接口是什么
接口是一组方法签名。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口 Interface{} 可以理解成空接口,可以接收任意类型的参数
接口类型是对其他类型行为的概括与抽象,Go语言的接口在于它是隐式实现,对于一个具体的类型,无需声明它实现了哪些接口,只需要提供接口所必须的方法即可
只要是实现了方法体 GetName 的结构体都是实现了 Animal 这个接口
- 接口的实现
type Animal interface { GetName() string }
- 接口接收参数的类型判断
接口可能为 nil,所以需要做 nil 判断
func E(x interface{}) { if x == nil { fmt.Println("empty interface") return } fmt.Println("non-empty interface") } func main() { var x *int = nil E(x) }
- 接口定义和使用
package main import "fmt" /* type 接口名字 interface{ 方法声明 方法名 (参数) 返回值 } */ type Person2 struct { Name string Age int } //定义两个子结构体 type Teacher struct { Person2 Subject string } type Student2 struct { Person2 Score int } //定义两个结构体方法 func (t *Teacher) SayHi(){ fmt.Printf("大家好,我是%s,我今年%d岁,我教%s\n",t.Name,t.Age,t.Subject) } func (s *Student2) SayHi(){ fmt.Printf("大家好,我是%s,我今年%d岁,我的成绩是%d\n",s.Name,s.Age,s.Score) } //定义接口 type Humaner interface { SayHi() } func main(){ tea := Teacher{Person2{"望合",18},"Go语言编程"} stu := Student2{Person2{"Ops",30},100} //创建接口类型变量 var h Humaner //将对象的地址赋值给接口类型 h = &stu h.SayHi() h = &tea h.SayHi() } C:\Users\vSphere\go\src\day04>go run 04.go 大家好,我是Ops,我今年30岁,我的成绩是100 大家好,我是望合,我今年18岁,我教Go语言编程
- 多态 将接口类型作为函数参数
package main import "fmt" /* type 接口名字 interface{ 方法声明 方法名 (参数) 返回值 } */ type Person2 struct { Name string Age int } //定义两个子结构体 type Teacher struct { Person2 Subject string } type Student2 struct { Person2 Score int } //定义两个结构体方法 func (t *Teacher) SayHi(){ fmt.Printf("大家好,我是%s,我今年%d岁,我教%s\n",t.Name,t.Age,t.Subject) } func (s *Student2) SayHi(){ fmt.Printf("大家好,我是%s,我今年%d岁,我的成绩是%d\n",s.Name,s.Age,s.Score) } //定义接口 type Humaner interface { SayHi() } //多态 将接口类型作为函数参数 func SayHi(h Humaner){ h.SayHi() } func main(){ //初始化对象 tea := Teacher{Person2{"望合",18},"Go语言编程"} stu := Student2{Person2{"Ops",30},100} //使用多态 SayHi(&tea) SayHi(&stu) } C:\Users\vSphere\go\src\day04>go run 04.go 大家好,我是望合,我今年18岁,我教Go语言编程 大家好,我是Ops,我今年30岁,我的成绩是100
2.实现接口
一个接口类型定义了一套方法,如果一个具体类型要实现该接口,那么必须要实现接口类型定义中的所有方法
type Reader interface { Read(p []byte) (n int, err error) } type Closter interface { Close() error } //通过组合已有接口得到的新新接口 type ReadWriter interface { Reader Closter }以上的语法称为嵌入式接口
type ReadWriter interface { Read(p []byte) (n int, err error) Write(p []byte) (n int, err error) } // 混用 type ReadWriters interface { Read(p []byte) (n int, err error) Closter }三种声明的效果都是一致的,方法定义的顺序也是毫无意义,真正有意义的只有接口的方法集
如果一个类型实现了一个接口要求的所有方法,那么这个类型实现了这个接口
接口的赋值规则,仅当一个表达式实现了一个接口时,这个表达式才可以赋给该接口
var w io.Writer w = os.Stdout //OK,*os.File有Write方法 w = new(bytes.Buffer) //OK,*bytes.Buffer有Write方法 w = time.Second //编译错误:time.Duration缺少Write方法 var rwc io.ReadWriteCloser rwc = os.Stdout //OK,*os.File有Read,Write,Close方法 rwc = new(bytes.Buffer) //编译错误,缺少Close方法 //当右侧表达式也是一个接口时,该规则也有效 w = rwc空接口,空接口对其实现类型没有任何要求,所以可以把任何值赋给空接口类型
非空的接口类型通常由一个指针类型来实现,特别是当接口类型的一个或多个方法暗示会修改接收者的情形,一个指向结构体的指针才是最常见的方法接收者
示例,flag.Value解析参数
默认的睡眠时间是1s,但是可以用-period命令标志来控制,flag.Duration函数创建了一个time.Duration类型的标志变量 //!+sleep var period = flag.Duration("period", 1*time.Second, "sleep period") func main() { flag.Parse() fmt.Printf("Sleeping for %v...", *period) time.Sleep(*period) fmt.Println() }type Celsius float64 type Fahrenheit float64 func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) } func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } type celsiusFlag struct{ Celsius } func (f *celsiusFlag) Set(s string) error { var unit string var value float64 fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed switch unit { case "C", "°C": f.Celsius = Celsius(value) return nil case "F", "°F": f.Celsius = FToC(Fahrenheit(value)) return nil } return fmt.Errorf("invalid temperature %q", s) } func CelsiusFlag(name string, value Celsius, usage string) *Celsius { f := celsiusFlag{value} flag.CommandLine.Var(&f, name, usage) return &f.Celsius }var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature") func main() { flag.Parse() fmt.Println(*temp) }
3.接口方法集
- 接口方法集
package main type Animal interface { GetName() string } type People struct { Name string `json:"name"` Age int `json:"age"` } func (p *People) GetName() string { return p.Name }
- 调用情况
var n Animal p1 := People{} p2 := &People{} n = p1 //无法编译 n = p2
4.接口方法集调用关系
仅影响接口,与实例或者指针调用无关
接口方法集定义了接口接受的规则规范里面描述的方法集
| 值 | 方法接收者 |
|---|---|
| T | (t T) |
| *T | (t T) && (t *T) |
| T 包含匿名字段 S | (t T) && (s S) |
| T 包含匿名字段 *S | (t T) && (s S) && (s *S) |
| *T 包含匿名字段 *S | (t T) && (t *T) && (s S) && (s *S) |
- 判断某个值是不是某个接口实现
var o interface{} = &People{Name: "LiLei"} if n, ok := o.(Animal); ok { fmt.Println(n.GetName()) }var o interface{} = &People{Name: "LiLei"} switch o.(type) { case People: fmt.Println("is people") case *People: fmt.Println("is people *") case Animal: fmt.Println("is animal") }输出为 is people *
如果没有选项 case *People 情况下,会输出 is animal,因为 People 实现了接口 Animal
People 和*People 是不同的类型,需要注意
- 类型断言(一)
package main import "fmt" type people struct { Name string } func main(){ var i interface{} = &people{"墨持"} //类型断言 if v,ok := i.(*people);ok{ fmt.Print(v.Name) }else{ fmt.Println("类型匹配失败") } } C:\Users\vSphere\go\src\day04>go run 05.go 墨持
- 类型断言(二)
package main import ( "fmt" ) type people struct { Name string } func main(){ var i interface{} = people{"乘志"} switch i.(type) { case people: fmt.Println("people") case *people: fmt.Println("*people") default: fmt.Println("类型匹配失败") } } C:\Users\vSphere\go\src\day04>go run 05.go people
5.接口值
一个接口类型的值其实有2个部分:
- 一个具体类型
- 该类型的一个值
var w io.Writer w = os.Stdout w = new(bytes.Buffer) w = nil注意:含有空指针的非空接口
空的接口值与仅仅动态值为nil的接口值是不一样的
对于某些类型,比如os.File,空接收值是合法的,但对于bytes.Buffer则不行,方法尽管被调用了,但在尝试访问缓冲区时崩溃了
问题在于尽管一个空的*bytes.Buffer指针拥有的方法满足了该接口,但它并不满足该接口所需的一些行为,既接收者不能为空.
6.使用sort.Interface来排序
原地排序算法
//!+main type Track struct { Title string Artist string Album string Year int Length time.Duration } var tracks = []*Track{ {"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")}, {"Go", "Moby", "Moby", 1992, length("3m37s")}, {"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")}, {"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")}, } func length(s string) time.Duration { d, err := time.ParseDuration(s) if err != nil { panic(s) } return d } //!-main // !+printTracks func printTracks(tracks []*Track) { const format = "%v\t%v\t%v\t%v\t%v\t\n" tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0) fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length") fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------") for _, t := range tracks { fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length) } tw.Flush() // calculate column widths and print table } //!-printTracks // !+artistcode type byArtist []*Track func (x byArtist) Len() int { return len(x) } func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist } func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] } //!-artistcode // !+yearcode type byYear []*Track func (x byYear) Len() int { return len(x) } func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year } func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] } //!-yearcode func main() { fmt.Println("byArtist:") sort.Sort(byArtist(tracks)) printTracks(tracks) fmt.Println("\nReverse(byArtist):") sort.Sort(sort.Reverse(byArtist(tracks))) printTracks(tracks) fmt.Println("\nbyYear:") sort.Sort(byYear(tracks)) printTracks(tracks) fmt.Println("\nCustom:") //!+customcall sort.Sort(customSort{tracks, func(x, y *Track) bool { if x.Title != y.Title { return x.Title < y.Title } if x.Year != y.Year { return x.Year < y.Year } if x.Length != y.Length { return x.Length < y.Length } return false }}) //!-customcall printTracks(tracks) } /* //!+artistoutput Title Artist Album Year Length ----- ------ ----- ---- ------ Go Ahead Alicia Keys As I Am 2007 4m36s Go Delilah From the Roots Up 2012 3m38s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Moby Moby 1992 3m37s //!-artistoutput //!+artistrevoutput Title Artist Album Year Length ----- ------ ----- ---- ------ Go Moby Moby 1992 3m37s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Delilah From the Roots Up 2012 3m38s Go Ahead Alicia Keys As I Am 2007 4m36s //!-artistrevoutput //!+yearoutput Title Artist Album Year Length ----- ------ ----- ---- ------ Go Moby Moby 1992 3m37s Go Ahead Alicia Keys As I Am 2007 4m36s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Delilah From the Roots Up 2012 3m38s //!-yearoutput //!+customout Title Artist Album Year Length ----- ------ ----- ---- ------ Go Moby Moby 1992 3m37s Go Delilah From the Roots Up 2012 3m38s Go Ahead Alicia Keys As I Am 2007 4m36s Ready 2 Go Martin Solveig Smash 2011 4m24s //!-customout */ // !+customcode type customSort struct { t []*Track less func(x, y *Track) bool } func (x customSort) Len() int { return len(x.t) } func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) } func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] } //!-customcode func init() { //!+ints values := []int{3, 1, 4, 1} fmt.Println(sort.IntsAreSorted(values)) // "false" sort.Ints(values) fmt.Println(values) // "[1 1 3 4]" fmt.Println(sort.IntsAreSorted(values)) // "true" sort.Sort(sort.Reverse(sort.IntSlice(values))) fmt.Println(values) // "[4 3 1 1]" fmt.Println(sort.IntsAreSorted(values)) // "false" //!-ints }
7.http.Handler接口
一个程序,有两个不同的Web服务器,在不同的端口监听,定义不同的URL,分发到不同的处理程序,只需要简单地构造另外一个ServerMux,再调用一次ListenAdnServer即可.
func main() { db := database{"shoes": 50, "socks": 5} http.HandleFunc("/list", db.list) http.HandleFunc("/price", db.price) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } //!-main type dollars float32 func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } type database map[string]dollars func (db database) list(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } } func (db database) price(w http.ResponseWriter, req *http.Request) { item := req.URL.Query().Get("item") if price, ok := db[item]; ok { fmt.Fprintf(w, "%s\n", price) } else { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) } }
8.error接口
error只是一个接口类型,包含返回错误消息的方法:
type error interface { Error() string }构造error最简单的方法是调用errors.New,它会返回一个包含指定的错误消息的新error实例
底层的errorString类型是一个结构,而没有直接用字符串,主要是为了避免将来无意间布局变更,满足error接口的是*errorString指针,而不是原始的errorString,主要是为了让每次New分配的error实例都互不相等.
可以直接查看error的源码