Go语言学习笔记(七)之方法

在面对对象编程(OOP),我们常把某个对象实现的具体行为的函数称作方法。例如 C++中A类的某个函数实现了某种行为,我们就叫做 A 的方法。在 golang 中如果要定义一个方法,只需要在函数声明时,在函数名前加上某个变量,即该变量实现了某个方法。

方法声明
type Point struct{
    X, Y float64
}
//按照传统方法,我们可能会按照下面的方式来写
func Distance(p, q Point) float64 {
    return math.Hypot(q.X - p.X, q.Y - p.Y)
}

//但在 go 语言中则是这样
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

参数 p 我们一般称为方法接收器(receiver), 按照早期 OOP 的说法, 调用一个方法称为"向一个对象发送消息", 这里的 p 类似C++中的 this 指针, python 中的 self.

方法调用是这样的:

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

p.Distance这种表达式叫做选择器, 因为编译器会自动选择适合 p 这个对象的 Distance 方法来执行.可以理解为 C++中每个类都有对应自己的方法, 尽管名字相同,但都有自己所属的命名空间.

比如我们可以再定义一个同样的 Distance 方法:

type Path []Point

func (path Path)Distance() float64 {
    sum := 0.0
    for i := range path {
        if i > 0 {
           sum += path[i-1].Distance(path[i]) //此处调用了上面 Point 的 Distance 方法
        }
    }
}
基于指针对象的方法

我们知道, go 语言中函数调用的参数值都是值拷贝, 如果需要更改入参的值, 则需要用指针来处理.同样, 声明方法时,也可以将接收器定义为指针类型. 例如:

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

但是需要注意的是, 一般约定如果某个类(比如 Point)有一个指针作为接收器的方法, 那么所有的方法都必须定义为指针接收器. 这里只是为了展示两种方法.

另外,如果类型本身是一个指针, 则不允许其出现在接收器中, 例如:

type P *int //P是一个 int 型的指针
func (P) f() {...} //此处会编译出错

在 go 语言中调用指针的方法,有多种写法:

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

幸运的是, 我们不需要牢记这么多写法, go 语言中编译器会帮忙做类型转换, 无论接收器是指针类型还是非指针类型,都可以调用.

总结一下共三种方式:

//1. 接收器实参与接收器形参相同
//此处为形参为 Point, 如果写 Point{1,2}.ScaleBy(2)则会报错
//原因是Point{1,2}是一个临时变量, 无法获取内存地址
Point{1,2}.Distance(q) 
//pptr为一个指针,实参也是指针
pptr.ScaleBy(2)


//2. 接收器形参为类型 T, 实参为类型*T
//此处p为类型 T, 调用的*T 的方法, 编译器隐式转换了
p.ScaleBy(2)

//3. 接收器形参为*T, 实参为类型 T
//同样编译器做隐式转换
pptr.Distance(q)
含有匿名结构体的方法

在上一篇文章中提到过匿名结构体, 如果某个结构体A中含有匿名结构体B, 则在调用 B 结构体的方法func()时,可以直接使用A.func().例如:

type Point struct { X, Y float64}

type ColoredPoint struct {
  Point
  Color color.RGBA
}

//Point 实现了 Distance 方法, 则
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))//"5"
p.ScaleBy(2)

注意:一个 ColoredPoint 并不是一个 Point, 可以看成它“has a” Point, 并且有从 Point 类引入的 Distance 和 ScaleBy 方法。

也可以在结构体中声明一个匿名指针,用来共享结构内存并动态的改变对象之间的关系。下例:

type ColoredPoint struct {
  *Point
  Color color.RGBA
}

p:= ColoredPoint{&Point{1,1}, red}
q:= ColoredPoint{&Point{5,4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point // p、q现在共享内存
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point)
方法值和方法表达式

之前的例子p.Distance叫做选择器,对于 Point.Distance则称为方法表达式,它们返回的“值”成为方法值

大多数情况,人们习惯于使用选择器来调用一个方法。使用方法表达式调用方法的话,函数的第一个参数会被用作接收器。例如:

p := Point{1,2}
q := Point{4,6}
distance := Point.Distance //method expression
fmt.Println(distance(p,q)) //"5"
fmt.Printf("%T\n", distance) //func(Point, Point) float64

方法表达式的好处在于当根据一个变量来决定调用同一个类型的哪个函数时,就可以来使用方法表达式。比如说:

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 []Point

func (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 {
    //call either path[i].Add(offset) or path[i].Sub(offset).
    path[i] = op(path[i], offset)
  }
}

👆的例子,变量 op 代表加法或者减法,二者都是属于 Point 类型,使用方法表达式就会变的很简洁。

Period.🤔

更多文章欢迎关注公众号:程序员 Morgan。

1534254397866

聚焦程序人生,关注自我管理,不给自己人生设限!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容