Go学习笔记
- 接口
- 接口定义方法
- 练习 11.1 simple_interface.go:
- 练习 11.2 interfaces_poly2.go:
- 接口嵌套接口
- 如何判断接口类型
- 方法一varI.(T)
- 方法二type-switch
- 练习 11.4 simple_interface2.go:
- 测试一个值是否实现了某个接口
- 接口方法设计的注意事项和规范
- 练习 11.5 interfaces_ext.go:
- 练习 11.6 point_interfaces.go:
- 练习 11.7 float_sort.go / float_sortmain.go:
- 练习 11.8 sort.go / sort_persons.go:
- 空接口
- 练习 11.9 simple_interface3.go:
- 用空接口构建通用类型或包含不同类型变量的数组
- 练习 11.10 min_interface.go / minmain.go:
- 反射
- 通过反射修改(设置)值
- 反射结构
- 练习 11.11:map_function_interface.go:
- 练习 11.12:map_function_interface_var.go:
- 练习 11.13:main_stack.go—stack/stack_general.go:
接口
接口定义方法
接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
例子:
type Namer interface {Method1(param_list) return_typeMethod2(param_list) return_type...
}
练习 11.1 simple_interface.go:
定义一个接口 Simpler,它有一个 Get() 方法和一个 Set(),Get() 返回一个整型值,Set() 有一个整型参数。创建一个结构体类型 Simple 实现这个接口。
接着定一个函数,它有一个 Simpler 类型的参数,调用参数的 Get() 和 Set() 方法。在 main 函数里调用这个函数,看看它是否可以正确运行。
package mainimport ("fmt"
)type Simpler interface {get() intset(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 = ireturn s
}
func main() {var si Simplersi = ss{1}show(si)
}
练习 11.2 interfaces_poly2.go:
a) 扩展 interfaces_poly.go 中的例子,添加一个 Circle 类型
b) 使用一个抽象类型 Shape(没有字段) 实现同样的功能,它实现接口 Shaper,然后在其他类型里内嵌此类型。扩展 10.6.5 中的例子来说明覆写。
package mainimport "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 stringshape
}func main() {c := circle{"circle", shape{}}c.say(c.name)
}
接口嵌套接口
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
比如接口 File 包含了 ReadWrite 和 Lock 的所有方法,它还额外有一个 Close() 方法。
type ReadWrite interface {Read(b Buffer) boolWrite(b Buffer) bool
}type Lock interface {Lock()Unlock()
}type File interface {ReadWriteLockClose()
}
如何判断接口类型
方法一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 assertionProcess(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 mainimport ("fmt"
)type Simpler interface {get() intset(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 Simplers := Simple{1}s1 := Rsimple{2}ss = spanduan(ss)ss = s1panduan(ss)}
测试一个值是否实现了某个接口
假定 v 是一个值,然后我们想测试它是否实现了 Stringer 接口,可以这样做:
func main() {var ss Simplers := Simple{1}s1 := Rsimple{2}ss = spanduan(ss)ss = s1panduan(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 mainimport ("fmt"
)type Simpler interface {get() intset(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 Simplers := new(Simple)s.i = 1s1 := new(Rsimple)s1.i = 2ss = spanduan(ss)ss = s1panduan(ss)
}
练习 11.5 interfaces_ext.go:
a). 继续扩展程序,定义类型 Triangle,让它实现 AreaInterface 接口。通过计算一个特定三角形的面积来进行测试(三角形面积=0.5 * (底 * 高))
b). 定义一个新接口 PeriInterface,它有一个 Perimeter 方法。让 Square 实现这个接口,并通过一个 Square 示例来测试它。
package mainimport "fmt"type AreaInterface interface {calArea() float64
}
type PeriInterface interface {calPeri() float64
}
type Triangle struct {high intwidth int
}func (a *Triangle) calArea() float64 {res := float64(a.high*a.width) / 2return res
}
func (a *Triangle) calPeri() float64 {res := float64(a.high+a.width) * 2return res
}type Square struct {high intwidth 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) * 2return res
}
func main() {var a AreaInterfacevar b PeriInterfacet1 := Triangle{5, 10}t2 := Square{5, 10}a = &t1fmt.Print(a.calArea(), "\n")a = &t2fmt.Print(a.calArea(), "\n")b = &t1fmt.Print(b.calPeri(), "\n")b = &t2fmt.Print(b.calPeri(), "\n")}
练习 11.6 point_interfaces.go:
继续 10.3 中的练习 point_methods.go,定义接口 Magnitude,它有一个方法 Abs()。让 Point、Point3 及 Polar 实现此接口。通过接口类型变量使用方法做 point.go 中同样的事情。
package mainimport ("fmt""math"
)type Magnitude interface {Abs() float64Scale(flag float64)
}
type Point3d struct {x inty intz 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 * flagcosx := float64(p.x) / lenscosy := float64(p.y) / lenscosz := float64(p.z) / lensp.x = int(len_S * cosx)p.y = int(len_S * cosy)p.z = int(len_S * cosz)}type Point2d struct {x inty 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 * flagcosx := float64(p.x) / lenscosy := float64(p.y) / lensp.x = int(len_S * cosx)p.y = int(len_S * cosy)
}type Polar struct {l float64cos float64
}func (p *Polar) Abs() float64 {return p.l
}
func (p *Polar) Scale(flag float64) {p.l *= flag
}
func main() {var m Magnitudep2 := new(Point2d)p2.x, p2.y = 5, 5p3 := new(Point3d)p3.x, p3.y, p3.z = 5, 5, 5p := new(Polar)p.l, p.cos = 5, 0.6m = p2res := m.Abs()fmt.Print(res, "\n")m.Scale(2)res = m.Abs()fmt.Print(res, "\n")m = p3res = m.Abs()fmt.Print(res, "\n")m.Scale(2)res = m.Abs()fmt.Print(res, "\n")m = pres = 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 float64import ("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 mainimport (float642 "awesomeProject/float64""fmt""sort"
)func main() {var ff float642.Float64Arrayff.Fill()fmt.Printf("%v\n", ff)var ff1 float642.Float64Arrayff1.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 mainimport ("fmt""sort""strings"
)type Person struct {firstName stringlastName string
}
type Persons []Personfunc (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 mainimport ("fmt"
)type any interface {
}func gl(a any) {if v, ok := a.(Simpler); ok {show(v)}
}type Simpler interface {get() intset(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 = ireturn s
}
func main() {var si Simplersi = ss{1}var a anya = sigl(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 mainimport ("fmt""strings"
)type Element interface {
}
type Miner interface {Len() intSwap(i, j int)Less(i, j int) boolAt(i int) ElementSet(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 falsecase float64:return falsecase 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 truecase float64:return truecase 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 falsecase int:return truecase 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 falsecase int:return truecase 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 intvar m MyInt = 5v := 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 mainimport ("fmt""reflect"
)func main() {var x float64 = 3.4v := reflect.ValueOf(x)// setting a value:// v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable valuefmt.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 mainimport ("fmt""reflect""strconv"
)type test struct {s1, s2, s3 string
}
type Test struct {S1, S2, S3 string
}
type any interface {
}func main() {var any1 anyany1 = 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 mainimport "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 mainimport "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 mainimport "fmt"type any interface{}
type tank struct {val []anylen 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 tankfmt.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())}
}