Go语言学习笔记 - 第六章 方法(The Go Programming Language)

第六章 方法

  • 个方法则是一个一个和特殊类型关联的函数
  • 到OOP编程的两个关键点,封装组合

常用库及方法

  • time.Duration time.Hour time.Duration.Seconds()

6.1方法声明

划重点

  • 在函数声明时,在其名字之前放上一个变量,即是一个方法
  • golang的接收器不像其他语言的thisself,可以任意选择,为了保持在方法间传递的一只新个简短性,建议使用类型的第一个字母的首字母小写。
  • p.Distance的表达式叫做选择器,选择器也会被用来选择一个struct类型的字段
  • 由于方法和字段都是在同一命名空间,所以方法和字段的名字不要相同,否则编译不通过
  • 不同的类型命名空间不同,这就意味着不同的类型可以拥有相同的名字的方法
  • Go语言与其他OOP语言不同,可以任意类型指定方法,所以在Go语言里为简单的数值、字符串、slice、map定义一些方法很方便。方法可以被声明到任意类型,只要不是一个指针或者一个interface

常用库及方法

  • math.Hypot

6.2基于指针对象的方法

划重点

  • 方法的名字是 (*Point).ScaleBy。这里的括号是必须的;没有括号的话这个表达式可能会被理解为 *(Point.ScaleBy)
  • 只有类型(Point)和指向他们的指针(*Point),才是可能会出现在接收器声明里的两种接收器
  • 为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver >type
  • 如果接收器需要一个指针作为接收器,Go语言可以直接使用类型的变量+选择器调用方法,因为Go语言编译器会隐式的用&变量去调用这个方法,同样对于类型变量的接收器,指针变量调用时可以直接使用,编译器会加上*。比如:
方法(类型变量直接作为指针接收器调用):
func (p *Point) ScaleBy(factor float64)
----
p := Point{1, 2}
p.ScaleBy(2)  //是允许的,编译器会使用&p去调用
----
方法(类型指针直接作为类型变量接收器调用):
func (p Point) Distance(q Point) float64
----
p := Point{1, 2}
pptr := &p
pptr.Distance(q) //是允许的,编译器会使用*pptr去调用

这种写法只适用于“变量”-字段、array,slice内的元素,但不能通过一个无法取地址的接收器来调用指针方法,比如临时变量的内存地址就无法获取得到,下面的调用是错误的:

Point{1, 2}.ScaleBy(2) // compile error: can't take address of >Point literal
--------下面的这个是可以的,因为Distance()的接收器是类型变量
Point{1, 2}.Distance(q) // it's ok
  • 接收器是一个类型变量时,方法操作的是该对象的一个复制;接收器是一个指针的时,方法操作的是该对象的一个引用,对成员的改变将会作用到该对象。
  • 总结:
    • 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换。
    • 在声明一个method的receiver该是指针还是非指针类型时,你需要考虑两方面的内容,第一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;第二方面是如果你用指针类型作为receiver,那么你一定要注意,这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝。熟悉C或者C++的人这里应该很快能明白。
    • array,slice,map 这些类型作为接收器无论是变量还是指针,都代表一个指针
6.2.1Nil也是一个合法的接收器类型

划重点

  • 方法理论上也可以用nil指针作为其接收器,尤其当nil对于对象来说是合法的零值时,比如map或者slice

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

划重点

  • 结构体S嵌入结构体T可以为匿名字段,也可以是有名字段,假设T有成员M,有名字段的方法不能通过S.M直接调用,只能通过S.Tname.M调用。
  • 嵌入结构体可以理解为是一种继承,但是和继承有些区别,即:其他语言的继承是"is a",而Go语言的嵌入结构体是"has a"。也就是说新类型不能代替嵌入类型直接传递给需要嵌入类型形参的方法,必须要显式的选择嵌入类型。比如:
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}

p.Distance(q) // compile error: cannot use q (ColoredPoint) as >Point
p.Distance(q.Point) // compile success.
  • 给出如下例子,则ColoredPoint拥有Point的所有成员及方法,可以通过ColoredPoint.Point.X 或者直接 ColoredPoint.X获取到X
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
  • 内嵌的匿名字段也可能是一个命名类型的指针,这种情况下字段和方法会被间接地引入到当前的类型中,这样让我们可以共享通用的结构并动态地改变对象之间的关系
  • 一个struct类型也可能会有多个匿名字段
  • 编译器解析一个选择器到方法时,它会首先去找直接定义在这个类
    型里的方法,然后找内嵌字段们引入的方法,然后去找内嵌字段的内嵌字段引入的方法,然后一直递归向下找。如果选择器有二义性的话编译器会报错,比如你在同一级里有两个同名的方法,这个时候需要显示的指定匿名字段,直到没有二义性。
  • 在该书中,作者定义的函数和方法的区别是指有没有接收器,而不像其他语言那样是指有没有返回值。
  • T是一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数"值",这种函数会将其第一个参数用作接收器,所以Point.Distance可以直接作为一个函数使用,跨包调用还要加上包名package.Point.Distance

6.4方法值和方法表达式

划重点

  • 方法值-“Method Value”
distanceFromP := p.Distance // method value
distanceFromPValue := p.Distance() // method result

6.5示例: Bit数组

划重点
常用库及方法

  • bytes.Buffer bytes.Buffer.WriteByte

6.6封装

划重点

  • 一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为“封装”
  • Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会。这同样适用于struct或者一个类型的方法
  • 在Go语言中,如果想要封装一个对象,我们必须将其定义为一个struct。
  • 这种基于名字的手段使得在语言中最小的封装单元是package,而不是像其它语言一样的类型。
  • 一个struct类型的字段对同一个包的所有代码都有可见性,无论你的代码是写在一个函数还是一个方法里。
  • 封装提供了三方面的优点。
    • 首先,因为调用方不能直接修改对象的变量值,其只需要关注少量的语 句并且只要弄懂少量变量的可能的值即可。
    • 第二,隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现 ,这样使设计包的程序员在不破坏对外的api情况下能得到更大的自由。
    • 第三,阻止了外部调用方对对象内部的值任意地进行修改。
  • 只用来访问或修改内部变量的函数被称为setter或者getter

你可能感兴趣的:(编程#golang,golang,go,编程语言)