Methods are special functions bound to object instances.
Method is the basic concept of object-oriented programming, used to maintain and display the object's own state. Objects are introverted, each instance object has its own different independent characteristics, and its external communication interface is exposed by its attributes and methods.
Ordinary functions focus on the algorithm flow, complete specific logical operations by receiving parameters, and return the final result.
In other words, methods have associated state, while functions usually don't.
The grammatical difference between method and function definition is that the former has a pre-instance to receive parameters, and the compiler uses this to determine the type of the method. In some languages, although there is no explicit definition, the this instance parameter is implicitly passed in the call.
You can define methods for the current package, as well as any type other than interfaces and pointers.
type N intfunc(n N)toString()string{
return fmt.Sprintf("%#x", n)}funcmain(){
var a N =25println(a.toString())}
The method also does not support overloading. There is no restriction on the name of the received parameter, and a short and meaningful name will be used by convention (this, self is not recommended). If the method does not reference the instance, the parameter name can be omitted and only the type is retained.
type N intfunc(N)test(){
println("hi!")}
The method can be regarded as a special function, so the receiver type can naturally be a basic type or a pointer type. This will affect whether the object instance is copied at the time of the call.
type N intfunc(n N)value(){
n++
fmt.Printf("v:%p,%v\n",&n, n)}func(n *N)pointer(){
(*n)++
fmt.Printf("v:%p,%v\n", n,*n)}funcmain(){
var a N =25
a.value()
fmt.Printf("a: %p,%v\n",&a,a)
a.pointer()
fmt.Printf("a: %p,%v\n",&a,a)}
You can use instance values or pointers to call methods, and the compiler will automatically convert between the basic type and the pointer type according to the method receiver type.
funcmain(){
var a N =25
p :=&a
a.value()//26,但a的值没变
p.pointer()// a=26,p是a的地址
p.value()//27, 但a的值还是26
p.pointer()//a=27}
The receiver of the pointer type must be a valid pointer (including nil) or be able to obtain an example address.
How to choose the receiver type of the method?
To modify the instance status, use *T
No need to modify the state of small objects or fixed values, it is recommended to use T
*T is recommended for large objects to reduce copying costs.
Reference type, string, function and other pointer packaging objects, directly use T
If it contains synchronization fields such as Mutex, use *T to avoid invalid lock operation due to copying
For other situations that cannot be determined, use *T
2. Anonymous fields
The method can be called like an anonymous field member, and the compiler is responsible for finding it.
type data struct{
sync.Mutex
buf [1024]byte}funcmain(){
d := data{
}
d.Lock()//编译器会处理为 sync.(*Mutex).Lock() 调用defer d.Unlock()}
The method also has the concealment problem of the same name, but with this feature, a similar override operation can be achieved.
type user struct{
}type manager struct{
user
}func(user)toString()string{
return"user"}func(m manager)toString()string{
return m.user.toString()+"; manager"}funcmain(){
var m manager
println(m.toString())println(m.user.toString())}
Output
user; manager
user
3. Method set
Type has a method set (method set) associated with it, which determines whether it implements an interface
The type T method set contains all receiver T methods
Type *T method set contains all receiver T+ *T methods
Anonymously embedded S, T method set includes all receiver S methods
Anonymously embed *S, T method set includes all receiver S+*S methods.
Anonymously embed S or *S, *T method set includes all receiver S+ *S methods.
The method set only affects interface implementation and method expression conversion, and has nothing to do with calling methods through instances or instance pointers. The instance does not use the method set, but calls it directly.
Obviously, anonymous fields are prepared for the method set. Otherwise, there is no need to spend a lot of time writing less field names.
Three characteristics of object-oriented: encapsulation, inheritance, polymorphism. Go only implements some features and prefers the idea of "combination over inheritance".
The modules are decomposed into smaller units that are independent of each other to deal with different aspects of the needs, and finally combined together in an anonymous embedding manner to jointly realize the external interface.
Its short and consistent call method hides the internal implementation details.
4. Expression
Methods are the same as functions. In addition to calling them directly, they can also be assigned to variables and passed as parameters. According to the different ways of reference, it can be divided into two states: expression and value
1. Method Expression
The method expression referenced by the type will be restored to the normal function style, the receiver is the first parameter, and the parameters must be passed explicitly when calling. As for the type, it can be T or *T, as long as the target method exists in the retype method set.
type N intfunc(n N)test(){
fmt.Printf("test.n: %p,%d\n",&n, n)}funcmain(){
var n N =25
fmt.Printf("main.n: %p, %d\n",&n,n)
f1 := N.test // func(n N)f1(n)
f2 :=(*N).test // func(n *N)f2(&n)//按方法集中的签名传递正确类型的参数}
Although the receiver type of the test method packaged by the *N method set is different, the compiler will ensure that the value is copied and passed according to the original defined type.
Of course, you can also call the expression method directly.
funcmain(){
var n N =25
N.test(n)(*N).test(&n)}
2. Method Value
Based on the method value referenced by the instance or pointer, the parameter will not be changed before, and it will still be called in the normal way.
But when the method value is assigned to a variable or a parameter is passed, the receiver object required for the execution of the method is immediately calculated and copied, and bound to it, so that the receiver parameter can be implicitly passed in when it is executed later.
funcmain(){
var n N =100
p :=&n
n++
f1 := n.test
n++
f2 := p.test
n++
fmt.Printf("main.n:%p, %v\n",p,n)f1()f2()}
The compiler will generate a wrapper function for the method value to implement indirect calls. As for the receiver copy, the implementation method is basically the same as that of the closure. It is packed into funcval and passed through the DX register.
Of course, if the receiver of the target method is a pointer type, then only the pointer is copied.
type N intfunc(n *N)test(){
fmt.Printf("test.n: %p,%d\n", n,*n)}funcmain(){
var n N =100
p :=&n
n++
f1 := n.test
n++
f2 := p.test
n++
fmt.Printf("main.n:%p, %v\n",p,n)f1()f2()}