一. go方法
go方法:在函数的func和函数名间增加一个特殊的接收器类型,接收器可以是结构体类型或非结构体类型。接收器可以在方法内部访问。创建一个接收器类型为Type的methodName方法。
func (t Type) methodName(parameter list) {
}
go引入方法的原因:
1)go不是纯粹的面向对象编程语言,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。
2)相同名字的方法可以定义在不同的类型上,而相同名字的函数不被允许。
方法调用
t.methodName(parameter list)
指针接收器与值接收器
区别:指针接收器的方法内部的改变对外可见,而值接收器不会改变方法外部的变量。
对于指针接收器&T Type而言,(&T).methodName与T.methodName等价。
匿名字段的方法
属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。
在方法中使用值接收器 与 在函数中使用值参数
当一个函数有一个值参数,它只能接受一个值参数。
当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
package main import "fmt" type rectangle struct { length int width int } func area(r rectangle){ fmt.Printf("Area Function result: %d ", (r.length * r.width)) } func (r rectangle)area(){ fmt.Printf("Area method result: %d ", (r.length * r.width)) } func main(){ r := rectangle{ length: 10, 5, } area(r) r.area() p := &r // area(p) // cannot use p (type *rectangle) as type rectangle in argument to area p.area() //通过指针调用接收器 }
在方法中使用指针接收器 与 在函数中使用指针参数
函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接收器和指针接收器。
在非结构体上的方法
为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。
对于内建类型,如int,应该在文件中创建一个类型别名,然后创建一个以该类型别名为接收器的方法。
二. go接口
接口是方法(方法签名,method signature)的集合。当一个类型定义了接口中的所有方法,就称它实现了该接口。与OOP类似,接口定义了一个类型应该具有的方法,由该类型决定如何实现这些方法。
type myInterface interface{ method1() method2() }
接口调用
//interface definition type VowelsFinder interface { FindVowels() []rune } type MyString string //MyString implements VowelsFinder func (ms MyString) FindVowels() []rune { var vowels []rune for _, rune := range ms { if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' { vowels = append(vowels, rune) } } return vowels } name := MyString("Sam Anderson") var v VowelsFinder v = name // possible since MyString implements VowelsFinder fmt.Printf("Vowels are %c", v.FindVowels())
如果一个类型包含了接口中声明的所有方法,那么它就隐式地实现了 Go 接口。
接口的内部表示
可以把接口看作内部的一个元组 (type, value)
。 type
是接口底层的具体类型(Concrete Type),而 value
是具体类型的值。
type Test interface { Tester() } type MyFloat float64 func (m MyFloat) Tester() { fmt.Println(m) } func describe(t Test) { fmt.Printf("Interface type %T value %v ", t, t) } func main() { var t Test f := MyFloat(89.7) t = f describe(t) t.Tester() }
输出:
Interface type main.myFloat value 89.7 89.7
空接口
没有包含方法的接口称为空接口。空接口表示为 interface{}
。由于空接口没有方法,因此所有类型都实现了空接口。
当指定参数为空接口时,可以接收任意类型,那如何获取参数的值呢? 通过类型断言。 v, ok := p.(int),判定参数是否为int并获取参数值。
类型断言
类型断言用于提取接口的底层值(Underlying Value)。
在语法 i.(T) 中,接口 i 的具体类型是 T,该语法用于获得接口的底层值。
v, ok := i.(T)
如果 i 的具体类型是 T,那么 v 赋值为 i 的底层值,而 ok 赋值为 true。
如果 i 的具体类型不是 T,那么 ok 赋值为 false,v 赋值为 T 类型的零值,此时程序不会报错。
类型选择(Type Switch)
类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。
类型断言的语法是 i.(type),获取接口的类型
还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较。
type Describer interface { Describe() } type Person struct { name string age int } func (p Person) Describe(){ fmt.Printf("%s is %d years old ", p.name, p.age) } func findType(i interface{}){ switch v := i.(type){ case Describer: v.Describe() default: fmt.Printf("unknown type ") } } func main(){ findType("wang") p := Person{ name: "qing", age: 25, } findType(p) } unknown type qing is 25 years old
在上面程序中,结构体 Person
实现了 Describer
接口。在第 19 行的 case 语句中,v
与接口类型 Describer
进行了比较。p
实现了 Describer
,因此满足了该 case 语句,于是当程序运行到第 32 行的 findType(p)
时,程序调用了 Describe()
方法。
实现接口:指针接受者与值接受者
使用值接受者声明的方法,接口既可以用值来调用,也能用指针调用。
对于使用指针接受者的方法,必须用一个指针或者一个可取得地址的值(&method)来调用。但接口中存储的具体值(Concrete Value)并不能取到地址,对于编译器无法自动获取 a 的地址,于是程序报错。
type Describer interface { Describe() } type Person struct { name string age int } func (p Person) Describe() { // 使用值接受者实现 fmt.Printf("%s is %d years old ", p.name, p.age) } type Address struct { state string country string } func (a *Address) Describe() { // 使用指针接受者实现 fmt.Printf("State %s Country %s", a.state, a.country) } func main() { var d1 Describer p1 := Person{"Sam", 25} d1 = p1 d1.Describe() p2 := Person{"James", 32} d1 = &p2 d1.Describe() var d2 Describer a := Address{"Washington", "USA"} /* 如果下面一行取消注释会导致编译错误: cannot use a (type Address) as type Describer in assignment: Address does not implement Describer (Describe method has pointer receiver) */ //d2 = a d2 = &a // 这是合法的 // 因为在第 22 行,Address 类型的指针实现了 Describer 接口 d2.Describe() }
接口的嵌套
type SalaryCalculator interface { DisplaySalary() } type LeaveCalculator interface { CalculateLeavesLeft() int } type EmployeeOperations interface { SalaryCalculator LeaveCalculator }
接口的零值
接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil。对于值为 nil 的接口,由于没有底层值和具体类型,当我们试图调用它的方法时,程序会产生 panic 异常。
Go接口最佳实践
1)倾向于使用小的接口定义,很多接口只包含一个方法。 如Reader,Writer,便于类型实现接口,方法太多,类型实现越麻烦。
2)较大的接口定义,可以由多个小接口定义组合而成。 即接口的嵌套。
3)只依赖于必要功能的最小接口。方法或函数的接口参数的范围或方法越小越好,这样便于参数的调用,和方法或函数被其他程序调用。
如func StoreData(reader Reader) error{},能传递Reader就不传递ReadWriter。