6. Method (go language study notes)

6. Method (go language study notes)


table of Contents

  1. definition
  2. Anonymous field
  3. Method set
  4. expression

1. Definition

  1. Methods are special functions bound to object instances.
  2. 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.
  3. Ordinary functions focus on the algorithm flow, complete specific logical operations by receiving parameters, and return the final result.
  4. In other words, methods have associated state, while functions usually don't.
  5. 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.
  6. You can define methods for the current package, as well as any type other than interfaces and pointers.
type N int

func (n N) toString() string {
    
    
	return fmt.Sprintf("%#x", n)
}

func main() {
    
    
	var a N = 25
	println(a.toString())
}
  1. 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 int

func (N) test() {
    
    
	println("hi!")
}
  1. 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 int

func (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)
}

func main() {
    
    
	var a N = 25

	a.value()
	fmt.Printf("a: %p,%v\n",&a,a)
	a.pointer()
	fmt.Printf("a: %p,%v\n",&a,a)
}
  1. Output
v: 0xc000018090,26
a: 0xc000018088,25
v: 0xc000018088,26
a: 0xc000018088,26

  1. 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.
func main() {
    
    
	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
}
  1. Output
v: 0xc0000a4010,26
v: 0xc0000a4008,26
v: 0xc0000a4040,27
v: 0xc0000a4008,27
  1. Multi-level paper call methods cannot be used.
    Insert picture description here
  2. The receiver of the pointer type must be a valid pointer (including nil) or be able to obtain an example address.
  3. How to choose the receiver type of the method?
    1. To modify the instance status, use *T
    2. No need to modify the state of small objects or fixed values, it is recommended to use T
    3. *T is recommended for large objects to reduce copying costs.
    4. Reference type, string, function and other pointer packaging objects, directly use T
    5. If it contains synchronization fields such as Mutex, use *T to avoid invalid lock operation due to copying
    6. For other situations that cannot be determined, use *T

2. Anonymous fields

  1. 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
}

func main() {
    
    
	d := data{
    
    }
	d.Lock() //编译器会处理为 sync.(*Mutex).Lock() 调用
	defer d.Unlock()
}
  1. 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"
}

func main() {
    
    
	var m manager
	println(m.toString())
	println(m.user.toString())
}
  1. Output
user; manager
user

3. Method set

  1. Type has a method set (method set) associated with it, which determines whether it implements an interface
    1. The type T method set contains all receiver T methods
    2. Type *T method set contains all receiver T+ *T methods
    3. Anonymously embedded S, T method set includes all receiver S methods
    4. Anonymously embed *S, T method set includes all receiver S+*S methods.
    5. Anonymously embed S or *S, *T method set includes all receiver S+ *S methods.
  2. 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.
  3. Obviously, anonymous fields are prepared for the method set. Otherwise, there is no need to spend a lot of time writing less field names.
  4. Three characteristics of object-oriented: encapsulation, inheritance, polymorphism. Go only implements some features and prefers the idea of ​​"combination over inheritance".
  5. 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.
  6. Its short and consistent call method hides the internal implementation details.

4. Expression

  1. 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

  1. 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 int

func (n N) test() {
    
    
	fmt.Printf("test.n: %p,%d\n", &n, n)
}

func main() {
    
    
	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)  		//按方法集中的签名传递正确类型的参数
}
  1. Output
main.n:0xc000018088, 25
test.n: 0xc0000180a0,25
test.n: 0xc0000180b0,25

  1. 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.
  2. Of course, you can also call the expression method directly.
func main() {
    
    
	var n N = 25
	N.test(n)
	(*N).test(&n)
}

2. Method Value

  1. 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.
  2. 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.
func main() {
    
    
	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()
}
  1. Output
main.n:0xc000096008, 103
test.n: 0xc000096020,101
test.n: 0xc000096030,102
  1. 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.
  2. Of course, if the receiver of the target method is a pointer type, then only the pointer is copied.
type N int

func (n *N) test() {
    
    
	fmt.Printf("test.n: %p,%d\n", n, *n)
}

func main() {
    
    
	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()
}
  1. Output
main.n:0xc000096008, 103
test.n: 0xc000096008,103
test.n: 0xc000096008,103
  1. As long as the receiver parameter type is correct, using nil can also be executed.

Guess you like

Origin blog.csdn.net/weixin_41910694/article/details/111356464