接口
接口定义方法
接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
例子:
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
练习 11.1 simple_interface.go:
定义一个接口 Simpler,它有一个 Get() 方法和一个 Set(),Get() 返回一个整型值,Set() 有一个整型参数。创建一个结构体类型 Simple 实现这个接口。
接着定一个函数,它有一个 Simpler 类型的参数,调用参数的 Get() 和 Set() 方法。在 main 函数里调用这个函数,看看它是否可以正确运行。
package main
import (
"fmt"
)
type Simpler interface {
get() int
set(i int) Simpler
}
func show(a Simpler) {
fmt.Print(a.get(), "\n")
a = a.set(10)
fmt.Print(a.get(), "\n")
}
type ss struct {
i int
}
func (s ss) get() int {
return s.i
}
func (s ss) set(i int) Simpler {
s.i = i
return s
}
func main() {
var si Simpler
si = ss{1}
show(si)
}
练习 11.2 interfaces_poly2.go:
a) 扩展 interfaces_poly.go 中的例子,添加一个 Circle 类型
b) 使用一个抽象类型 Shape(没有字段) 实现同样的功能,它实现接口 Shaper,然后在其他类型里内嵌此类型。扩展 10.6.5 中的例子来说明覆写。
package main
import "fmt"
type shaper interface {
say(string2 string)
}
type shape struct {
}
func (a *shape) say(string2 string) {
fmt.Print("i am ", string2)
}
type circle struct {
name string
shape
}
func main() {
c := circle{"circle", shape{}}
c.say(c.name)
}
接口嵌套接口
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
比如接口 File 包含了 ReadWrite 和 Lock 的所有方法,它还额外有一个 Close() 方法。
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
如何判断接口类型
方法一varI.(T)
一个接口类型的变量 varI 中可以包含任何类型的值,必须有一种方式来检测它的 动态 类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用 类型断言 来测试在某个时刻 varI 是否包含类型 T 的值:
v := varI.(T)
可以多使用下面的代码进行判断,如果转换合法,v 是 varI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,ok 是 false,也没有运行时错误发生。
if v, ok := varI.(T); ok { // checked type assertion
Process(v)
return
}
方法二type-switch
接口变量的类型也可以使用一种特殊形式的 switch 来检测:type-switch
例子:
switch t := areaIntf.(type) {
case *Square:
fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
fmt.Printf("nil value: nothing to check?\n")
default:
fmt.Printf("Unexpected type %T\n", t)
}
练习 11.4 simple_interface2.go:
接着练习 11.1 中的内容,创建第二个类型 RSimple,它也实现了接口 Simpler,写一个函数 fi(),使它可以区分 Simple 和 RSimple 类型的变量。
package main
import (
"fmt"
)
type Simpler interface {
get() int
set(i int)
}
type Rsimple struct {
i int
}
func (s Rsimple) get() int {
return s.i
}
func (s Rsimple) set(i int) {
s.i = i
}
type Simple struct {
i int
}
func (s Simple) get() int {
return s.i
}
func (s Simple) set(i int) {
s.i = i
}
func panduan(items interface{}) {
switch items.(type) {
case Simple:
fmt.Print("it is Simple \n")
case Rsimple:
fmt.Print("it is Rsimple\n")
default:
fmt.Print("nothing\n")
}
}
func main() {
var ss Simpler
s := Simple{1}
s1 := Rsimple{2}
ss = s
panduan(ss)
ss = s1
panduan(ss)
}
测试一个值是否实现了某个接口
假定 v 是一个值,然后我们想测试它是否实现了 Stringer 接口,可以这样做:
func main() {
var ss Simpler
s := Simple{1}
s1 := Rsimple{2}
ss = s
panduan(ss)
ss = s1
panduan(ss)
if sv, ok := interface{}(s1).(Simpler); ok {
fmt.Printf("v implements Get(): %d\n", sv.get())
}
}
如果v得类型不是接口那么我们需要使用interface{}先将他转换为接口
接口方法设计的注意事项和规范
在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以根据具体类型 P 直接辨识的:
指针方法可以通过指针调用
值方法可以通过值调用
接收者是值的方法可以通过指针调用,因为指针会首先被解引用
接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
译注
Go 语言规范定义了接口方法集的调用规则:
类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
类型 T 的可调用方法集包含接受者为 T 的所有方法
类型 T 的可调用方法集不包含接受者为 *T 的方法
上述例子改成指针调用的例子:
package main
import (
"fmt"
)
type Simpler interface {
get() int
set(i int)
}
type Rsimple struct {
i int
}
func (s *Rsimple) get() int {
return s.i
}
func (s *Rsimple) set(i int) {
s.i = i
}
type Simple struct {
i int
}
func (s *Simple) get() int {
return s.i
}
func (s *Simple) set(i int) {
s.i = i
}
func panduan(items interface{}) {
switch items.(type) {
case *Simple:
fmt.Print("it is Simple \n")
case *Rsimple:
fmt.Print("it is Rsimple\n")
default:
fmt.Print("nothing\n")
}
}
func main() {
var ss Simpler
s := new(Simple)
s.i = 1
s1 := new(Rsimple)
s1.i = 2
ss = s
panduan(ss)
ss = s1
panduan(ss)
}
练习 11.5 interfaces_ext.go:
a). 继续扩展程序,定义类型 Triangle,让它实现 AreaInterface 接口。通过计算一个特定三角形的面积来进行测试(三角形面积=0.5 * (底 * 高))
b). 定义一个新接口 PeriInterface,它有一个 Perimeter 方法。让 Square 实现这个接口,并通过一个 Square 示例来测试它。
package main
import "fmt"
type AreaInterface interface {
calArea() float64
}
type PeriInterface interface {
calPeri() float64
}
type Triangle struct {
high int
width int
}
func (a *Triangle) calArea() float64 {
res := float64(a.high*a.width) / 2
return res
}
func (a *Triangle) calPeri() float64 {
res := float64(a.high+a.width) * 2
return res
}
type Square struct {
high int
width int
}
func (a *Square) calArea() float64 {
res := float64(a.high * a.width)
return res
}
func (a *Square) calPeri() float64 {
res := float64(a.high+a.width) * 2
return res
}
func main() {
var a AreaInterface
var b PeriInterface
t1 := Triangle{5, 10}
t2 := Square{5, 10}
a = &t1
fmt.Print(a.calArea(), "\n")
a = &t2
fmt.Print(a.calArea(), "\n")
b = &t1
fmt.Print(b.calPeri(), "\n")
b = &t2
fmt.Print(b.calPeri(), "\n")
}
练习 11.6 point_interfaces.go:
继续 10.3 中的练习 point_methods.go,定义接口 Magnitude,它有一个方法 Abs()。让 Point、Point3 及 Polar 实现此接口。通过接口类型变量使用方法做 point.go 中同样的事情。
package main
import (
"fmt"
"math"
)
type Magnitude interface {
Abs() float64
Scale(flag float64)
}
type Point3d struct {
x int
y int
z int
}
func (p *Point3d) Abs() float64 {
res := math.Sqrt(float64(p.x*p.x + p.y*p.y + p.z*p.z))
return res
}
func (p *Point3d) Scale(flag float64) {
lens := p.Abs()
len_S := lens * flag
cosx := float64(p.x) / lens
cosy := float64(p.y) / lens
cosz := float64(p.z) / lens
p.x = int(len_S * cosx)
p.y = int(len_S * cosy)
p.z = int(len_S * cosz)
}
type Point2d struct {
x int
y int
}
func (p *Point2d) Abs() float64 {
res := math.Sqrt(float64(p.x*p.x + p.y*p.y))
return res
}
func (p *Point2d) Scale(flag float64) {
lens := p.Abs()
len_S := lens * flag
cosx := float64(p.x) / lens
cosy := float64(p.y) / lens
p.x = int(len_S * cosx)
p.y = int(len_S * cosy)
}
type Polar struct {
l float64
cos float64
}
func (p *Polar) Abs() float64 {
return p.l
}
func (p *Polar) Scale(flag float64) {
p.l *= flag
}
func main() {
var m Magnitude
p2 := new(Point2d)
p2.x, p2.y = 5, 5
p3 := new(Point3d)
p3.x, p3.y, p3.z = 5, 5, 5
p := new(Polar)
p.l, p.cos = 5, 0.6
m = p2
res := m.Abs()
fmt.Print(res, "\n")
m.Scale(2)
res = m.Abs()
fmt.Print(res, "\n")
m = p3
res = m.Abs()
fmt.Print(res, "\n")
m.Scale(2)
res = m.Abs()
fmt.Print(res, "\n")
m = p
res = m.Abs()
fmt.Print(res, "\n")
m.Scale(2)
res = m.Abs()
fmt.Print(res, "\n")
}
练习 11.7 float_sort.go / float_sortmain.go:
类似 11.7 和示例 11.3/4,定义一个包 float64,并在包里定义类型 Float64Array,然后让它实现 Sorter 接口用来对 float64 数组进行排序。
另外提供如下方法:0
NewFloat64Array():创建一个包含 25 个元素的数组变量(参考 10.2 )
List():返回数组格式化后的字符串,并在 String() 方法中调用它,这样就不用显式地调用 List() 来打印数组(参考 10.7)
Fill():创建一个包含 10 个随机浮点数的数组(参考 4.5.2.6)
在主程序中新建一个此类型的变量,然后对它排序并进行测试。
FloatArray.go
package float64
import (
"math/rand"
"strconv"
"strings"
)
type Float64Array struct {
res []float64
}
func (f *Float64Array) NewFloat64Array() {
f.res = make([]float64, 25)
}
func (f *Float64Array) List() string {
sb := strings.Builder{}
for _, v := range f.res {
sb.WriteString(strconv.FormatFloat(v, 'f', 10, 64) + "\t")
}
return sb.String()
}
func (f *Float64Array) String() string {
return f.List()
}
func (f *Float64Array) Fill() {
f.res = make([]float64, 10)
for i, _ := range f.res {
f.res[i] = rand.Float64()
}
}
func (f Float64Array) Len() int {
return len(f.res)
}
func (f Float64Array) Less(i, j int) bool {
if f.res[i] > f.res[j] {
return true
} else {
return false
}
}
func (f Float64Array) Swap(i, j int) {
temp := f.res[i]
f.res[i] = f.res[j]
f.res[j] = temp
}
main.go
package main
import (
float642 "awesomeProject/float64"
"fmt"
"sort"
)
func main() {
var ff float642.Float64Array
ff.Fill()
fmt.Printf("%v\n", ff)
var ff1 float642.Float64Array
ff1.NewFloat64Array()
fmt.Printf("%v\n", ff1)
sort.Sort(ff)
fmt.Printf("%v\n", ff)
}
结果:
练习 11.8 sort.go / sort_persons.go:
定义一个结构体 Person,它有两个字段:firstName 和 lastName,为 []Person 定义类型 Persons 。让 Persons 实现 Sorter 接口并进行测试。
package main
import (
"fmt"
"sort"
"strings"
)
type Person struct {
firstName string
lastName string
}
type Persons []Person
func (p Persons) String() string {
sb := strings.Builder{}
for i, _ := range p {
sb.WriteString(p[i].firstName + " " + p[i].lastName + "\t")
}
return sb.String()
}
func (p Persons) Len() int {
return len(p)
}
func (p Persons) Less(i, j int) bool {
pi, pj := p[i], p[j]
if strings.Compare(pi.firstName, pj.firstName) == 1 {
return true
} else if strings.Compare(pi.firstName, pj.firstName) == -1 {
return false
} else {
if strings.Compare(pi.lastName, pj.lastName) == 1 {
return true
} else if strings.Compare(pi.lastName, pj.lastName) == -1 {
return false
} else {
return false
}
}
}
func (p Persons) Swap(i, j int) {
temp := p[i]
p[i] = p[j]
p[j] = temp
}
func main() {
ps := Persons{Person{"asda", "ada"}, Person{"dasda", "adasdsa"}, Person{"dasdas", "dadasgdg"}, Person{"asda", "fdghfdghd"}}
fmt.Printf("%v\n", ps)
sort.Sort(ps)
fmt.Printf("%v\n", ps)
}
结果:
空接口
空接口或者最小接口 不包含任何方法,它对实现不做任何要求:
type Any interface {}
练习 11.9 simple_interface3.go:
继续练习 11.2,在它中添加一个 gI() 函数,它不再接受 Simpler 类型的参数,而是接受一个空接口参数。然后通过类型断言判断参数是否是 Simpler 类型。最后在 main 使用 gI() 取代 fI() 函数并调用它。确保你的代码足够安全。
package main
import (
"fmt"
)
type any interface {
}
func gl(a any) {
if v, ok := a.(Simpler); ok {
show(v)
}
}
type Simpler interface {
get() int
set(i int) Simpler
}
func show(a Simpler) {
fmt.Print(a.get(), "\n")
a = a.set(10)
fmt.Print(a.get(), "\n")
}
type ss struct {
i int
}
func (s ss) get() int {
return s.i
}
func (s ss) set(i int) Simpler {
s.i = i
return s
}
func main() {
var si Simpler
si = ss{1}
var a any
a = si
gl(a)
}
用空接口构建通用类型或包含不同类型变量的数组
们给空接口定一个别名类型 Element:type Element interface{}
然后定义一个容器类型的结构体 Vector,它包含一个 Element 类型元素的切片:
type Vector struct {
a []Element
}
Vector 里能放任何类型的变量,因为任何类型都实现了空接口,实际上 Vector 里放的每个元素可以是不同类型的变量。我们为它定义一个 At() 方法用于返回第 i 个元素:
func (p *Vector) At(i int) Element {
return p.a[i]
}
再定一个 Set() 方法用于设置第 i 个元素的值:
func (p *Vector) Set(i int, e Element) {
p.a[i] = e
}
Vector 中存储的所有元素都是 Element 类型,要得到它们的原始类型(unboxing:拆箱)需要用到类型断言。TODO:The compiler rejects assertions guaranteed to fail,类型断言总是在运行时才执行,因此它会产生运行时错误。
练习 11.10 min_interface.go / minmain.go:
仿照 11.7 中开发的 Sorter 接口,创建一个 Miner 接口并实现一些必要的操作。函数 Min() 接受一个 Miner 类型变量的集合,然后计算并返回集合中最小的元素。
package main
import (
"fmt"
"strings"
)
type Element interface {
}
type Miner interface {
Len() int
Swap(i, j int)
Less(i, j int) bool
At(i int) Element
Set(i int, e Element)
}
type Vector struct {
a []Element
}
func (p *Vector) Len() int {
return len(p.a)
}
// 不同类型sting>float64>float32>int
func (p *Vector) Less(i, j int) bool {
pi, pj := p.a[i], p.a[j]
switch pi.(type) {
case int:
switch pj.(type) {
case int:
piv, pjv := pi.(int), pj.(int)
if piv > pjv {
return true
} else {
return false
}
case float32:
return false
case float64:
return false
case string:
return false
}
case string:
switch pj.(type) {
case string:
piv, pjv := pi.(string), pj.(string)
if strings.Compare(piv, pjv) == 1 {
return true
} else {
return false
}
case int:
return true
case float64:
return true
case float32:
return true
}
case float64:
switch pj.(type) {
case float64:
piv, pjv := pi.(float64), pj.(float64)
if piv > pjv {
return true
} else {
return false
}
case string:
return false
case int:
return true
case float32:
return true
}
case float32:
switch pj.(type) {
case float32:
piv, pjv := pi.(float32), pj.(float32)
if piv > pjv {
return true
} else {
return false
}
case string:
return false
case int:
return true
case float64:
return false
}
}
return false
}
func (p *Vector) Swap(i, j int) {
pi, pj := p.a[i], p.a[j]
p.Set(i, pj)
p.Set(j, pi)
}
func (p *Vector) At(i int) Element {
return p.a[i]
}
func (p *Vector) Set(i int, e Element) {
p.a[i] = e
}
func Sort(data Miner) {
for pass := 1; pass < data.Len(); pass++ {
for i := 0; i < data.Len()-pass; i++ {
if data.Less(i+1, i) {
data.Swap(i, i+1)
}
}
}
}
func Min(data Miner) Element {
Sort(data)
lens := data.Len()
return data.At(lens - 1)
}
func Max(data Miner) Element {
Sort(data)
return data.At(0)
}
func main() {
v := Vector{[]Element{123, 464, 13.01, 15430.154, "adsdsa", "adadasda", "tyurueueue"}}
res := Min(&v)
switch res.(type) {
case int:
fmt.Print(res.(int))
case string:
fmt.Print(res.(string))
case float64:
fmt.Print(res.(float64))
case float32:
fmt.Print(res.(float32))
}
res1 := Max(&v)
switch res1.(type) {
case int:
fmt.Print(res1.(int))
case string:
fmt.Print(res1.(string))
case float64:
fmt.Print(res1.(float64))
case float32:
fmt.Print(res1.(float32))
}
}
结果:
反射
两个简单的函数,reflect.TypeOf 和 reflect.ValueOf,返回被检查对象的类型和值。他们的返回值分别为reflect.Type 和 reflect.Value类型。有许多方法用于检查和操作它们。 Type 和 Value 都有 Kind() 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。同样 Value 有叫做 Int() 和 Float() 的方法可以获取存储在内部的值(跟 int64 和 float64 一样)Kind() 总是返回底层类型:
例子:
func main() {
type MyInt int
var m MyInt = 5
v := reflect.ValueOf(m)
fmt.Println(v.Kind())
fmt.Println(v.Type())
fmt.Println(v.Int())
}
结果:
在这里插入图片描述
通过反射修改(设置)值
假设我们要把 x 的值改为 3.1415。Value 有一些方法可以完成这个任务。首先需要判断是否可设置,是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性:可以使用 CanSet() 方法测试是否可设置。当 v := reflect.ValueOf(x) 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 v = reflect.ValueOf(&x)。通过 Type() 我们看到 v 现在的类型是 *float64 并且仍然是不可设置的。要想让其可设置我们需要使用 Elem() 函数,这间接地使用指针:v = v.Elem()现在 v.CanSet() 返回 true 并且v.SetFloat(3.1415) 设置成功了
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
// setting a value:
// v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
fmt.Println("settability of v:", v.CanSet())
v = reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of v:", v.Type())
fmt.Println("settability of v:", v.CanSet())
v = v.Elem()
fmt.Println("The Elem of v is: ", v)
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(3.1415) // this works!
fmt.Println(v.Interface())
fmt.Println(v)
}
结果:
反射结构
有些时候需要反射一个结构类型。NumField() 方法返回结构内的字段数量;通过一个 for 循环用索引取得每个字段的值 Field(i)。但是如果尝试更改一个值,需将
被导出字段的首字母大写设置为大写。
package main
import (
"fmt"
"reflect"
"strconv"
)
type test struct {
s1, s2, s3 string
}
type Test struct {
S1, S2, S3 string
}
type any interface {
}
func main() {
var any1 any
any1 = test{"adas", "adasd", "dasdad"}
any3 := Test{"huw", "adhjs", "wequwe"}
value := reflect.ValueOf(any1)
value1 := reflect.ValueOf(&any3).Elem()
for i := 0; i < value.NumField(); i++ {
fmt.Printf("Field %d: %v\n", i, value.Field(i))
fmt.Printf("Field %d: %v\n", i, value1.Field(i).Interface())
value1.Field(i).SetString(strconv.Itoa(i))
}
for i := 0; i < value.NumField(); i++ {
fmt.Printf("Field %d: %v\n", i, value1.Field(i))
}
}
练习 11.11:map_function_interface.go:
在练习 7.13 中我们定义了一个 map() 函数来使用 int 切片 (map_function.go)。
通过空接口和类型断言,现在我们可以写一个可以应用于许多类型的泛型的 map() 函数,为 int 和 string 构建一个把 int 值加倍和将字符串值与其自身连接(译者注:即 “abc” 变成 “abcabc” )的 map() 函数 mapFunc()。
提示:为了可读性可以定义一个 interface{} 的别名,比如:type obj interface{}。
package main
import "fmt"
type any interface{}
func main() {
// define a generic lambda function mf:
mf := func(i any) any {
switch i.(type) {
case int:
return i.(int) * i.(int)
case string:
return i.(string) + i.(string)
}
return i
}
isl := []any{0, 1, 2, 3, 4, 5}
res1 := mapFunc(mf, isl)
for _, v := range res1 {
fmt.Println(v)
}
println()
ssl := []any{"0", "1", "2", "3", "4", "5"}
res2 := mapFunc(mf, ssl)
for _, v := range res2 {
fmt.Println(v)
}
}
func mapFunc(mf func(any) any, list []any) []any {
result := make([]any, len(list))
for ix, v := range list {
result[ix] = mf(v)
}
return result
}
练习 11.12:map_function_interface_var.go:
稍微改变练习 11.11,允许 mapFunc() 接收不定数量的 items。
package main
import "fmt"
type any interface{}
func main() {
// define a generic lambda function mf:
mf := func(i any) any {
switch i.(type) {
case int:
return i.(int) * i.(int)
case string:
return i.(string) + i.(string)
}
return i
}
res1 := mapFunc(mf, 1, 2, 3, 4, 5, 6, 7)
for _, v := range res1 {
fmt.Println(v)
}
println()
res2 := mapFunc(mf, "1", "2", "3", "4", "5", "6")
for _, v := range res2 {
fmt.Println(v)
}
}
func mapFunc(mf func(any) any, list ...any) []any {
result := make([]any, len(list))
for ix, v := range list {
result[ix] = mf(v)
}
return result
}
在这里插入图片描述
练习 11.13:main_stack.go—stack/stack_general.go:
在练习 10.16 和 10.17 中我们开发了一些栈结构类型。但是它们被限制为某种固定的内建类型。现在用一个元素类型是 interface{}(空接口)的切片开发一个通用的栈类型。
实现下面的栈方法:
Len() int
IsEmpty() bool
Push(x interface{})
Pop() (interface{}, error)
Pop() 改变栈并返回最顶部的元素;Top() 只返回最顶部元素。
在主程序中构建一个充满不同类型元素的栈,然后弹出并打印所有元素的值。
package main
import "fmt"
type any interface{}
type tank struct {
val []any
len int
}
func (this *tank) Len() int {
return this.len
}
func (this *tank) IsEmpty() bool {
if this.len == 0 {
return true
} else {
}
}
func (this *tank) Push(x any) {
this.val = append(this.val, x)
this.len++
}
func (this *tank) Pop() any {
res := this.val[this.len-1]
this.len--
return res
}
func main() {
var t tank
fmt.Println(t.IsEmpty())
fmt.Println(t.Len())
t.Push("asdasd")
t.Push(1)
t.Push(1.4546)
t.Push(rune('a'))
fmt.Println(t.IsEmpty())
fmt.Println(t.Len())
for i := 0; i < len(t.val); i++ {
fmt.Println(t.Pop())
}
}