方法 方法是与类型相关联的函数
方法声明 一个方法的声明如下,其中参数p,叫做方法的接收器 (receiver):
1 2 3 4 5 6 7 8 9 10 import "math" type Point struct { X, Y float64 } func (p Point) Distance (q Point) float64 { return math.Htpot(q.X-p.X, q.Y-p.Y) }
可以给同一个包内的任意命名类型 (基础数据类型需要起别名)定义方法,只要这个命名类型的底层类型 不是指针 或者interface
指针类型的接收器 也可以定义接收器对象为指针 类型,这样可以避免拷贝 、或者更新 接收器对象:
1 2 3 4 5 func (p *Point) ScaleBy (factor float64 ) { p.X *= factor p.Y *= factor }
注意:
嵌入结构体扩展类型 可以嵌入其他结构体,嵌入匿名结构体时,可以直接访问叶子属性 而不需要给出完整的路径、也可以拥有匿名结构体的所有方法
1 2 3 4 5 6 7 8 9 10 11 12 type Point struct { X, Y float64 } type RGBA struct { R, G, B, A uint8 } type ColoredPoint struct { Point Color color.RGBA }
同时,可以把ColoredPoint
类型当作接收器来调用Point
里的方法,即使ColoredPoint
里没有声明这些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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}fmt.Println(p.Distance(q.Point)) p.ScaleBy(2 ) q.ScaleBy(2 ) fmt.Println(p.Distance(q.Point))
一个ColoredPoint
并不是一个Point
,但他**”has a”Point**,并且它有从Point
类里引入的Distance
和ScaleBy
方法。Distance函数的参数类型是Point
,所以必须显式地选择q.Point
方法值和方法表达式 方法值 常用p.Distance()
调用方法,实际上将其分成两步来执行也是可能的。p.Distance
叫作选择器 ,选择器会返回一个方法值 :一个将方法Point.Distance
绑定到特定接收器变量的函数。
这个函数可以不通过指定其接收器即可被调用 ,因为已经在前文中指定过了,只要传入函数的参数即可
1 2 3 4 5 6 7 8 9 10 11 12 p := Point{1 , 2 } q := Point{4 , 6 } distanceFromP := p.Distance fmt.Println(distanceFromP(q)) var origin Point fmt.Println(distanceFromP(origin)) scaleP := p.ScaleBy scaleP(2 ) scaleP(3 ) scaleP(10 )
方法表达式 当T是一个类型时,方法表达式 可能会写作T.f
或者(*T).f
,会返回一个函数”值”,这种函数会将其第一个参数用作接收器 :
1 2 3 4 5 6 7 8 9 10 11 p := Point{1 , 2 } q := Point{4 , 6 } distance := Point.Distance fmt.Println(distance(p, q)) fmt.Printf("%T\n" , distance) scale := (*Point).ScaleBy scale(&p, 2 ) fmt.Println(p) fmt.Printf("%T\n" , scale)
当你根据一个变量来决定调用同一个类型的哪个函数 时,方法表达式就显得很有用了。可以根据选择来调用接收器各不相同的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Point struct { X, Y float64 }func (p Point) Add (q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }func (p Point) Sub (q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }type Path []Pointfunc (path Path) TranslateBy (offset Point, add bool ) { var op func (p, q Point) Point if add { op = Point.Add } else { op = Point.Sub } for i := range path { path[i] = op(path[i], offset) } }
封装 一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为封装 。
Go语言只有一种控制可见性 的手段:大写首字母 的标识符会从定义它们的包中被导出,小写字母 的则不会。这种基于名字的手段使得在语言中最小的封装单元是package ,而不是像其它语言一样的类型,如一个struct类型的字段对同一个包的所有代码都有可见性。
封装的好处 :
隐藏实现细节
可以对数据(某些字段)进行验证 ,保证安全合理
封装的步骤
将结构体、字段的首字母小写,使之不能导出
给结构体所在包提供一个工厂模式 的函数,首字母大写,类似于构造函数
提供访问getter 或修改setter 内部变量的函数
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 package personimport "fmt" type person struct { name string age int sal float64 } func NewPerson (name string ) *person { return &person{ name:name, } } func (user *person) Setage (age int ) { if age >0 && age < 150 { user.age = age }else { fmt.Println("年龄数值不对!" ) } } func (user *person) Getage () int { return user.age } func (user *person) Updatename (name string ) string { user.name=name return user.name }