Go语言学习笔记——方法

方法的声明

方法的声明和普通函数类似,只是在函数名字前面多了一个参数,这个参数把这个方法绑定到这个参数对应的类型上,例:

type Point struct{ X, Y float64 }

func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

指针接收者的方法

由于主调函数会复制每一个实参变量,如果函数需要更新一个变量,或者如果一个实参太大而我们希望避免复制整个实参,因此我们必须使用指针来传递变量的地址,这也同样适用于更新接收者:将它绑定到指针类型:

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

习惯上遵循如果Point的任何一个方法使用指针接收者,那么所有的Point方法都应该使用指针接收者,
即使有一些方法不需要

不允许本身是指针的类型进行方法声明:

type P *int
func (P) f() {} // 编译错误,非法的接收者类型

通过提供*Point能够调用(*Point).ScaleBy方法

r := &Point{1, 2}
r.ScaleBy(2)

p := Point{1, 2}
pptr := &p
pptr.ScaleBy(2)

(&p).ScaleBy(2)

如果接收者p是Point类型的变量,但方法要求一个*Point接收者,也可以简写为:

p.ScaleBy(2)

实际上编译器会对变量进行&p的隐式转换,只有变量才允许这么做,不能够对一个不能取地址的Point接收者参数调用*Point方法,因为无法获取临时变量的地址

Point{1, 2}.ScaleBy(2) // 编译错误,不能获得Point类型字面量的地址

有三种情况:

  1. 实参接受者和形参接收者是同一个类型,比如都是T类型或都是*T类型
  2. 实参接收者是T类型的变量而形参接收者是*T类型,编译器会隐式地获取变量
p.ScaleBy(2) // 隐式转为(&p)
  1. 实参接收者是*T类型而形参接收者是T类型,编译器会隐式地解引用接收者,获得实际的取指
pptr.Distance(q) // 隐式转为(*pptr)

nil是一个合法的接收者

type IntList struct {
    Value int
    Tail *IntList
}

func (list *IntList) Sum() int {
    if list == nil {
        return 0
    }
    return list.Value + list.Tail.Sum()
}

通过结构体内嵌组成类型

import "image/color"
type Point struct{ X, Y float64 }

type ColoredPoint struct {
	Point
	Color color.RGBA
}

Point的方法都被纳入到ColoredPoint类型中

与其他面向对象语言不同的是,Point类型并不是ColoredPoint的基类,而是组合

p.Distance(q) // 编译错误:不能将q(ColoredPoint)转换为Point类型

方法变量与表达式

可以将p.Distance赋予一个方法变量,它是一个函数,把方法(Point.Distance)绑定到一个接收者p上,函数只需要提供实参而不需要提供接收者就能够调用

p := Point{1, 2}
q := Point{3, 4}
distanceFormP := p.Distance
fmt.Println(distanceFromP(q))

和调用一个普通函数不同,在调用方法的时候必须提供接收者,并且按照选择子的语法进行调用,方法表达式写成T.f或者(*T).f,其中T是类型,是一种函数变量,把原来的方法的接收者替换成函数的第一个形参,因此它可以像平常的函数一样调用

p := Point{1, 2}
q := Point{3, 4}
distance := Point.Distance // 方法表达式
fmt.Println(distance(p, q))

封装

Go语言只有一种方式控制命名的可见性:定义的时候,首字母大写的标识符是可以从包中导出的,而首字母没有大写的则不导出

也就是说,要封装一个对象,必须使用结构体

Go语言中封装的单元是包而不是类型,无论是在函数内的代码还是方法内的代码,结构体类型内的字段对于同一个包中的所有代码都是可见的

你可能感兴趣的:(Go)