方法method
Go没有类,但是能为类型定义方法method
。所谓方法就是一个具有特殊receiver
参数的函数,receiver
在自己的参数列表中,位于func
关键字与函数名之间。method
只是一个有着receiver
的特殊函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
|
你也可以为非struct
的数据类型定义方法,如type MyFloat float64
,但方法和类型必须在同一包下。
pointer receiver
如果需要更改该类型变量的值,那么就要用到pointer receiver
。如果使用值参数,那么就是值传递,其它普通函数也是一样的。
method
不同于一般function
之处在于其receiver
为pointer
时,即可以接受pointer
也可以接受value
的变量的调用,其效果都等同于传入pointer
。同理当method
的receiver
为value
时,不管传入pointer
还是value
,效果都是值传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main import "fmt" type Vertex struct { X, Y float64 } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(2) ScaleFunc(&v, 10) p := &Vertex{4, 3} p.Scale(3) ScaleFunc(p, 8) fmt.Println(v, p) }
|
运行:
而一般的function,其参数类型必须与声明严格一致,否则无法编译。
以上特性叫做pointer indirection
(指针迂回?)。
使用pointer
的receiver
有两个好处,1可以更改其值,2可以避免调用一次copy一次。另外一个type
的method
应该使用同一各类的receiver
,即value
或pointer
,其原因接下来会说明。
interface
接口定义了一组方法的签名。一个接口的value可以存放任何实现了该组接口的值。接上面的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package main import ( "fmt" "math" ) type Abser interface { Abs() float64 } func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat implements Abser a = &v // a *Vertex implements Abser // In the following line, v is a Vertex (not *Vertex) // and does NOT implement Abser. a = v fmt.Println(a.Abs()) } type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
|
在main
中a = v
语句会报错,因为Vertex
类型并没有实现Abser
接口,实现该接口是*Vertex
类型。这一点容易让人迷惑,因为方法具有indirection
特性,但interface
看起来没有。
隐式的接口实现
type只要实现了接口所定义的方法,它就实现了该接口,而无需显式地使用implements
这样的关键字,这样就解耦了接口的定义与实现,甚至可以从一组现有的type
中抽象出一个新的接口(先实现,再定义接口)。
interface value
实质上,接口可以视为值和具体类型(value, type)的元组。interface value
存放着隐式的具体类型的值。调用一个接口的方法则执行该隐式具体类型的同名方法。
这有点“多态”的特性,以下是我自己写的演示程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package main import ("fmt" "math") type Shape interface{ Area() float64 } type Rectangel struct{ des string x,y float64 } type Triangel struct{ des string x,y,a float64 } func (r *Rectangel) Area() float64{ return r.x * r.y } func (t *Triangel) Area() float64{ return t.x * t.y * math.Sin(t.a) * 0.5 } func main(){ var a1 , a2 Shape r := &Rectangel{"Recangel",3,4} t := &Triangel{"Triangel",3,4,math.Pi/2} a1 = r a2 = t fmt.Println(r,t) fmt.Println(a1.Area(),a2.Area()) }
|
运行:
1 2
| &{Recangel 3 4} &{Triangel 3 4 1.5707963267948966} 12 6
|
interface所赋的实质类型值为nil
如果实质类型的值为nil
,此时方法receiver
为nil
。对许多其它语言来说,调用null
对象的方法会异常,但是Go不会。
把上述程序稍微改改
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func (r *Rectangel) Area() float64{ if r == nil { return 0 } return r.x * r.y } ... var a1 , a2, a3 Shape ... var r2 *Rectangel ... a3 = r2 fmt.Println(r,t,r2) fmt.Println(a1.Area(),a2.Area(), a3.Area())
|
运行
1 2
| &{Rectangel 3 4} &{Triangel 3 4 1.5707963267948966} <nil> 12 6 0
|
这所以还能正常运行,是因为虽然值为nil
,但其实质类型是确定的(*Rectangel
)。但如果类型也为nil
…
零值interface
即无具体类型,也无值。此时能够通过编译,但运行时调用方法就会报错,因为不能确定调用何种类型的对应方法。
例如再将上述程序修改
1 2 3
| var a1 , a2, a3, a4 Shape ... a4.Area()
|
使用go install 能够通过编译,但运行bin/mygo二进制文件时出现runtime error
1 2 3 4 5 6 7 8 9 10
| &{Rectangel 3 4} &{Triangel 3 4 1.5707963267948966} <nil> 12 6 0 panic: runtime error: invalid memory address or nil pointer dereference [signal 0xb code=0x1 addr=0x20 pc=0x401590] goroutine 1 [running]: panic(0x4db680, 0xc82000a180) /usr/local/go/src/runtime/panic.go:464 +0x3e6 main.main() /tmp/mymtd/src/myinterface/myinterface.go:40 +0x4f0
|
空接口
无方法的接口为空接口interface{}
,它可以保存任何类型的值,因为所有类型都有至少0个接口。空接口用来处理不知道具体类型的值,例如fmt.Print
可以接受任何空接口值。
1 2 3 4 5 6 7
| var i interface{} i = 42 fmt.Printf("(%v, %T)\n",i,i) i = 31.34 fmt.Printf("(%v, %T)\n",i,i) i = "hello" fmt.Printf("(%v, %T)\n",i,i)
|
运行:
1 2 3
| (42, int) (31.34, float64) (hello, string)
|
有点类似万能类型。
类型断言
通过类型断言可了解接口value
的具体类型。
断言i
值的具体类型为T
,并将其值赋给t
。如果i值类型并不为T,那么就会触发panic。
类型断言也可以返回两个值,实质类型和bool值
如果i值类型为T,那么就会得到值和true
,否则就会得到nil
和T和false
.
1 2 3 4 5 6
| var i interface{} i = 42 f,ok := i.(float64) fmt.Printf("f value = %v, f type = %T, ok = %v\n",f,f,ok) f2,ok2 := i.(int) fmt.Println(f2,ok2)
|
运行:
1 2
| f value = 0, f type = float64, ok = false 42 true
|
type switch
type switch
结构允许一系列的类型断言
1 2 3 4 5 6 7 8
| switch v := i.(type) { case T: // here v has type T case S: // here v has type S default: // no match; here v has the same type as i }
|
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main import "fmt" func do(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) } } func main() { do(21) do("hello") do(true) }
|
结果
1 2 3
| Twice 21 is 42 "hello" is 5 bytes long I don't know about type bool!
|
Stringer接口
最普遍的一个接口是fmt
包中定义的Stringer
接口
1 2 3
| type Stringer interface { String() string }
|
它使用一个string来描述自己,fmt和其它包都使用这个接口来打印变量的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main import "fmt" type Person struct { Name string Age int } func (p Person) String() string { return fmt.Sprintf("%v (%v years)", p.Name, p.Age) } func main() { a := Person{"Arthur Dent", 42} z := Person{"Zaphod Beeblebrox", 9001} fmt.Println(a, z) }
|
有点类似java
的toString
方法,另外注意这次用到了fmt.Sprintf
函数。
Stringer接口练习
type IPAddr [4]int,其打印形式应该为1.2.3.4
1 2 3 4 5 6 7 8 9 10 11 12 13
| package main import "fmt" type IPAddr [4]int func (ip IPAddr) String() string{ return fmt.Sprintf("%v.%v.%v.%v",ip[0],ip[1],ip[2],ip[3]) } func main(){ ip := IPAddr{104,194,78,21} fmt.Println(ip) }
|