前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Methods in Go

Methods in Go

作者头像
李海彬
发布2019-05-08 10:53:36
6470
发布2019-05-08 10:53:36
举报
文章被收录于专栏:Golang语言社区Golang语言社区

原文地址:https://go101.org/article/method.html

Go supports some object-orient programming features. Method is one of these features. This article will introduce method related concepts in Go.

Method Declarations

In Go, we can (explicitly) declare a method for type T and *T , where T must satisfy 4 conditions:

1. T must be a defined type; 2. T must be defined in the same package as the method declaration; 3. T must not be a pointer type; 4. T must not be an interface type. Interface types will be explained the next article. Type T and *T are called the receiver type of the respective methods declared for them. Type T is called the receiver base types of all methods declared for both type T and *T .

Note, we can also declare methods for alias types of the T and *T types specified above. The effect is the same as declaring methods for the T and *T types themselves.

If a method is declared for a type, we can say the type has (or owns) the method.

From the above listed conditions, we will get the conclusions that we can never (explicitly) declare methods for

1. built-in basic types, such as int and string , for we can't declare methods in the builtin standard package. 2. interface types. But an interface type can own methods. Please read the next article for details. 3. non-defined types except the pointer types *T which are described above, including all kinds of unnamed composite types. However, if an unnamed struct type embeds other types which have methods, then compiler will implicitly declare some methods for the unnamed struct type and the unnamed pointer type whose base type is the unnamed struct type. Please read type embedding for details. A method declaration is similar to a function declaration, but it has an extra parameter declaration part. The extra parameter part can contain one and only one parameter of the receiver type of the method. The only one parameter is called a receiver parameter of the method declaration. The receiver parameter must be enclosed in a () and declared between the func keyword and the method name.

Here are some method declaration examples:

代码语言:javascript
复制
 1// Age and int are two distinct types. We
 2// can't declare methods for int and *int,
 3// but can for Age and *Age.
 4type Age int
 5func (age Age) LargerThan(a Age) bool {
 6    return age > a
 7}
 8func (age *Age) Increase() {
 9    *age++
10}
11
12// Receiver of custom defined function type.
13type FilterFunc func(in int) bool
14func (ff FilterFunc) Filte(in int) bool {
15    return ff(in)
16}
17
18// Receiver of custom defined map type.
19type StringSet map[string]struct{}
20func (ss StringSet) Has(key string) bool {
21    _, present := ss[key]
22    return present
23}
24func (ss StringSet) Add(key string) {
25    ss[key] = struct{}{}
26}
27func (ss StringSet) Remove(key string) {
28    delete(ss, key)
29}
30
31// Receiver of custom defined struct type.
32type Book struct {
33    pages int
34}
35func (b Book) Pages() int {
36    return b.pages
37}
38func (b *Book) SetPages(pages int) {
39    b.pages = pages
40}

From the above examples, we know that the receiver base types not only can be struct types, but also can be other kinds of types, such as basic types and container types, as long as the receiver base types satisfy the 4 conditions listed above.

In some other programming languages, the receiver parameter names are always the implicit this , which is not a recommended identifier for receiver parameter names in Go.

The receiver of type *T are called pointer receiver, non-pointer receivers are called value receivers. Personally, I don't recommend to view the terminology pointer as an opposite of the terminology value, for pointer values are just special values. But, I am not against using the pointer receiver and value receiver terminologies here. The reason will be explained below.

Method names can be the blank identifier _ . A type can have multiple methods with the blank identifier as names. But such methods can never be called. Only exported methods can be called from other packages. Method calls will be introduced in a later section.

Each Method Corresponds to an Implicit Function

For each method declaration, compiler will declare a corresponding implicit function for it. For the last two methods declared for type Book and type *Book in the last example in the last section, two following functions are implicitly declared by compiler:

代码语言:javascript
复制
1func Book.Pages(b Book) int {
2    // The body is the same as the Pages method.
3    return b.pages
4}
5
6func (*Book).SetPages(b *Book, pages int) {
7    // The body is the same as the SetPages method.
8    b.pages = pages
9}

In each of the two implicit function declarations, the receiver parameter is removed from its corresponding method declaration and inserted into the normal parameter list as the first one. The function bodies of the two implicitly declared functions is the same as their corresponding method explicit bodies.

The implicit function names, Book.Pages and (*Book).SetPages , are both of the form TypeDenotation.MethodName . As identifiers in Go can't contain the period special characters, the two implicit function names are not legal identifiers, so the two functions can't be declared explicitly. They can only be declared by compiler implicitly, but they can be called in user code:

代码语言:javascript
复制
 1package main
 2
 3import "fmt"
 4
 5type Book struct {
 6    pages int
 7}
 8func (b Book) Pages() int {
 9    return b.pages
10}
11func (b *Book) SetPages(pages int) {
12    b.pages = pages
13}
14
15func main() {
16    var book Book
17    // Call the two implicit declared functions.
18    (*Book).SetPages(&book, 123)
19    fmt.Println(Book.Pages(book)) // 123
20}

In fact, compilers not only declare the two implicit functions, they also rewrite the two corresponding explicit declared methods to let the two methods call the two implicit functions in the method bodies (at least, we can think this happens), just like the following code shows:

代码语言:javascript
复制
1func (b Book) Pages() int {
2    return Book.pages(b)
3}
4func (b *Book) SetPages(pages int) {
5    (*Book).SetPages(b, pages)
6}

Implicit Methods With Pointer Receivers

For each method declared for value receiver type T , a corresponding method with the same name will be implicitly declared by compiler for type *T . By the example above, the Pages method is declared for type Book , so compiler will implicitly declare a method with the same name Pages for type *Book . The same name method only contain one line of code, which is a call to the implicit function Book.Pages introduced above.

代码语言:javascript
复制
1func (b *Book) Pages() int {
2    return Book.Pages(*b)
3}

This is why I don't reject to use the value receiver terminology (as the opposite of the pointer receiver terminology). After all, when we expliclty declare a method for a non-pointer type, in fact two methods are declared, the explicit one for the non-pointer type, the other implicit one is for the corresponding pointer type.

As the last section has mentioned, for each declared method, compiler will also declare a corresponding implicit function for it. So for the just mentioned implicitly declared method, the following implicit function is declared by compiler.

代码语言:javascript
复制
1func (*Book).Pages(b *Book) int {
2    return Book.Pages(*b)
3}

In other words, for each explicitly declared method with a value receiver, two implicit functions and one implicit method will also be declared at the same time.

Method Prototypes and Method Sets

A method prototype can be viewed as a function prototype without the func keyword. We can view each method declaration is composed of the func keyword, a receiver parameter declaration, a method prototype and a method (function) body.

For example, the method prototypes of the Pages and SetPages methods shown above are

代码语言:javascript
复制
1Pages() int
2SetPages(pages int)
3

Each type has a method set. The method set of a non-interface type is composed of all the method prototypes of the methods declared, either explicitly or implicitly, for the type, except the ones whose names are the blank identifier _ . Interface types will be explained in the next article.

For example, the method sets of the Book type shown in the previous sections is

代码语言:javascript
复制
1Pages() int

and the method set of the *Book type is

代码语言:javascript
复制
1Pages() int
2SetPages(pages int)
3

The order of the method prototypes in a method set is not important for the method set.

For a method set, if every method prototype in it is also in another method set, then we say the former method set is a subset of the latter one, and the latter one is a superset of the former one. If two method sets are subsets (or supersets) of each other, then we say the two method sets are identical.

Given a type T , assume it is neither a pointer type nor an interface type, for the reason mentioned in the last section, the method set of a type T is always a subset of the method set of type *T . For example, the method set of the Book type shown above is a subset of the method set of the *Book type.

Please note, non-exported method names, which start with lower-case letters, from different packages will be always viewed as two different method names, even if the two method names are the same in literal.

Method sets play an important role in the polymorphism feature of Go. About polymorphism, please read the next article (interfaces in Go) for details.

The method sets of the following types are always blank: 1. built-in basic types. 2. defined pointer types. 3. pointer types whose base types are interface or pointer types. 4. undefined array, slice, map, function and channel types.

Method Values and Method Calls

Methods are special functions in fact. Methods are often called member functions. When a type owns a method, each value of the type will own an immutable member of function type. The member name is the same as the method name and the type of the member is the same as the function declared with the form of the method declaration but without the receiver part.

A method call is just a call to such a member function. For a value v , its method m can be represented with the selector form v.m , which is a function value.

An example containing some method calls:

代码语言:javascript
复制
 1package main
 2
 3import "fmt"
 4
 5type Book struct {
 6    pages int
 7}
 8
 9func (b Book) Pages() int {
10    return b.pages
11}
12
13func (b *Book) SetPages(pages int) {
14    b.pages = pages
15}
16
17func main() {
18    var book Book
19
20    fmt.Printf("%T \n", book.Pages)       // func() int
21    fmt.Printf("%T \n", (&book).SetPages) // func(int)
22    // &book has an implicit method.
23    fmt.Printf("%T \n", (&book).Pages) // func() int
24
25    // Call the three methods.
26    (&book).SetPages(123)
27    book.SetPages(123) // equivalent to the last line
28    fmt.Println(book.Pages())    // 123
29    fmt.Println((&book).Pages()) // 123
30}

(Different from C language, there is not the -> operator in Go to call methods with pointer receivers, so (&book)->SetPages(123) is illegal in Go.)

Wait! Why does the line book.SetPages(123) in the above example compile okay? After all, the method SetPages is not declared for the book type. Ah, this is just a syntactic sugar to make programming convenient. This sugar only works for addressable value receivers. Compiler will automatically take the address of the addressable book value when it is passed as the receiver argument of a SetPages method call.

As above just mentioned, when a method is declared for a type, each value of the type will own a member function. Zero values are not exceptions, whether or not the zero values if the type are represented by nil .

Example:

代码语言:javascript
复制
 1package main
 2
 3type StringSet map[string]struct{}
 4func (ss StringSet) Has(key string) bool {
 5    // Never panic here, even if ss is nil.
 6    _, present := ss[key]
 7    return present
 8}
 9
10type Age int
11func (age *Age) IsNil() bool {
12    return age == nil
13}
14func (age *Age) Increase() {
15    *age++ // If age is a nil pointer, then
16           // dereferencing it will panic.
17}
18
19func main() {
20    _ = (StringSet(nil)).Has   // will not panic
21    _ = ((*Age)(nil)).IsNil    // will not panic
22    _ = ((*Age)(nil)).Increase // will not panic
23
24    _ = (StringSet(nil)).Has("key") // will not panic
25    _ = ((*Age)(nil)).IsNil()       // will not panic
26
27    // This following line will panic. But the panic is
28    // not caused by invoking the method. It is caused by
29    // the nil pointer dereference within the method body.
30    ((*Age)(nil)).Increase()
31}

Receiver Arguments Are Passed by Copy

Same as general function arguments, the receiver arguments are also passed by copy. So, the modifications on the direct part of a receiver argument in a method call will not be reflected to the outside of the method.

An example:

代码语言:javascript
复制
 1package main
 2
 3import "fmt"
 4
 5type Book struct {
 6    pages int
 7}
 8
 9func (b Book) SetPages(pages int) {
10    b.pages = pages
11}
12
13func main() {
14    var b Book
15    b.SetPages(123)
16    fmt.Println(b.pages) // 0
17}

Another example:

代码语言:javascript
复制
 1package main
 2
 3import "fmt"
 4
 5type Book struct {
 6    pages int
 7}
 8
 9type Books []Book
10
11func (books Books) Modify() {
12    // Modifications on the underlying part of
13    // the receiver will be reflected to outside
14    // of the method.
15    books[0].pages = 500
16    // Modifications on the direct part of the
17    // receiver will not be reflected to outside
18    // of the method.
19    books = append(books, Book{789})
20}
21
22func main() {
23    var books = Books{{123}, {456}}
24    books.Modify()
25    fmt.Println(books) // [{500} {456}]
26}

Some off topic, if the two lines in the orders of the above Modify method are exchanged, then both of the modifications will not be reflected to outside of the method body.

代码语言:javascript
复制
 1func (books Books) Modify() {
 2    books = append(books, Book{789})
 3    books[0].pages = 500
 4}
 5
 6func main() {
 7    var books = Books{{123}, {456}}
 8    books.Modify()
 9    fmt.Println(books) // [{123} {456}]
10}

The reason here is that the append call will allocate a new memory block to storage the elements of the copy of the passed slice receiver argument. The allocation will not reflect to the passed slice receiver argument itself.

To make both of the modifications be reflected to outside of the method body, the receiver of the method must be a pointer one.

代码语言:javascript
复制
 1func (books *Books) Modify() {
 2    *books = append(*books, Book{789})
 3    (*books)[0].pages = 500
 4}
 5
 6func main() {
 7    var books = Books{{123}, {456}}
 8    books.Modify()
 9    fmt.Println(books) // [{500} {456} {789}]
10}

Should a Method Be Declared With Pointer Receiver or Value Receiver?

Firstly, from the last section, we know that sometimes we must declare methods with pointer receivers.

In fact, we can always declare methods with pointer receivers without any logic problems. It is just a matter of program performance that sometimes it is better to declare methods with value receivers.

For the cases value receivers and pointer receivers are both acceptable, here are some factors needed to be considered to make decisions. 1. Too many pointer copies may cause heavier workload for garbage collector. 2. If the size of a value receiver type is large, then the receiver argument copy cost may be not negligible. Pointer types are all small-size types. 3. Declaring methods of both value receivers and pointer receivers for the same base type is more likely to cause data races if the declared methods are called concurrently in multiple goroutines. 4. Values of the types in the sync standard package should not be copied, so defining methods with value receivers for struct types which embedding the types in the sync standard package is problematic. If it is hard to make a decision whether a method should use a pointer receiver or a value receiver, then just choose the pointer receiver way.


版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Method Declarations
  • Each Method Corresponds to an Implicit Function
  • Implicit Methods With Pointer Receivers
  • Method Prototypes and Method Sets
  • Method Values and Method Calls
  • Receiver Arguments Are Passed by Copy
  • Should a Method Be Declared With Pointer Receiver or Value Receiver?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档