为什么Go函数的定义方式不同?

huangapple go评论76阅读模式
英文:

Why a go function is defined differently?

问题

我正在尝试学习Go语言。当我以为我理解了函数是什么,如何使用它,并且想要了解接口时,我卡在了这个问题上(来源:Go博客)。

package main

import "fmt"

//定义一个Rectangle结构体,它有长度和宽度两个属性
type Rectangle struct { 
   length, width int
}

//定义一个可以应用于Rectangle类型的Area函数
func (r Rectangle) Area() int {
   return r.length * r.width
}

func main() {
   r := Rectangle{length:5, width:3} //使用给属性赋值的方式定义一个新的Rectangle实例
   fmt.Println("Rectangle details are: ", r)  
   fmt.Println("Rectangle's area is: ", r.Area())
}

为什么我们使用func (r Rectangle) Area() int而不是func Area(r Rectangle) int?它们有什么不同吗?

英文:

I'm trying to learn Go. When I thought that I understand what a function is, how to use it and was looking to get into interfaces I got stuck on this (source Go blog )

package main

import "fmt"

//define a Rectangle struct that has a length and a width
type Rectangle struct { 
   length, width int
}

//write a function Area that can apply to a Rectangle type
func (r Rectangle) Area() int {
   return r.length * r.width
}

func main() {
   r := Rectangle{length:5, width:3} //define a new Rectangle instance with values for its properties
   fmt.Println("Rectangle details are: ",r)  
   fmt.Println("Rectangle's area is: ", r.Area())
}

Why do we have func (r Rectangle) Area() int and not func Area(r Rectangle) int ? Is it any different ?

答案1

得分: 9

这意味着你的函数与一个类型相关联。你可以为任何用户定义的类型这样做。不同之处在于如何调用函数,而不是使用:

a := Area(r);

你将使用:

a := r.Area();

这也意味着如果你定义了一个方法,如下所示:

func (r Rectangle) String() string

Rectangle 将自动实现 Stringer 接口,你将能够打印 Rectangle

英文:

This means that your function is attached to a type. You can do that for any user-defined type. The difference is how you call the function, instead of using:

a := Area(r);

you will use:

a := r.Area();

This also means that if you define a method like

func (r Rectangle) String() string

Rectangle will automatically implement Stringer, and you will gain the ability to print Rectangle.

答案2

得分: 4

它们在执行上是功能等效的,但在语义上有所不同。形式为func F(obj T)的函数(在这种情况下是func Area(r Rectangle))与编写代码func (obj T) F()(在这种情况下是func (r Rectangle) Area())是相同的——只要方法体当然是相同的。

func (r Rectangle) Area() int {
    return r.length * r.width
}

//...
r.Area()

等同于

func Area(r Rectangle) int {
    return r.length * r.width
}
//...
Area(r)

它们在返回相同的结果方面是相等的。实际上,如果使用反射检查函数,你会发现方法接收器实际上在内部被视为第一个参数。不相等的是接口实现。如果你有一个接口

type Areaer interface {
  Area() int
}

(只需使用Areaer,这是奇怪的,但是推荐的命名约定)。前一种实现将导致Rectangle实现Area,但后一种实现不会。这意味着

func PrintArea(a Areaer) {
    fmt.Println(a.Area())
}

当以PrintArea(r)调用时,将适用于第一种实现,但不适用于第二种实现。

何时使用自由函数和方法在很大程度上是一个设计选择。通常,方法将附加的接收器上,而函数将使用附加的接收器。一个例子是,图可能知道其后继或节点,但A*之类的函数可以是一个接受图的函数。

对于Rectangle来说,诸如WidthHeightAreaGrowSetPosition之类的方法可能是很好的方法,因为它们是Rectangle自身了解的事物(或可以对自身执行的操作),而确定两个矩形是否相交的Intersects之类的函数可能是一个接受两个矩形作为参数的函数。

当然,你会发现很多例外情况,这要么是由于糟糕的设计,要么是经过仔细考虑。然而,我认为这是对一般情况的一个不错的概述。

英文:

They're functionally equivalent in execution, but different in semantics. A function of the form func F(obj T) (in this case func Area(r Rectangle)) is the same thing as writing the code func (obj T) F() (in this case func (r Rectangle) Area()) -- as long as the method body is the same of course.

func (r Rectangle) Area() int {
    return r.length * r.width
}

//...
r.Area()

Is equal to

func Area(r Rectangle) int {
    return r.length * r.width
}
//...
Area(r)

They're equal in the sense that they return the same thing. In fact, if you inspect the function with reflection, you'll find that the method receiver is actually considered the first argument internally. What's not equal is the interface implementation. If you have an interaface

type Areaer interface {
  Area() int
}

(Just roll with the Areaer thing, it's weird but the recommended naming convention). The former implementation will cause Rectangle to implement Area, but the latter won't. Meaning

func PrintArea(a Areaer) {
    fmt.Println(a.Area())
}

When called as PrintArea(r) will work on the first implementation, but not the second.

When to use a free function vs a method is largely a design choice. Generally a method will be ON the attached receiver, while a function will USE the attached receiver. An example is that a Graph may know its successors or nodes, but a function like A* could be a function that takes in a graph.

For the Rectangle, things like Width, Height, Area, Grow, or SetPosition might be good methods because they're things the Rectangle knows about itself (or can do to itself), while a function like Intersects that determines if two rectangles intersect might be a function that takes two rectangles as arguments.

Of course, you'll find plenty of exceptions, either due to bad design or careful consideration. However, I think that's a decent overview of the general state of affairs.

答案3

得分: 2

这是一个方法而不是一个函数。通常情况下,你可以选择使用哪个(尽管当你有多个函数都作用于相同类型的对象时,方法通常更合理)。唯一的限制是你只能在接口中使用方法。例如:

type Shape interface {
    Area() int
}

type Rectangle struct {
    ...
}

func (r *Rectangle) Area() int {
    ...
}

在这种情况下,为了满足Shape接口,Area()必须是一个方法。而如果是这样的话:

func Area(r *Rectangle) int {
    ...
}

将无法满足接口的要求。

英文:

It is a method as opposed to a function. Generally, you pick and choose which you'd prefer to use (although methods often make more sense when you have number of functions that all act on the same type of object). The only caveat to this is you can only use methods with interfaces. For example:

type Shape interface {
    Area() int
}

type Rectangle struct {
    ...
}

func (r *Rectangle) Area() int {
    ...
}

In this case, to satisfy the Shape interface, Area() must be a method. Having:

func Area(r *Rectangle) int {
    ...
}

would not satisfy the interface.

huangapple
  • 本文由 发表于 2014年3月11日 08:15:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/22313986.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定