返回介绍

method

发布于 2024-10-12 12:35:54 字数 5396 浏览 0 评论 0 收藏 0

现在假设有这么一个场景,定义了一个 struct 叫做长方形,现在想要计算他的面积,那么按照一般的思路应该会用下面的方式来实现

package main
import "fmt"
type Rectangle struct {
    width, height float64
}
func area(r Rectangle) float64 {
    return r.width*r.height
}
func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    fmt.Println("Area of r1 is: ", area(r1))
    fmt.Println("Area of r2 is: ", area(r2))
}

这段代码可以计算出来长方形的面积,但是 area() 不是作为 Rectangle 的方法实现的(类似面向对象里面的方法),而是将 Rectangle 的对象(如 r1,r2)作为参数传入函数计算面积的。

这样实现当然没有问题,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,想计算他们的面积的时候怎么办?那就只能增加新的函数,但是函数名就必须要跟着换了,变成 area_rectangle, area_circle, area_triangle...

椭圆代表函数,而这些函数并不从属于 struct(或者以面向对象的术语来说,并不属于 class),他们是单独存在于 struct 外围,而非在概念上属于某个 struct 的。

很显然,这样的实现并不优雅,并且从概念上来说"面积"是"形状"的一个属性,它是属于这个特定的形状的,就像长方形的长和宽一样。

基于上面的原因所以就有了 method 的概念, method 是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在 func 后面增加了一个 receiver(也就是 method 所依从的主体)。

用上面提到的形状的例子来说,method area() 是依赖于某个形状(比如说 Rectangle) 来发生作用的。Rectangle.area() 的发出者是 Rectangle, area() 是属于 Rectangle 的方法,而非一个外围函数。

更具体地说,Rectangle 存在字段 height 和 width, 同时存在方法 area(), 这些字段和方法都属于 Rectangle。

用 Rob Pike 的话来说就是:

"A method is a function with an implicit first argument, called a receiver."

method 的语法如下:

func (r ReceiverType) funcName(parameters) (results)

下面用最开始的例子用 method 来实现:

package main
import (
    "fmt"
    "math"
)
type Rectangle struct {
    width, height float64
}
type Circle struct {
    radius float64
}
func (r Rectangle) area() float64 {
    return r.width*r.height
}
func (c Circle) area() float64 {
    return c.radius * c.radius * math.Pi
}
func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    c1 := Circle{10}
    c2 := Circle{25}
    fmt.Println("Area of r1 is: ", r1.area())
    fmt.Println("Area of r2 is: ", r2.area())
    fmt.Println("Area of c1 is: ", c1.area())
    fmt.Println("Area of c2 is: ", c2.area())
}

在使用 method 的时候重要注意几点

  • 虽然 method 的名字一模一样,但是如果接收者不一样,那么 method 就不一样

  • method 里面可以访问接收者的字段

  • 调用 method 通过 . 访问,就像 struct 里面访问字段一样

在上例,method area() 分别属于 Rectangle 和 Circle, 于是他们的 Receiver 就变成了 Rectangle 和 Circle, 或者说,这个 area() 方法 是由 Rectangle/Circle 发出的。

值得说明的一点是,图示中 method 用虚线标出,意思是此处方法的 Receiver 是以值传递,而非引用传递,是的,Receiver 还可以是指针,两者的差别在于,指针作为 Receiver 会对实例对象的内容发生操作,而普通类型作为 Receiver 仅仅是以副本作为操作对象,并不对原实例对象发生操作。后文对此会有详细论述。

那是不是 method 只能作用在 struct 上面呢?当然不是,他可以定义在任何自定义的类型、内置类型、struct 等各种类型上面。什么叫自定义类型,自定义类型不就是 struct,其实不是这样的,struct 只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。

type typeName typeLiteral

请看下面这个申明自定义类型的代码

type ages int
type money float32
type months map[string]int
m := months {
    "January":31,
    "February":28,
    ...
    "December":31,
}

这样就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于 c 中的 typedef,例如上面 ages 替代了 int,回到 method 可以在任何的自定义类型中定义任意多的 method ,接下来让看一个复杂一点的例子

package main
import "fmt"
const(
    WHITE = iota
    BLACK
    BLUE
    RED
    YELLOW
)
type Color byte
type Box struct {
    width, height, depth float64
    color Color
}
type BoxList []Box //a slice of boxes
func (b Box) Volume() float64 {
    return b.width * b.height * b.depth
}
func (b *Box) SetColor(c Color) {
    b.color = c
}
func (bl BoxList) BiggestColor() Color {
    v := 0.00
    k := Color(WHITE)
    for _, b := range bl {
        if bv := b.Volume(); bv > v {
            v = bv
            k = b.color
        }
    }
    return k
}
func (bl BoxList) PaintItBlack() {
    for i := range bl {
        bl[i].SetColor(BLACK)
    }
}
func (c Color) String() string {
    strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
    return strings[c]
}
func main() {
    boxes := BoxList {
        Box{4, 4, 4, RED},
        Box{10, 10, 1, YELLOW},
        Box{1, 1, 20, BLACK},
        Box{10, 10, 1, BLUE},
        Box{10, 30, 1, WHITE},
        Box{20, 20, 20, YELLOW},
    }
    fmt.Printf("We have %d boxes in our set\n", len(boxes))
    fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
    fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
    fmt.Println("The biggest one is", boxes.BiggestColor().String())
    fmt.Println("Let's paint them all black")
    boxes.PaintItBlack()
    fmt.Println("The color of the second one is", boxes[1].color.String())
    fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
}

上面的代码通过 const 定义了一些常量,然后定义了一些自定义类型

  • Color 作为 byte 的别名

  • 定义了一个 struct:Box,含有三个长宽高字段和一个颜色属性

  • 定义了一个 slice:BoxList,含有 Box

然后以上面的自定义类型为接收者定义了一些 method

  • Volume() 定义了接收者为 Box,返回 Box 的容量

  • SetColor(c Color),把 Box 的颜色改为 c

  • BiggestColor() 定在在 BoxList 上面,返回 list 里面容量最大的颜色

  • PaintItBlack() 把 BoxList 里面所有 Box 的颜色全部变成黑色

  • String() 定义在 Color 上面,返回 Color 的具体颜色(字符串格式)

上面的代码通过文字描述出来之后是不是很简单?一般解决问题都是通过问题的描述,去写相应的代码实现。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文