This is the 18th day of my participation in the August Challenge

methods

Method statement

Method declarations are similar to normal function declarations, except that the function name is preceded by an additional argument. This parameter binds the method to the type of the parameter

package main import ( "fmt" "math" ) type Point struct { X,Y float64 } func Distance(p,q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } func main() { p := Point{1, 2} q := Point{4, 6} fmt.Println(Distance(p, Q)) / / FMT. "5" function call Println (conviction yourself istance (q)) / / "5" method call}Copy the code

The additional parameter p is called the receiver of the method. In the Go language, the receiver does not use a special name (such as self or this). Instead, we choose the receiver name ourselves, just like we define variables. When calling a method, the receiver precedes the name of the method so that it is consistent with the declaration

The two Distance function declarations above do not conflict; the first declares a package-level function. The second declares a method of type Point, so its name should be point.distance

The expression P. dance is called a selector because it selects the appropriate Distance method for the receiver P. Selectors are also used to select certain field values in a structure type. Since methods and fields come from the same namespace, declaring a method called X in a Point structure type conflicts with the member X in the structure and will fail compilation

Since each type can have many custom methods of its own, we can use the name Distance as the method name for different types. The Path type below represents a line segment, also using Distance as the method name

Type Path []Point func (Path Path) Distance() float64 {sum := 0.0 for I := range Path {if I >0 {sum += path[i-1].Distance(path[i]) } } return sum }Copy the code

Path is a named slice rather than a structural type such as Point, but we can still define methods for it. Unlike other types of languages, Go can bind methods to any type, making it easy to define additional behavior for simple types (numbers, strings, slices, maps, even functions). Any type in the same package can declare a method, as long as its type is neither pointer nor interface

A method that points to the receiver

Since the calling function copies every argument variable, we must use Pointers to pass the address of the variable if the function needs to update a variable, or if an argument is too large and we want to avoid copying the entire argument. This also applies to the update receiver, which we can bind to a pointer type such as *Point

func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}
Copy the code

The name of this method is (*Point).scaleby. Parentheses are required; without parentheses, expressions are resolved to *(point.scaleby)

In formal development, it is customary to follow that if any method of Point uses a pointer receiver, then all methods of that type should use a pointer receiver, even if some methods do not necessarily need one

To prevent confusion, method declarations are not allowed for types that are themselves Pointers

type P *int func (P) f() {/*...... */}// compile error, illegal receiver type // Can call (*Point).scaleby method by providing *Point, such as: R: & Point = {1, 2} r.S caleBy. (2) the FMT Println (* r) / / p: "{2, 4}" or Point of = {1, 2} PPTR: = & p PPTR. ScaleBy. (2) the FMT Println (p) / / "{2, 4}" or p: Point of = {1, 2} (& p) ScaleBy. (2) the FMT Println (p) / / "{2, 4}"Copy the code

If receiver P is a variable of type Point, but the method requires a *Point receiver, we can abbreviate it to

p.ScaleBy(2)
Copy the code

The actual compiler will factor the variable &p. Only variables are allowed to do this, including struct fields like p.X or slice or elements of arrays like a[0]. You cannot call the *Point method on a Point receiver parameter that cannot get an address because you cannot get the address of a temporary variable

Point{1, 2}.scaleby (2)// compile error, cannot get address of Point literalCopy the code

conclusion

In a valid method call expression, only statements that fit one of the following three forms are valid

  1. Argument receivers and parameter receivers are of the same type, such as both type T or both *T
  2. The argument receiver is a variable of type T, and the parameter receiver is of type *T. The compiler implicitly retrieves the address of the variable
func (p *Point) Distance(q Point){/*... */} p.dance (q)// implicitly converts to &pCopy the code
  1. The argument receiver is of type *T and the parameter receiver is of type T. The compiler implicitly dereferences the receiver to get the actual value
func (p Point) Distance(q Point){/*... */} (&p).Distance(q)Copy the code

Nil is a legitimate receiver

Just as some functions allow nil Pointers as arguments, so does the receiver of a method, especially if nil is a meaningful zero value in a type (such as map and Slice types). An example structure method:

Type IntList struct{Value int Tail *IntList} //Sum returns the Sum of list elements func (list *IntList) Sum() int { if list == nil { return 0 } return list.Value + list.Tail.Sum() } l := nil l.Sum()Copy the code

Look at another example of a map type

// Values Map a string to a string list Type Values map[string][]string //Get Returns the first value with a given key func (v Values) Get(key String) string {if vs := v[key]; Len (vs) > 0 {return vs[0]} return "" func (v Values) Add(key, value string) { v[key] = append(v[key], value) } m := url.Values{"lang":{"en"}} m.Add("item", "1") m.Add("item", "2") fmt.Println(m.Get("lang"))// "en" fmt.Println(m.Get("q")) // "" fmt.Println(m.Get("item"))// "1" FMT. Println (m/" item ") / / "[1] 2 m =" nil FMT. Println (m.G et (" item ")) / / "" m. dd (" item", "3") / / downtime: assigned to empty the map typesCopy the code

In the last Get call, the nil receiver acts as an empty map. It can be written equally as Values(nil).get (“item”), but nil.get (“item”) will not compile because the type of nil is not yet determined

Types are formed by intraduct of structures

Again, an example will help you understand

type Point struct{
    X, Y float64
}

type ColoredPoint struct{
    Point
    Color color.RGBA
}
Copy the code

The ColoredPoint structure has a Point type embedded in it to retrieve characters X and Y. Embedding makes it easier to define the ColoredPoint type. You can directly use all fields in ColoredPoint without mentioning the Point type

var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X) // "1"
cp.Point.Y = 2
fmt.Println(cp.Y) // "2"
Copy the code

The same applies to methods of type Point. We can call methods of type Point embedded with type ColoredPoint from a receiver of type ColoredPoint, even if the method is not declared by type ColoredPoint

red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"
Copy the code

Methods of the Point type can be found incorporated into the ColoredPoint type, and in this way, embedding allows for the formation of complex types consisting of many fields, each providing some method

If you know anything about object orientation, you might think that Point is the base class of ColoredPoint, and ColoredPoint is a subclass or a derived class, or that ColoredPoint is one of the manifestations of Point. Distance has a parameter Point, q is not Point, so q has an embedded Point field, but it must be used explicitly

P.styance (q) // Compilation error: cannot convert q to Point typeCopy the code

ColoredPoint is not a Point, but it contains a Point, and it has two additional methods Distance and ScaleBy from Point. If you consider the implementation, the embedded field actually tells the compiler to generate additional wrapper methods to call the method declared by Point, which is equivalent to the following code:

func (p ColoredPoint) Distance(q Point) {
    return p.Point.Distance(q)
}

func (p *ColoredPoint) ScaleBy(factor float64) {
    p.Point.ScaleBy(factor)
}
Copy the code

Method variables and expressions

We can also assign the selector, p.dance, to a method variable, which is a function that binds the method (point.distance) to a receiver, P. A function can be called only by providing arguments, not receivers

p := Point{1, 2} q := Point{4, Println(distanceFromP(q)) // "5" var origin Point // {0, 0} FMT. Println (distanceFromP (origin)) / / "2.236067..." // Method variable scaleP(2) // p becomes (2, 4) scaleP(3) // then (6, 12) scaleP(10) // then (60, 120)Copy the code

This method variable is useful if the API inside the package calls a function value and the consumer expects the function to behave as if it were calling a particular recipient’s method. For example, the function time.afterfunc calls a function after a specified delay

type Rocket struct{/*... */} func (r *Rocket) Launch() {/*... */} r := new(Rocket) time.afterfunc (10 * time.second, func() {r.aunch ()}) AfterFunc(10 * time.second, r.aunch)Copy the code

Related to method variables are method expressions. Instead of calling a normal function, the receiver must be provided when a method is called, and the call follows the selector’s syntax. The method expression is written as T.f or (*T).f, where T is type, a function variable, and replaces the receiver of the original method with the first parameter of the function, so it can be called just like a normal function

P := Point{1, 2} q := Point{4, 6} distance := Point.Distance q)) // "5" fmt.Printf("%T\n", distance) // "func(Point, Point) float64" scale := (*Point).ScaleBy scale(&p, 2) fmt.Println(p) // {2, 4} fmt.Printf("%T\n", scale) // "func(*Point, float64)"Copy the code

If you need a value to represent one of multiple methods of the same type, a method variable can help you call the corresponding method of the value to handle different receivers

type Point struct{ X, Y float64 } func (p Point) Add(q Point) Point{ return Point{p.X+q.X, p.Y+q.Y} } func (p Point) Sub(q Point) Point{ return Point{p.X-q.X, p.Y-q.Y} } type Path []Point func (path Path) TranslateBy(offset Point, add bool) { var op func(p, q Point) Point if add { op = Point.Add } else { op = Point.Sub } for i := range path { // Call path[I].add (offset) or path[I].sub (offset) path[I] = op(path[I], offset)}}Copy the code

encapsulation

The Go language contains only encapsulation of the three main features of object orientation. And, in Go, there is only one way to control the visibility of names: when defined, uppercase identifiers are accessible across packages, while lowercase identifiers can only be used within packages. The same mechanism applies to methods in fields and types within structures (note: encapsulation, in package dimensions)

In the Go language, the encapsulated unit is a package, not a type. Fields within a structure type are visible to all code in the same package, whether it is code inside a function or a method

Three advantages of encapsulation

  1. Because the use method cannot modify the object’s variables directly, no more statements are needed to check the variable’s value
  2. Hiding implementation details prevents user dependent properties from changing, giving designers more flexibility to change API implementations without breaking compatibility

Look at an example to help you understand. The Bytes package has a Buffer type that is used to stack very small strings, so to optimize performance, the implementation reserves some extra space to avoid frequent memory requests. Because Buffer is a structure type, this space uses an additional [64]byte field and is not named with a capital letter. Since this field is private, Buffer users outside the Bytes package will not care about the implementation, other than feeling the performance improvement

type Buffer struct { buf []byte initial [64]byte /*... Func (b *Buffer) Grow(n int) {if b.biuf == nil {b.biuf = B.itial [:0]// Initially use pre-allocated space}  if len(b.buf)+n > cap(b.buf) { buf := make([]byte, b.Len(), 2*cap(b.buf)+n) copy(buf, b.buf) b.buf = buf } }Copy the code
  1. Prevent users from arbitrarily changing object variables. Because an object’s variables can only be modified by functions in the same package, the package author can ensure that all functions maintain resources in the object.

The Counter type below allows the user to increment or reset the Counter, but not arbitrarily set the value of the current Counter

type Counter struct{
    n int
}

func (c *Counter) N() int {
    return c.n
}
func (c *Counter) Increment() {
    c.n++
}
func (c *Counter) Reset() {
    c.n = 0
}
Copy the code

reference

The Go Programming Language — Alan A. A. Donovan

Go Language Learning Notes — Rain Marks