少女祈祷中...

方法

在go语言中,可以对结构体定义方法。比如我们定义这样一个结构体和它的方法。

1
2
3
4
5
6
7
8
9
10
11
type Student struct {
Name string
Id string
Age int
}

func (s Student) Info() {
fmt.Printf("s.Name: %v\n", s.Name)
fmt.Printf("s.Id: %v\n", s.Id)
fmt.Printf("s.Age: %v\n", s.Age)
}

这里要注意的是,我们定义了一个Student类型的方法,接收者是值而不是指针。在main函数里,可以先声明结构体变量,再直接调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var s1 = Student{
Name: "pzqi",
Id: "001",
Age: 9,
}
var s2 = &Student{
Name: "qipz",
Id: "002",
Age: 10,
}
s1.Info()
s2.Info()
}
1
2
3
s.Name: pzqi
s.Id: 001
s.Age: 9

要注意的是,这里不管是声明地址还是变量,都可以作为接收者调用方法,并且都是值传递。

我们定义一个方法用来改变结构体里字段的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (s Student) Rename() {
s.Name = "111"
}
func main() {
var s1 = Student{
Name: "pzqi",
Id: "001",
Age: 9,
}
var s2 = &Student{
Name: "qipz",
Id: "002",
Age: 10,
}
s1.Rename()
s1.Info()
s2.Rename()
s2.Info()
}
1
2
3
4
5
6
s.Name: pzqi
s.Id: 001
s.Age: 9
s.Name: qipz
s.Id: 002
s.Age: 10

这里不管s是值还是地址,最后都是作为值传入了方法中,所以并没有改变原本的s里的字段。
其实在运行之前ide就已经会提示问题了。
就在这一行代码,s.Name = "111"。ide会提示:
ineffective assignment to field Student.Name,意思是无效的赋值。因为我们传入了一个值类型的参数,在函数里只对它进行了一次赋值操作,既没有改变原本的值,也没有在函数中的后续部分使用这个值,所以它就很智能的认为这是无效的操作。

如果想修改的话,必须把方法的接收者定义为指针类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (s *Student) Rename() {
s.Name = "111"
}
func main() {
var s1 = Student{
Name: "pzqi",
Id: "001",
Age: 9,
}
var s2 = &Student{
Name: "qipz",
Id: "002",
Age: 10,
}
s1.Rename()
s1.Info()
s2.Rename()
s2.Info()
}
1
2
3
4
5
6
s.Name: 111
s.Id: 001
s.Age: 9
s.Name: 111
s.Id: 002
s.Age: 10

这种情况下,不管声明变量是值还是指针,也都可以调用这个方法,并且是传递的是指针,在函数中可以直接修改原变量的字段值。而指针可以直接取字段是内部的优化,s.Name(*s).Name是一样的,所以就简单来写。

接口

定义这样一个接口,并让Student这个结构体类型实现接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type act interface {
learn(int, int)
eat(int, int)
sleep(int, int)
}

func (s Student) learn(start, end int) {
fmt.Printf("%v learn %v\n", s.Name, end-start)
}

func (s Student) eat(start, end int) {
fmt.Printf("%v eat %v\n", s.Name, end-start)
}

func (s Student) sleep(start, end int) {
fmt.Printf("%v sleep %v\n", s.Name, end-start)
}

这里实现方法时,既可以用值当接收者,也可以用指针当接收者。
但这两种方式有所不同。
我们再定义一个函数,接收接口类型。

1
2
3
4
5
func bhv(a act) {
a.eat(10, 12)
a.learn(13, 15)
a.sleep(19, 23)
}

如果是用值作为接收者,那么这个接收接口类型的函数接收的结构体既可以是值,也可以是指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var s1 = Student{
Name: "pzqi",
Id: "001",
Age: 9,
}
var s2 = &Student{
Name: "qipz",
Id: "002",
Age: 10,
}
bhv(s1)
bhv(s2)
}
1
2
3
4
5
6
pzqi eat 2
pzqi learn 2
pzqi sleep 4
qipz eat 2
qipz learn 2
qipz sleep 4

但是如果用结构体在实现接口中的方法时,接收者是指针类型,那么接收接口类型的函数接收的结构体必须是一个指针。
比如我们只修改接口中的其中一个方法的实现方式。

1
2
3
func (s *Student) learn(start, end int) {
fmt.Printf("%v learn %v\n", s.Name, end-start)
}

这个时候编译器就会报错:
cannot use s1 (variable of type Student) as act value in argument to bhv: Student does not implement act (method learn has pointer receiver)
大致意思就是,我们声明的s1是一个结构体的值类型变量,但是把s1作为参数传入bhv这个函数中,bhv调用了learn方法,而learn方法的接收者又是指针类型,所以编译器认为s1并没有实现这个接口,不能作为act接口类型传入函数。
但是如果直接调用s1.learn(),还是可以正常使用,并且传递过去的是一个指针,可以直接改变原变量中的内容。