函数和方法的区别

函数只是一段代码的执行过程,而方法往往会与某个类型的对象绑定。

方法是OOP的概念,在一个方法的实现中,我们可以通过特定方式访问对象的私有变量、方法,例如C类语言的this指针,Python的self等,在方法的调用过程中,对象往往被隐式传递。

Golang方法声明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type Student struct {
	Name string
	ID   uint16
}

func (s Student) Display() {
	fmt.Printf("Student ID:%d Name:%s\n", s.ID, s.Name)
}

func NewStudent(id uint16, name string) Student {
	return Student{ID: id, Name: name}
}

func main() {
	s1 := NewStudent(1001, "小明")
	s1.Display() //Student ID:1001 Name:小明
}

在上面的例子中,我们在Display名字前增加了一个参数s,这被称为接收器,使用接收器后,我们可以在方法实现中使用s,仿佛它是通过参数传递进来一样,就像下面这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type Student struct {
	Name string
	ID   uint16
}

func Display(s Student) {
	fmt.Printf("Student ID:%d Name:%s\n", s.ID, s.Name)
}

func NewStudent(id uint16, name string) Student {
	return Student{ID: id, Name: name}
}

func main() {
	s1 := NewStudent(1001, "小明")
	Display(s1) //Student ID:1001 Name:小明
}

这两种方式是很相似的,唯一比较大的区别是例子中的s1可以访问Student结构中的私有对象或者私有方法,而例子2中的s1不可以。

指针的方法

在上面的例子中,当我们使用方法s1.Display()的时候,s1是通过值传递的方式传入Display方法的这意味着,如果我们在方法中对Student中的成员进行修改,并不会影响调用者本身:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type Student struct {
	Name string
	ID   uint16
}

func (s Student) Display() {
	fmt.Printf("Student ID:%d Name:%s\n", s.ID, s.Name)
}

func (s Student) Rename(newName string) {
	s.Name = newName
}

func NewStudent(id uint16, name string) Student {
	return Student{ID: id, Name: name}
}

func main() {
	s1 := NewStudent(1001, "小明")
	s1.Display() //Student ID:1001 Name:小明
	s1.Rename("小暗")
	s1.Display() //Student ID:1001 Name:小明
}

为了能让Rename方法修改Student中的成员,我们需要将接收者改为Student指针类型,这样即便传递过程是值传递,指针仍然是指向同一个对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type Student struct {
	Name string
	ID   uint16
}

func (s Student) Display() {
	fmt.Printf("Student ID:%d Name:%s\n", s.ID, s.Name)
}

func (s *Student) Rename(newName string) {
	s.Name = newName
}

func NewStudent(id uint16, name string) Student {
	return Student{ID: id, Name: name}
}

func main() {
	s1 := NewStudent(1001, "小明")
	s1.Display() //Student ID:1001 Name:小明
	s1.Rename("小暗")
	s1.Display() //Student ID:1001 Name:小暗
}

语法糖

在上面的例子中,我们将Rename方法的接收者改成了指针类型,按理说我们应该用一个Student指针类型变量去调用,像这种:

1
    (&s1).Rename("小暗")

其实对于Display方法来说,我们也可以用Student指针来调用:

1
    (&s1).Display() //Student ID:1001 Name:小暗

这里Golang帮我们提供了一种语法糖,即:

语法糖

不论接收者是非指针类型还是指针类型,在调用时都会被转换成正确的类型。

对于接收者是使用指针类型还是非指针类型,我们需要考虑的是:

  • 对于内存较大的对象,使用指针类型接收者可以有更好的效率。

  • 对于需要对内部元素进行修改的方法,必须使用指针类型否则无法真正修改到数据。

  • 如果需要在方法内对接收者进行再次拷贝,如果接收者是指针类型,其仍然会指向同一块内存。

BGM