漫游Go语言(五)之方法、接口与类型断言
方法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
19package 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
29package 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)
}
运行:1
{60 80} &{96 72}
而一般的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
42package 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
35package 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
14func (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
3var a1 , a2, a3, a4 Shape
...
a4.Area()
使用go install 能够通过编译,但运行bin/mygo二进制文件时出现runtime error1
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
7var 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
的具体类型。1
t := i.(T)
断言i
值的具体类型为T
,并将其值赋给t
。如果i值类型并不为T,那么就会触发panic。
类型断言也可以返回两个值,实质类型和bool值1
t, ok := i.(T)
如果i值类型为T,那么就会得到值和true
,否则就会得到nil
和T和false
.1
2
3
4
5
6var 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
2f value = 0, f type = float64, ok = false
42 true
type switch
type switch
结构允许一系列的类型断言1
2
3
4
5
6
7
8switch 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
20package 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
3Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!
Stringer接口
最普遍的一个接口是fmt
包中定义的Stringer
接口1
2
3type Stringer interface {
String() string
}
它使用一个string来描述自己,fmt和其它包都使用这个接口来打印变量的值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package 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.41
2
3
4
5
6
7
8
9
10
11
12
13package 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)
}