Go语言中函数和方法有什么区别?

88

我正在尝试学习Go语言,文档非常好。但是我在文档中没有找到函数和方法之间的区别。

据我目前的理解:函数是“全局”的,这意味着我不需要导入包就可以使用函数,它们始终存在。而方法则绑定在包上。这样说正确吗?


2
术语“方法”和“函数”只是历史惯例。普通语言意味着“如何”与“什么”,这并不是事实,因此很容易引起混淆。我们可能认为“函数”是数学上的,但这也不是事实,因此很容易引起混淆。事实上,“方法”只是从面向对象语言中采用的与特定类型相关联的术语,而“函数”则是从过程式语言中采用的自由函数。我认为您可以看到这些简单的解释在Go中成立:方法在类型上调用,而函数则不是。 - Rick O'Shea
5个回答

134
据我目前的理解:函数是“全局”的,这意味着我不必导入包就可以使用函数,它们总是存在的。方法绑定到包。这正确吗?
不,那不正确。只有一些来自builtin包的函数是始终可用的。其他所有内容都需要导入。
术语“方法”出现在面向对象编程中。在面向对象的语言中(例如C ++),您可以定义一个“类”,该类封装了数据和彼此相关的函数。该类内的函数称为“方法”,您需要该类的实例才能调用此类方法。
在Go中,术语基本上是相同的,尽管Go不是传统意义上的面向对象编程语言。在Go中,一个带有接收器的函数通常被称为方法(可能仅因为人们仍然习惯OOP的术语)。
例如:
func MyFunction(a, b int) int {
  return a + b
}
// Usage:
// MyFunction(1, 2)

但是
type MyInteger int
func (a MyInteger) MyMethod(b int) int {
  return a + b
}
// Usage:
// var x MyInteger = 1
// x.MyMethod(2)

10
我认为“Go不是一门面向对象的语言”这种说法并不准确。在我的看法中,Go 更偏爱组合而非继承,以至于它为前者提供了额外的语法糖,并完全摒弃了后者。 - Tobias
6
我对此不确定。在我看来,面向对象编程的范例之一是子类化和类型层次。这在Go语言中是不可能的(庆幸!)。当然,Go提供了其他方法(如嵌入)来解决类似的问题,但它们并非一对一的替代。不过,让我们停止争论术语的细节吧:D。 - tux21b
4
谢谢,我终于搞懂了!一个缺失的部分是,方法总是与类型一起使用。 - Dominik Obermaier
5
@tux21b 感谢提供示例。有一个问题,您的方法包含一个“无效操作:a + b(类型不匹配的 MyInteger 和 int)”。为了使其正常工作,您需要将 a 强制转换为 int 或将 b 转换为 MyInteger。例如:return int(a) + b 或者 return a + MyInteger(b)。在第二种情况下,您需要更改方法签名中的返回值类型为 MyInteger 而不是 int。【playground】(http://play.golang.org/p/tVlSCnkbx5) - SunSparc
1
所以有两种看待面向对象编程的方式。最初的定义与类或继承无关。Alan Kay是否认为Go是一种面向对象的语言并不确定,因为它缺少“极端后期绑定”的部分。对于其余的内容,Kay说重要的概念是对象传递消息。Go做得很好。https://www.youtube.com/watch?v=HqZQBmNVhBw <- 这是一个很好的讲座。如果你选择遵循Bjarne Stroustrup的面向对象编程,那么当然,类、继承等都是必须的。 - thenamewasmilo
显示剩余2条评论

31

Tux的回答很好,但是我想通过Go语言中与struct相关的方法来补充它(因为我经常使用这种方法)。所以假设你想要构建一个用于计算三角形各种方法的东西。你可以从一个struct开始:

type Triangle struct {
    a, b, c float64
}

然后您想添加一些函数来计算周长和面积:

func valid(t *Triangle) error {
    if t.a + t.b > t.c && t.a + t.c > t.b && t.b + t.c > t.a {
        return nil
    }
    return errors.New("Triangle is not valid")
}

func perimeter(t *Triangle) (float64, error) {
    err := valid(t)
    if err != nil {
        return -1, err
    }

    return t.a + t.b + t.c, nil
}

func square(t *Triangle) (float64, error) {
    p, err := perimeter(t)
    if err != nil {
        return -1, err
    }

    p /= 2
    s := p * (p - t.a) * (p - t.b) * (p - t.c)
    return math.Sqrt(s), nil
}

现在你已经拥有了一个可工作的程序Go Playground。在这种情况下,你的函数需要一个参数(指向三角形的指针),并且会进行某些操作。在面向对象编程中,人们可能会创建一个类,然后添加方法。我们可以将结构体视为一种具有字段的类,现在我们添加方法:

func (t *Triangle) valid() error {
    if t.a + t.b > t.c && t.a + t.c > t.b && t.b + t.c > t.a {
        return nil
    }
    return errors.New("Triangle is not valid")
}

func (t *Triangle) perimeter() (float64, error) {
    err := t.valid()
    if err != nil {
        return -1, err
    }

    return t.a + t.b + t.c, nil
}

func (t *Triangle) square() (float64, error) {
    p, err := t.perimeter()
    if err != nil {
        return -1, err
    }

    p /= 2
    s := p * (p - t.a) * (p - t.b) * (p - t.c)
    return math.Sqrt(s), nil
}

我们有一个完整的 工作示例

请注意,它看起来非常像对象的方法。


一个带有指针参数的函数和一个方法之间有没有功能上的区别?方法只是语法糖吗? - ban_javascript
如果函数可以像您上面列出的所有内容一样完成所有工作,我仍然不明白使用方法的意义。 - star

22

这里详细解释了它们 - https://anil.cloud/2017/01/26/golang-functions-methods-simplified/

Go语言中的函数遵循以下语法:

func FunctionName(Parameters...) ReturnTypes...

例子:

func add(x int, y int) int

执行:

  add(2,3) 

方法类似于函数,但附加在类型上(称为接收器)。官方指南中指出:“方法是带有特殊接收器参数的函数”。接收器出现在func关键字和方法名称之间。方法的语法如下:

func (t ReceiverType) FunctionName(Parameters...) ReturnTypes...

例子:

func (t MyType) add(int x, int y) int

执行:

type MyType string
t1 := MyType("sample")
t1.add(1,2)

现在让我们把指针带入讨论。Go语言采用传值方式,这意味着每次函数/方法调用时都会传递参数的新副本。要通过引用传递它们,您可以使用指针。
带有指针参数/参数列表的函数的语法。
func FunctionName(*Pointers...,Parameters...) ReturnTypes...

示例

func add(t *MyType, x int, y int) int

执行:

type MyType string
t1 := MyType("sample")
add(&t1,4,5)

同样地,对于方法而言,接收者类型可以是指针。带有指针作为接收者的方法的语法如下:

(译注:此处为原文内容,请勿删除)

func (*Pointer) FunctionName(Parameters...) ReturnTypes...

例子

func (t *MyType) add(x int, y int) int

执行操作:

要执行:

type MyType string
t1 := MyType("sample")
t1.add(2,3)

请注意,我们仍然可以使用t1.add()来执行带有指针接收器的方法(即使‘t1’不是指针),Go将解释它为(&t1).add()。同样,具有值接收器的方法也可以使用指针调用,这种情况下Go将解释p.add()为(*p).add()(其中‘p’是指针)。这仅适用于方法而不适用于函数。
具有指针接收器的方法非常有用,可以获得类似“Java”的行为,其中该方法实际上修改的是接收器指向的值,而不是其副本。

只是想知道,那样做会违背不可变性的整个论点,因为不可变性的目的就是不修改值。 - Chirrut Imwe

0

以上答案对我来说很好。除此之外,我还想补充一点。

函数是从包中调用的东西。而方法也是一个函数,但是从特定类型的值中调用。而则是函数的返回值。

例如:

  1. rightNow := time.Now()

time --> 包,Now() --> 函数,因为它是由一个包调用的,rightNow是从Now()函数返回的type为time.Time的value

  1. year := rightNow.Year()

rightNow --> 值,Year() --> 函数,但由于它是由某个类型(这里是time.Time)的调用的,因此它被称为方法。为了方便识别,方法具有与其调用的相同类型的接收器。


0

Go方法与Go函数类似,唯一的区别在于方法中包含一个接收器参数。通过接收器参数,方法可以访问接收器的属性。这里,接收器可以是结构类型或非结构类型。

func(reciver_name Type) method_name(parameter_list)(return_type){
    // Code
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接