本小节学习go语言的方法和接口,虽然go语言没有提供类的设计,但可以通过结构体来类比实现对应功能,而类具有对应的方法,同理结构体也可以有自己的函数,我们称之为方法,具体定义是在函数前面加上接收者。

方法

具体定义方法如下,在函数之前添加接收者:

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

当然在go语言中,我们除了可以定义结构体的方法,还能够指定自定义类型的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
	"fmt"
	"math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

定义结构体的方法时,我们既可以定义在结构体上也可以定义在结构体指针上,这两种有什么区别呢?其实,单从指针上可以看出,定义在指针上的方法,方法内可以更改结构体的数据,此外由于是基于指针的传递,因此传递开销较少,如果是复杂结构体上定义的方法,则调用方法时存在拷贝结构体的问题,因此会有性能开销。

 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
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 (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}

我们在学习指针时了解到,结构体的指针可以直接通过指针访问结构体的数据,同样是语法优化,方法的接收者为结构体的指针时,结构体的值也可以直接调用方法。

学习结构体的时候了解过匿名结构体,同理我们定义的结构体方法,如果包含的匿名结构体定义了方法,则外部结构体也可以调用匿名结构体的方法。如果方法名冲突,则需要指定匿名结构体名称了。

接口

学习过java的话,应该知道抽象类,就是规定了要实现的方法,go语言里面的接口也和这个类似,是定义了指定一些列方法。如果一个结构体实现了接口指定的所有方法,则可以说这个结构体是实现了接口。我们可以通过接口类型来屏蔽底层细节,只使用接口定义的方法。

接口的定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
type Abser interface {
	Abs() float64
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}
func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)

	a = f  // a MyFloat implements Abser

	fmt.Println(a.Abs())
}

通过接口的定义,我们可以知道如果定义了一个空的接口,则任何类型都可以算作实现了这个空接口,因此我们有了interface{}这一个特殊类型。

想要详细了解接口的底层细节,可以阅读Golang|interface部分。

断言

我们可以通过i.(T)的方式实现接口的断言。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // panic
	fmt.Println(f)
}

此外结合switch关键字我们还有一种接口判断的逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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)
	}
}

参考