方法 在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()
,还是可以正常使用,并且传递过去的是一个指针,可以直接改变原变量中的内容。