一、接口定义
1.接口提供了一种方式来说明对象的行为:如果谁能搞定这件事,它就可以用在这儿。接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的),接口里也不能包含变量。通过如下格式定义接口:
type Namer interface { // Namer 是一个接口类型
Method1(param_list) return_type
Method2(param_list) return_type
...
}
2.Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法,(按照约定,只包含一个方法的)接口的名字由方法名加 [e]r 后缀组成,例如Printer 、 Reader 、 Writer 、 Logger 等等。还有一些不常用的方式(当后缀 er 不合适时),比如Recoverable ,此时接口名以 able 结尾,或者以 I 开头(像 .NET 或 Java 中那样)。
3.Go 语言中接口可以有值,一个接口类型的变量或一个接口值 : var ai Namer , ai 是一个多字(multiword)数据结构,它的值是 nil ,它本质上是一个指针。。实现了 Namer 接口类型的变量可以赋值给 ai (接收者值),此时方法表中的指针会指向被实现的接口方法。当然如果另一个类型(也实现了该接口)的变量被赋值给 ai ,这二者(指针和方法实现)也会随之改变。
4.类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。实现某个接口的类型(除了实现接口方法外)可以有其他的方法。一个类型可以实现多个接口。接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。所有这些特性使得接口具有很大的灵活性。
package main
import "fmt"
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (sq *Square) Zhouchang() float32{
return sq.side*4
}
func main() {
sq1 := new(Square)
sq1.side = 5
var areaIntf Shaper
areaIntf = sq1
// shorter,without separate declaration:
// areaIntf := Shaper(sq1)
// or even:
// areaIntf := sq1
fmt.Printf("The square has area: %f\n,周长是:%f", areaIntf.Area(),sq1.Zhouchang())
}
5.有两个类型 stockPosition 和 car ,它们都有一个 getValue() 方法,我们可以定义一个具有此方法的接口 valuable 。接着定义一个使用 valuable 类型作为参数的函数 showValue() ,所有实现了valuable 接口的类型都可以用这个函数。
package main
import "fmt"
type stockPosition struct {
ticker string
sharePrice float32
count float32
}
/* method to determine the value of a stock position */
func (s stockPosition) getValue() float32 {
return s.sharePrice * s.count
}
type car struct {
make string
model string
price float32
}
/* method to determine the value of a car */
func (c car) getValue() float32 {
return c.price
}
/* contract that defines different things that have value */
type valuable interface {
getValue() float32
}
func showValue(asset valuable) {
fmt.Printf("Value of the asset is %f\n", asset.getValue())
}
func main() {
o := valuable( stockPosition{"GOOG", 577.20, 4})
//var o valuable = stockPosition{"GOOG", 577.20, 4}
showValue(o)
o = car{"BMW", "M3", 66500}
showValue(o)
}
二、接口嵌套接口
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。比如接口 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()
}
三、类型断言:如何检测和转换接口变量的类型
1.一个接口类型的变量 varI 中可以包含任何类型的值,必须有一种方式来检测它的 动态 类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用 类型断言 来测试在某个时刻 varI 是否包含类型 T 的值:v := varI.(T) // unchecked type assertion。更安全的方式是使用以下形式来进行类型断言:
if v, ok := varI.(T); ok { // checked type assertion
Process(v)
return
}
// varI is not of type T
如果转换合法, v 是 varI 转换到类型 T 的值, ok 会是 true ;否则 v 是类型 T 的零值, ok 是 false ,也没有运行时错误发生。应该总是使用上面的方式来进行类型断言。多数情况下,我们可能只是想在 if 中测试一下 ok 的值,此时使用以下的方法会是最方便的:
if _, ok := varI.(T); ok {
// ...
}
2.程序中定义了一个新类型 Circle ,它也实现了 Shaper 接口。 if t, ok := areaIntf.(*Square); ok 测试 areaIntf里是否有一个包含 *Square 类型的变量,结果是确定的;然后我们测试它是否包含一个 *Circle 类型的变量,结果是否定的。
package main
import (
"fmt"
"math"
)
type Square struct {
side float32
}
type Circle struct {
radius float32
}
type Shaper interface {
Area() float32
}
func main() {
var areaIntf Shaper
sq1 := new(Square)
sq1.side = 5
areaIntf = sq1
// Is Square the type of areaIntf?
if t, ok := areaIntf.(*Square); ok {
fmt.Printf("The type of areaIntf is: %T\n", t)
}
if u, ok := areaIntf.(*Circle); ok {
fmt.Printf("The type of areaIntf is: %T\n", u)
} else {
fmt.Println("areaIntf does not contain a variable of type Circle")
}
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (ci *Circle) Area() float32 {
return ci.radius * ci.radius * math.Pi
}
四、类型判断:type-switch
1.接口变量的类型也可以使用一种特殊形式的 switch 来检测:type-switch。变量 t 得到了 areaIntf 的值和类型, 所有 case 语句中列举的类型( nil 除外)都必须实现对应的接口(在上例中即 Shaper ),如果被检测类型没有在 case 语句列举的类型中,就会执行 default 语句。可以用 type-switch 进行运行时类型分析,但是在 type-switch 不允许有 fallthrough 。如果仅仅是测试变量的类型,不用它的值,那么就可以不需要赋值语句,比如:
switch areaIntf.(type) {
case *Square:
// TODO
case *Circle:
// TODO
...
default:
// TODO
}
2.代码片段展示了一个类型分类函数,它有一个可变长度参数,可以是任意类型的数组,它会根据数组元素的实际类型执行不同的动作:
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool:
fmt.Printf("Param #%d is a bool\n", i)
case float64:
fmt.Printf("Param #%d is a float64\n", i)
case int, int64:
fmt.Printf("Param #%d is a int\n", i)
case nil:
fmt.Printf("Param #%d is a nil\n", i)
case string:
fmt.Printf("Param #%d is a string\n", i)
default:
fmt.Printf("Param #%d is unknown\n", i)
}
}
}
可以这样调用此方法: classifier(13, -14.3, "BELGIUM", complex(1, 2), nil, false) 。在处理来自于外部的、类型未知的数据时,比如解析诸如 JSON 或 XML 编码的数据,类型测试和转换会非常有用。
五、测试一个值是否实现了某个接口
假定 v 是一个值,然后我们想测试它是否实现了 Stringer 接口,可以这样做:
type Stringer interface {
String() string
}
if sv, ok := v.(Stringer); ok {
fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}
Print 函数就是如此检测类型是否可以打印自身的。接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。编写参数是接口变量的函数,这使得它们更具有一般性。使用接口使代码更具有普适性。
六、使用方法集与接口
作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的在 lst 上调用 CountInto 时会导致一个编译器错误,因为 CountInto 需要一个 Appender ,而它的方法 Append 只定义在指针上。 在 lst 上调用 LongEnough 是可以的,因为 Len 定义在值上。在 plst 上调用 CountInto 是可以的,因为 CountInto 需要Appender ,并且它的方法 Append 定义在指针上。在 plst 上调用 LongEnough 也是可以的,因为指针会被自动解引用。
package main
import (
"fmt"
)
type List []int
func (l List) Len() int {
return len(l)
}
func (l *List) Append(val int) {
*l = append(*l, val)
}
type Appender interface {
Append(int)
}
func CountInto(a Appender, start, end int) {
for i := start; i <= end; i++ {
a.Append(i)
}
}
type Lener interface {
Len() int
}
func LongEnough(l Lener) bool {
return l.Len()*10 > 42
}
func main() {
// A bare value
var lst List
// compiler error:
// cannot use lst (type List) as type Appender in argument to CountInto:
// List does not implement Appender (Append method has pointer receiver)
// CountInto(lst, 1, 10)
if LongEnough(lst) { // VALID:Identical receiver type
fmt.Printf("- lst is long enough\n")
}
// A pointer value
plst := new(List)
CountInto(plst, 1, 10) //VALID:Identical receiver type
if LongEnough(plst) {
// VALID: a *List can be dereferenced for the receiver
fmt.Printf("- plst is long enough\n")
}
}
总结:在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以从具体类型 P 直接可以辨识的:指针方法可以通过指针调用、值方法可以通过值调用、接收者是值的方法可以通过指针调用,因为指针会首先被解引用、接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址、将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
注:Go 语言规范定义了接口方法集的调用规则:类型 T 的可调用方法集包含接受者为 T 或 T 的所有方法集、类型 T 的可调用方法集包含接受者为 T 的所有方法、类型 T 的可调用方法集不包含接受者为 *T 的方法。
七、 第一个例子:使用 Sorter 接口排序
例子是来自标准库的 sort 包,要对一组数字或字符串排序,只需要实现三个方法:反映元素个数的Len() 方法、比较第 i 和 j 个元素的 Less(i, j) 方法以及交换第 i 和 j 个元素的 Swap(i, j) 方法。排序函数的算法只会使用到这三个方法(可以使用任何排序算法来实现,此处我们使用冒泡排序):
//sort.go
package sort
type Sorter interface {
Len() int //长度
Less(i, j int) bool //比较
Swap(i, j int) //交换
}
func Sort(data Sorter) { //冒泡排序从大到小
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 IsSorted(data Sorter) bool { //检查是否以排序,从大到小
n := data.Len()
for i := n - 1; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
return true
}
// Convenience types for common cases
type IntArray []int //针对int切片
func (p IntArray) Len() int { return len(p) } //返回int长度
func (p IntArray) Less(i, j int) bool { return p[i] < p[j] } //索引对应的两个值大小布尔值
func (p IntArray) Swap(i, j int) { p[i], p[j] = p[j], p[i] } //交换两值
type StringArray []string
func (p StringArray) Len() int { return len(p) }
func (p StringArray) Less(i, j int) bool { return p[i] < p[j] }
func (p StringArray) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Convenience wrappers for common cases
func SortInts(a []int) { Sort(IntArray(a)) } //int切片排序
func SortStrings(a []string) { Sort(StringArray(a)) }
func IntsAreSorted(a []int) bool { return IsSorted(IntArray(a)) } //检查是否排序
func StringsAreSorted(a []string) bool { return IsSorted(StringArray(a)) }
函数 Sort(data Interface) 用来对此类对象进行排序,可以用它们来实现对其他类型的数据(非基本类型)进行排序。在例子中,我们也是这么做的,不仅可以对 int 和 string 序列进行排序,也可以对用户自定义类型 dayArray 进行排序。
//sortmain.go
package sort
import (
"./sort"
"fmt"
)
func ints() {
data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
a := sort.IntArray(data) //conversion to type IntArray
sort.Sort(a)
if !sort.IsSorted(a) {
panic("fails")
}
fmt.Printf("The sorted array is: %v\n", a)
}
func strings() {
data := []string{"monday", "friday", "tuesday", "wednesday", "sunday", "thursday", "", "saturday"}
a := sort.StringArray(data)
sort.Sort(a)
if !sort.IsSorted(a) {
panic("fail")
}
fmt.Printf("The sorted array is: %v\n", a)
}
type day struct {
num int
shortName string
longName string
}
type dayArray struct {
data []*day
}
func (p *dayArray) Len() int { return len(p.data) }
func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num }
func (p *dayArray) Swap(i, j int) { p.data[i], p.data[j] = p.data[j], p.data[i] }
func days() {
Sunday := day{0, "SUN", "Sunday"}
Monday := day{1, "MON", "Monday"}
Tuesday := day{2, "TUE", "Tuesday"}
Wednesday := day{3, "WED", "Wednesday"}
Thursday := day{4, "THU", "Thursday"}
Friday := day{5, "FRI", "Friday"}
Saturday := day{6, "SAT", "Saturday"}
data := []*day{&Tuesday, &Thursday, &Wednesday, &Sunday, &Monday, &Friday, &Saturday}
a := dayArray{data}
sort.Sort(&a)
if !sort.IsSorted(&a) {
panic("fail")
}
for _, d := range data {
fmt.Printf("%s ", d.longName)
}
fmt.Printf("\n")
}
func main() {
ints()
strings()
days()
}
八、第二个例子:读和写
只要类型实现了读写接口,提供 Read() 和 Write 方法,就可以从它读取数据,或向它写入数据。一个对象要是可读的,它必须实现 io.Reader 接口,这个接口只有一个签名是 Read(p []byte) (n int, err error) 的方法,它从调用它的对象上读取数据,并把读到的数据放入参数中的字节切片中,然后返回读取的字节数和一个 error 对象,如果没有错误发生返回 nil ,如果已经到达输入的尾端,会返回 io.EOF("EOF") ,如果读取的过程中发生了错误,就会返回具体的错误信息。类似地,一个对象要是可写的,它必须实现 io.Writer 接口,这个接口也只有一个签名是 Write(p[]byte) (n int, err error) 的方法,它将指定字节切片中的数据写入调用它的对象里,然后返回实际写入的字节数一个 error 对象(如果没有错误发生就是 nil )。io 包里的 Readers 和 Writers 都是不带缓冲的, bufio 包里提供了对应的带缓冲的操作,在读写 UTF-8 编码的文本文件时它们尤其有用。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
九、空接口
1.空接口或者最小接口 不包含任何方法,它对实现不做任何要求:type Any interface {},任何其他类型都实现了空接口, any 或 Any 是空接口一个很好的别名或缩写。可以给一个空接口类型的变量 var val interface {} 赋任何类型的值。
package main
import "fmt"
type specialString string
var whatIsThis specialString = "hello"
func TypeSwitch() {
testFunc := func(any interface{}) {
switch v := any.(type) {
case bool:
fmt.Printf("any %v is a bool type", v)
case int:
fmt.Printf("any %v is an int type", v)
case float32:
fmt.Printf("any %v is a float32 type", v)
case string:
fmt.Printf("any %v is a string type", v)
case specialString:
fmt.Printf("any %v is a special String!", v)
default:
fmt.Println("unknown type!")
}
}
testFunc(whatIsThis)
}
func main() {
TypeSwitch()
}
2.构建通用类型或包含不同类型变量的数组:通过使用空接口,我们给空接口定一个别名类型 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:拆箱)需要用到类型断言。
3.复制数据切片至空接口切片:必须使用 for-range 语句来一个一个显式地复制:
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}
4.通用类型的节点数据结构:节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 NewNode 方法,及设置数据的 SetData 方法。
package main
import "fmt"
type Node struct {
le *Node
data interface{}
ri *Node
}
func NewNode(left, right *Node) *Node {
return &Node{left, nil, right}
}
func (n *Node) SetData(data interface{}) {
n.data = data
}
func main() {
root := NewNode(nil, nil)
root.SetData("root node")
// make child (leaf) nodes:
a := NewNode(nil, nil)
a.SetData("left node")
b := NewNode(nil, nil)
b.SetData("right node")
root.le = a
root.ri = b
fmt.Printf("%v\n", root) // Output: &{0x125275f0 root node 0x125275e0}
}
5.接口到接口:一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法。这个转换是在运行时进行检查的,转换失败会导致一个运行时错误:这是 Go 语言动态的一面,可以拿它和 Ruby 和 Python 这些动态语言相比较。假定:
var ai AbsInterface // declares method Abs()
type SqrInterface interface {
Sqr() float
}
var si SqrInterface
pp := new(Point) // say *Point implements Abs, Sqr
var empty interface{}
那么下面的语句和类型断言是合法的:
empty = pp // 空接口满足一切接口
ai = empty.(AbsInterface) //接口赋值到具有相同底层方法接口
// (runtime failure otherwise)
si = ai.(SqrInterface) // 即使没有该接口方法,也会赋值sqr()
empty = si // *Point implements 是空接口
// Note: statically checkable so type assertion not necessary.
下面是函数调用的一个例子:
type myPrintInterface interface {
print()
}
func f3(x myInterface) {
x.(myPrintInterface).print() // type assertion to myPrintInterface
}
x 转换为 myPrintInterface 类型是完全动态的:只要 x 的底层类型(动态类型)定义了 print 方法这个调用就可以正常运行。
十、 反射包
1.方法和类型的反射:反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如它的大小、方法和动态的调用这些方法。
2.变量的最基本信息就是类型和值:反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。两个简单的函数, reflect.TypeOf 和 reflect.ValueOf ,返回被检查对象的类型和值。例如,x 被定义为: var xfloat64 = 3.4 ,那么 reflect.TypeOf(x) 返回 float64 , reflect.ValueOf(x) 返回
3.反射可以从接口值反射到对象,也可以从对象反射回接口值。reflect.Type 和 reflect.Value 都有许多方法用于检查和操作它们。一个重要的例子是 Value 有一个 Type 方法返回reflect.Value 的 Type。另一个是 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。同样 Value 有叫做 Int 和 Float 的方法可以获取存储在内部的值(跟 int64 和 float64 一样)。
4.Kind 总是返回底层类型:
type MyInt int
var m MyInt = 5
v := reflect.ValueOf(m)
方法 v.Kind() 返回 reflect.Int 。变量 v 的 Interface() 方法可以得到还原(接口)值,所以可以这样打印 v 的值: fmt.Println(v.Interface())。
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x)) //变量类型
v := reflect.ValueOf(x) //变量值
fmt.Println("value:", v)
fmt.Println("type:", v.Type()) //类型
fmt.Println("kind:", v.Kind()) //类型
fmt.Println("value:", v.Float()) //float值
fmt.Println(v.Interface()) //接口值
fmt.Printf("value is %5.2e\n", v.Interface())
y := v.Interface().(float64) //float64打印接口值
fmt.Println(y)
}
5.通过反射修改(设置)值:Value 有一些方法可以完成这个任务,但是必须小心使用: v.SetFloat(val) 。是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性:可以使用 CanSet() 方法测试是否可设置。在例子中我们看到 v.CanSet() 返回 false: settability of v: false。 v := reflect.ValueOf(x) 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 v = reflect.ValueOf(&x) 。通过 Type() 我们看到 v 现在的类型是 *float64 并且仍然是不可设置的。要想让其可设置我们需要使用 Elem() 函数,这间接的使用指针: v = v.Elem()。
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x) //生成reflect对象
// 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: 必须传入指针地址,而不是拷贝
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) // 更改属性
fmt.Println(v.Interface()) //检查接口值
fmt.Println(v)
}
6.反射结构:反射一个结构类型。结构中只有被导出字段(首字母大写)才是可设置的, NumField() 方法返回结构内的字段数量;通过一个 for 循环用索引取得每个字段的值 Field(i) 。我们同样能够调用签名在结构上的方法,例如,使用索引 n 来调用: Method(n).Call(nil) 。
package main
import (
"fmt"
"reflect"
)
type NotknownType struct { //结构
s1, s2, s3 string
}
func (n NotknownType) String() string { //结构具有的方法
return n.s1 + " - " + n.s2 + " - " + n.s3
}
// 空接口赋值
var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
func main() {
value := reflect.ValueOf(secret) //
typ := reflect.TypeOf(secret) // main.NotknownType 类型
// alternative:
//typ := value.Type() // main.NotknownType
fmt.Println(typ)
knd := value.Kind() // 接口值的种类
fmt.Println(knd)
// iterate through the fields of the struct:
for i := 0; i < value.NumField(); i++ { //遍历属性
fmt.Printf("Field %d: %v\n", i, value.Field(i)) //具体属性
// error: panic: reflect.Value.SetString using value obtained using unexported field
//value.Field(i).SetString("C#")
}
// call the first method, which is String():
results := value.Method(0).Call(nil) //调用第一个接口方法,传入0参
fmt.Println(results) // [Ada - Go - Oberon]
}
结构中设置值示例:
package main
import (
"fmt"
"reflect"
)
type T struct {
A int
B string
}
func main() {
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t) //t is now {77 Sunset Strip}
}
十一、Printf 和反射
1.在Go 语言的标准库中,反射的功能被大量地使用。fmt 包中的 Printf(以及其他格式化输出函数)都会使用反射来分析它的 ... 参数。
Printf 的函数声明为:func Printf(format string, args ... interface{}) (n int, err error)。Printf 中的 ... 参数为空接口类型。Printf 使用反射包来解析这个参数列表。所以,Printf 能够知道它每个参数的类型。因此格式化字符串中只有%d而没有 %u 和 %ld,因为它知道这个参数是 unsigned 还是 long。这也是为什么 Print 和Println 在没有格式字符串的情况下还能如此漂亮地输出。
2.使用print函数,参数为空接口,不需要输入参数类型,示例。
package main
import (
"os"
"strconv"
)
type Stringer interface { //字符串转换接口
String() string
}
type Celsius float64 //浮点数
func (c Celsius) String() string { //变量实现字符串转换接口
//将浮点数转换字符串,'f'(-ddd.dddd),prec控制精度(排除指数部分):对'f'表示小数点后的数字个数;bitSize表示f的来源类型(32:float32、64:float64
return strconv.FormatFloat(float64(c),'f', 1, 64) + " °C"
}
type Day int
var dayName = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
func (day Day) String() string {
return dayName[day]
}
func print(args ...interface{}) { //打印函数,参数为。。。空接口
for i, arg := range args {
if i > 0 {os.Stdout.WriteString(" ")}
switch a := arg.(type) { // type switch //针对不同种类采取不同方式打印
case Stringer: os.Stdout.WriteString(a.String())
case int: os.Stdout.WriteString(strconv.Itoa(a))
case string: os.Stdout.WriteString(a)
// more types
default: os.Stdout.WriteString("???")
}
}
}
func main() {
print(Day(1), "was", Celsius(18.36)) // Tuesday was 18.4 °C
}
十二、接口与动态类型
1.Go 的动态类型:Go 是唯一结合了接口值,静态类型检查(是否该类型实现了某个接口),运行时动态转换的语言,并且不需要显式地声明类型是否满足某个接口。该特性允许我们在不改变已有的代码的情况下定义和使用新接口。接收一个(或多个)接口类型作为参数的函数,其实参可以是任何实现了该接口的类型。 实现了某个接口的类型可以被传给任何以此接口为参数的函数, 没有实现接口方法不会调用该函数。
package main
import "fmt"
type IDuck interface {
Quack()
Walk()
}
func DuckDance(duck IDuck) {
for i := 1; i <= 3; i++ {
duck.Quack()
duck.Walk()
}
}
type Bird struct {
// ...
}
func (b *Bird) Quack() {
fmt.Println("I am quacking!")
}
func (b *Bird) Walk() {
fmt.Println("I am walking!")
}
func main() {
b := new(Bird)
DuckDance(b)
}
2. 动态方法调用:Go 的实现通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 interface{} 这样的“泛型”上,你可以通过类型断言来检查变量是否实现了相应接口。Go 在这里用了和 gob 相同的机制:定义了两个接口 GobEncoder 和 GobDecoder 。这样就允许类型自己实现从流编解码的具体方式;如果没有实现就使用标准的反射方式。因此 Go 提供了动态语言的优点,却没有其他动态语言在运行时可能发生错误的缺点。你用不同的类型表示 XML 输出流中的不同实体。然后我们为 XML 定义一个如下的“写”接口(甚至可以把它定义为私有接口):
type xmlWriter interface {
WriteXML(w io.Writer) error
}
现在我们可以实现适用于该流类型的任何变量的 StreamXML 函数,并用类型断言检查传入的变量是否实现了该接口;如果没有,我们就调用内建的 encodeToXML 来完成相应工作:
// Exported XML streaming function.
func StreamXML(v interface{}, w io.Writer) error {
if xw, ok := v.(xmlWriter); ok {
// It’s an xmlWriter, use method of asserted type.
return xw.WriteXML(w)
}
// No implementation, so we have to use our own function (with perhaps reflection):
return encodeToXML(v, w)
}
// Internal XML encoding function.
func encodeToXML(v interface{}, w io.Writer) error {
// ...
}
3.接口的提取:提取接口 是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的面向对象语言那样维
护整个的类层次结构。Go 接口可以让开发者找出自己写的程序中的类型。假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行
为,这时就可以创建一个接口来使用。 假设我们需要一个新的接口TopologicalGenus ,用来给 shape 排序(这里简单地实现为返回 int)。我们需要做的是给想要满足接口的类型实现Rank() 方法。
//multi_interfaces_poly.go
package main
import "fmt"
type Shaper interface {
Area() float32
}
type TopologicalGenus interface {
Rank() int
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (sq *Square) Rank() int {
return 1
}
type Rectangle struct {
length, width float32
}
func (r Rectangle) Area() float32 {
return r.length * r.width
}
func (r Rectangle) Rank() int {
return 2
}
func main() {
r := Rectangle{5, 3} // Area() of Rectangle needs a value
q := &Square{5} // Area() of Square needs a pointer
shapes := []Shaper{r, q}
fmt.Println("Looping through shapes for area ...")
for n, _ := range shapes {
fmt.Println("Shape details: ", shapes[n])
fmt.Println("Area of this shape is: ", shapes[n].Area())
}
topgen := []TopologicalGenus{r, q}
fmt.Println("Looping through topgen for rank ...")
for n, _ := range topgen {
fmt.Println("Shape details: ", topgen[n])
fmt.Println("Topological Genus of this shape is: ", topgen[n].Rank())
}
}
4.显式地指明类型实现了某个接口:如果你希望满足某个接口的类型显式地声明它们实现了这个接口,你可以向接口的方法集中添加一个具有描述性名字的方法。例如:
type Fooer interface {
Foo()
ImplementsFooer()
}
类型 Bar 必须实现 ImplementsFooer 方法来满足 Fooer 接口,以清楚地记录这个事实。大部分代码并不使用这样的约束,因为它限制了接口的实用性。但是有些时候,这样的约束在大量相似的接口中被用来解决歧义:
type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}
5.空接口和函数重载: 我们看到函数重载是不被允许的。在 Go 语言中函数重载可以用可变参数 ...T 作为函数最后一个参数来实
现。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口)类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。
函数 fmt.Printf 就是这样做的:
fmt.Printf(format string, a ...interface{}) (n int, errno error)
这个函数通过枚举 slice 类型的实参动态确定所有参数的类型。并查看每个类型是否实现了 String() 方法,如果是就用于产生输出信息。
6.接口的继承:当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的
接口方法。例如:
type Task struct {
Command string
*log.Logger
}
这个类型的工厂方法像这样:
func NewTask(command string, logger *log.Logger) *Task {
return &Task{command, logger}
}
当 log.Logger 实现了 Log() 方法后,Task 的实例 task 就可以调用该方法:task.Log()。类型可以通过继承多个接口来提供像 多重继承 一样的特性:
type ReaderWriter struct {
*io.Reader
*io.Writer
}
7.有用的接口可以在开发的过程中被归纳出来。添加新接口非常容易,因为已有的类型不用变动(仅仅需要实现新接口的方法)。已有的函数可以扩展为使用接口类型的约束性参数:通常只有函数签名需要改变。对比基于类的 OO 类型的语言在这种情况下则需要适应整个类层次结构的变化。
十三、总结
Go 没有类,而是松耦合的类型、方法对接口的实现。
OO 语言最重要的三个方面分别是:封装,继承和多态,在 Go 中它们是怎样表现的呢?
封装(数据隐藏):和别的 OO 语言有 4 个或更多的访问层次相比,Go 把它简化为了 2 层:
1)包范围内的:通过标识符首字母小写, 对象 只在它所在的包内可见;
2)可导出的:通过标识符首字母大写, 对象 对所在包以外也可见。
类型只拥有自己所在包中定义的方法。
继承:用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现;
多态:用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。Go 接口不是 Java 和 C# 接口的变体,而且接口间是不相关的,并且是大规模编程和可适应的演进型设计的关键。
十四、结构体、集合和高阶函数
有点复杂,通常你在应用中定义了一个结构体,那么你也可能需要这个结构体的(指针)对象集合,比如:
type Any interface{}
type Car struct {
Model string
Manufacturer string
BuildYear int
// ...
}
type Cars []*Car
在定义所需功能时我们可以利用函数可以作为(其它函数的)参数的事实来使用高阶函数,例如:
1)定义一个通用的 Process() 函数,它接收一个作用于每一辆 car 的 f 函数作参数:
// Process all cars with the given function f:
func (cs Cars) Process(f func(car *Car)) {
for _, c := range cs {
f(c)
}
}
2)在上面的基础上,实现一个查找函数来获取子集合,并在 Process() 中传入一个闭包执行(这样就可以访问局部切
片 cars ):
// Find all cars matching a given criteria.
func (cs Cars) FindAll(f func(car *Car) bool) Cars {
cars := make([]*Car, 0)
cs.Process(func(c *Car) {
if f(c) {
cars = append(cars, c)
}
})
return cars
}
3)实现 Map 功能,产出除 car 对象以外的东西:
// Process cars and create new data.
func (cs Cars) Map(f func(car *Car) Any) []Any {
result := make([]Any, 0)
ix := 0
cs.Process(func(c *Car) {
result[ix] = f(c)
ix++
})
return result
}
现在我们可以定义下面这样的具体查询:
allNewBMWs := allCars.FindAll(func(car *Car) bool {
return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})
4)我们也可以根据入参返回不同的函数。也许我们想根据不同的厂商添加汽车到不同的集合,但是这可能会是多变
的。所以我们可以定义一个函数来产生特定的添加函数和 map 集:
func MakeSortedAppender(manufacturers[]string)(func(car*Car),map[string]Cars) {
// Prepare maps of sorted cars.
sortedCars := make(map[string]Cars)
for _, m := range manufacturers {
sortedCars[m] = make([]*Car, 0)
}
sortedCars["Default"] = make([]*Car, 0)
// Prepare appender function:
appender := func(c *Car) {
if _, ok := sortedCars[c.Manufacturer]; ok {
sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
} else {
sortedCars["Default"] = append(sortedCars["Default"], c)
}
}
return appender, sortedCars
}
现在我们可以用它把汽车分类为独立的集合,像这样:
manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Jaguar"}
sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
allUnsortedCars.Process(sortedAppender)
BMWCount := len(sortedCars["BMW"])
全部代码注释:
// cars.go
package main
import (
"fmt"
)
type Any interface{} //空接口
type Car struct { //Car数据结构
Model string
Manufacturer string
BuildYear int
// ...
}
type Cars []*Car //car结构指针对象空切片
func main() {
// make some cars:初始化车辆,并取其地址
ford := &Car{"Fiesta", "Ford", 2008}
bmw := &Car{"XL 450", "BMW", 2011}
merc := &Car{"D600", "Mercedes", 2009}
bmw2 := &Car{"X 800", "BMW", 2008}
// query:生成指针对象集合
allCars := Cars([]*Car{ford, bmw, merc, bmw2})
allNewBMWs := allCars.FindAll(func(car *Car) bool {
return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})
fmt.Println("AllCars: ", allCars) //打印所有车
fmt.Println("New BMWs: ", allNewBMWs) //符合条件的车
//
manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Jaguar"}
sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
allCars.Process(sortedAppender) //遍历所有车
fmt.Println("Map sortedCars: ", sortedCars)
BMWCount := len(sortedCars["BMW"]) //打印宝马数量
fmt.Println("We have ", BMWCount, " BMWs")
}
// Process all cars with the given function f: 根据给予的函数处理所有的车
func (cs Cars) Process(f func(car *Car)) {
for _, c := range cs {
f(c)
}
}
// Find all cars matching a given criteria. //根据给与的标准(函数)匹配合适的车辆
func (cs Cars) FindAll(f func(car *Car) bool) Cars {
cars := make([]*Car, 0) //生成长度为0的[]*Car类型的切片
cs.Process(func(c *Car) {
if f(c) { //符合传入函数条件
cars = append(cars, c) //将符合条件车地址添加到cars切片
}
})
return cars
}
// Process cars and create new data.
func (cs Cars) Map(f func(car *Car) Any) []Any {
result := make([]Any, len(cs))
ix := 0
cs.Process(func(c *Car) {
result[ix] = f(c)
ix++
})
return result
}
func MakeSortedAppender(manufacturers []string) (func(car *Car), map[string]Cars) {
// Prepare maps of sorted cars.
sortedCars := make(map[string]Cars) //制造厂商和车辆映射数组
for _, m := range manufacturers {
sortedCars[m] = make([]*Car, 0) //每一个厂商赋值nil指针
}
sortedCars["Default"] = make([]*Car, 0) //default设置为nil
// Prepare appender function:
appender := func(c *Car) { //按照制造厂商将指针分类
if _, ok := sortedCars[c.Manufacturer]; ok {
sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
} else {
sortedCars["Default"] = append(sortedCars["Default"], c)
}
}
return appender, sortedCars
}