Contents
  1. 1. 方法method
    1. 1.1. pointer receiver
  2. 2. interface
    1. 2.1. 隐式的接口实现
    2. 2.2. interface value
    3. 2.3. interface所赋的实质类型值为nil
    4. 2.4. 零值interface
    5. 2.5. 空接口
  3. 3. 类型断言
    1. 3.1. type switch
  4. 4. Stringer接口
    1. 4.1. Stringer接口练习

方法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之处在于其receiverpointer时,即可以接受pointer也可以接受value的变量的调用,其效果都等同于传入pointer。同理当methodreceivervalue时,不管传入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)
}

运行:

1
{60 80} &{96 72}

而一般的function,其参数类型必须与声明严格一致,否则无法编译。

以上特性叫做pointer indirection(指针迂回?)。

使用pointerreceiver有两个好处,1可以更改其值,2可以避免调用一次copy一次。另外一个typemethod应该使用同一各类的receiver,即valuepointer,其原因接下来会说明。

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)
}

maina = 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,此时方法receivernil。对许多其它语言来说,调用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的具体类型。

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
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)
}

有点类似javatoString方法,另外注意这次用到了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)
}

Contents
  1. 1. 方法method
    1. 1.1. pointer receiver
  2. 2. interface
    1. 2.1. 隐式的接口实现
    2. 2.2. interface value
    3. 2.3. interface所赋的实质类型值为nil
    4. 2.4. 零值interface
    5. 2.5. 空接口
  3. 3. 类型断言
    1. 3.1. type switch
  4. 4. Stringer接口
    1. 4.1. Stringer接口练习