方法声明

在函数声明时,在其名字之前放上一个变量,即是一个方法

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

import (
	"fmt"
)

type Point struct{
	x, y float64
}

func (p Point) dist(q Point)float64{
	dx := p.x - q.x
	dy := p.y - q.y
	return dx*dx + dy*dy
}

func main(){
	var p Point = Point{x: 1, y: 2}
	var q Point = Point{x: 3, y: 4}
	fmt.Println(p.dist(q))
}
  • 其中(p Point)p被称为reciver, reciver只可以是类型或指针类型
  • 命名建议:reciver取类型的第一个字母的小写
  • 一般会约定如果Point这个类有一个指针作为接收器的方法,那么所有Point的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数
  • 在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的(意味着P只能是一个非指针类型)
1
2
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type

基于指针对象的方法

  • 避免拷贝时的巨大开销,可以使用基于指针的方法。
1
2
3
4
func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}
  • 语法糖:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Point struct{
	x, y float64
}

func (p *Point) dist(q Point)float64{
	dx := p.x - q.x
	dy := p.y - q.y
	return dx*dx + dy*dy
}

func main(){
	var p Point = Point{x: 1, y: 2}
	var q Point = Point{x: 3, y: 4}
	fmt.Println(p.dist(q))
}

  • 其中p.dist(q)编译器会隐式地帮我们用&p去掉用dist()方法
  • 同理,如果reciver实参是类型T,但形参是类型*T, 编译器会隐式的解引用
  • 不管method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会做隐式转换(取引用和解引用)

Nil也是一个合法的接收器类型

Nil也是一个合法的接收器类型,但注意nil需要是某个类型的nil。举一个例子:

 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
package url

type Values map[string][]string

func (v Values) Get(key string) string {
    if vs := v[key]; len(vs) > 0 {
        return vs[0]
    }
    return ""
}

func (v Values) Add(key, value string) {
    v[key] = append(v[key], value)
}

m := url.Values{"lang": {"en"}} // direct construction
m.Add("item", "1")
m.Add("item", "2")

fmt.Println(m.Get("lang")) // "en"
fmt.Println(m.Get("q"))    // ""
fmt.Println(m.Get("item")) // "1"      (first value)
fmt.Println(m["item"])     // "[1 2]"  (direct map access)

m = nil   // 注意这一行,m的类型是url.Values
fmt.Println(m.Get("item")) // ""
m.Add("item", "3")         // panic: assig

通过嵌入结构体来扩展类型

  • 通过组合的方式,把组合进来的成员x的方法x_method_1()和x_method_2()变为自己的方法。
  • 表现上像通过组合然后unpack的方式实现继承,或者说像mixin…
  • 表现为定义一个匿名成员,如下面的ColoredPoint中的Point
  • 类型中内嵌的匿名字段也可能是一个指针
 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
package main

import (
	"fmt"
	"image/color"
	"math"
)

type Point struct{ X, Y float64 }

func (p Point) ScaleBy(size float64){
	p.X *= size
	p.Y *= size
}

func (p Point) dist(q Point)float64{
	var dx float64 = p.X - q.X
	var dy float64 = p.Y - q.Y
	return math.Sqrt(dx*dx + dy*dy)
}


type ColoredPoint struct {
	Point
	Color color.RGBA
}

func main(){
	red := color.RGBA{255, 0, 0, 255}
	blue := color.RGBA{0, 0, 255, 255}
	var p = ColoredPoint{Point{1, 1}, red}
	var q = ColoredPoint{Point{5, 4}, blue}
	p.ScaleBy(2)
	q.ScaleBy(3)
	fmt.Println(p)
	fmt.Println(q)

	fmt.Println(p.dist(q.Point))  // 注意这里的调用
}

注意最后一行的调用,必须传入q.Point

方法值和方法表达式

go可以像python一样,把方法绑定了一个实例,然后命名为一个函数。例如下面的distanceFromP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func (p Point) dist(q Point)float64{
	var dx float64 = p.X - q.X
	var dy float64 = p.Y - q.Y
	return math.Sqrt(dx*dx + dy*dy)
}
q := Point{4, 6}
distanceFromP := p.dist  

dst = Point.dist      // 注意这里
fmt.Println(dst(p, q))
  • 注意上面的dst, dst默认绑定到第一个参数p

封装

通过变量首字母大小写实现:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会