0. Second-level pointer

For some reason this post appears at the top of bing and Google searches for “Golang secondary pointer” (Baidu does not). I feel it is my duty to explain how the Golang secondary pointer works here.

  1. A pointer is the address of a value. All variables are addressable, but not all values. like1.2Struct values do not have an address, whereas struct values do have an addressThis article).
    • &It is an addressable operator. If the value behind it can be addressed, an error will be reported if it cannot
  2. In addition, the pointer itself is also a value, and this value and1.2Again, it’s not addressable. That’s why&(&a)No, but firstb = &aAgain,&bBut it can be.
    • A pointer itself does not have an address, but it does when assigned to a variable. That is why we call it a second-level pointer. Only an address assigned to a variable can produce a second-level pointer.
  3. Various posts often use mixed Pointers, pointer variables and pointer types, all called Pointers. Here’s the difference between themp := &xAs an example.
    1. P is the pointer variable,
    2. The value that P stores, the address, is a pointer.
    3. *x is a pointer type that you see when you print the type.
    4. When you read this post in the future, pay attention to which pointer the post is trying to express.
  4. Here’s how to get a second-order pointer. Suppose there are two cases:
    • The value of the original data itself has an address:
      var a = &MyStruct{"hi"} // Assign the address of the structure to the first level pointer (a)
      var b = &a // Select * from (a) where (c);
      Copy the code
    • The value of the raw data itself has no address:
      var a = 1 // int has no address and can only be assigned to a variable (a) first
      var b = &a // Assign the address of a to the first level pointer (b)
      var c = &b // Select * from (b) where (c);
      Copy the code
  5. And then let’s do a couple of examples, because examples are always the best way to understand:
    1. The code for the first example is as follows. What is the output?
      func main(a) {
          x := 0
          p := &x
          test(&p)
          fmt.Println(*p)
      }
      
      func test(p **int) {
          x := 100
          *p = &x
      }
      Copy the code
      • The answer is: 100
    2. Same code, change the output tofmt.Pritln(x)What’s the answer?
      • The answer is: 0

    It can be explained by this picture:

    1. Run test first*p = &xBefore, there was a structure inside the test function (inside, outside, respectively).Here are a few things to note:
      • Pointer variables don’t point to memory, only addresses point to memory, and pointer variables just store the value of the address. For example, 0x0001 in the figure above points to the address where the X variable is stored outside the test function. But p (outer) is a pointer variable, which itself is just a value, which is 0x0001, and does not refer to x (outer).
      • In other words,P ()The value is0x0002.* p (inside)The value is0x0001.* * p (inside)The value of theta is 0.
      • Note that you can never change the address (not even the pointer lvalue), only which address is stored in the pointer variable (the pointer lvalue is actually dry).
    2. And then you run it*p = &xAfter that, the graph looks like this:Note that:
      • You might think*p (inside) = &xWhat you change is the direction of p inside, but it’s not. First of all, on the left* p (inside)Addressing operations, you get p(outside), and the right&xTake out the(x)That’s the address of theta0x0004. So this whole operation is equal toP (external) = 0x0004.
      • So the only way to get the address is to use an ampersand, whereas an asterisk takes you directly to memory, and if there’s a variable in that memory, you can change that variable directly.
    3. Then add a variation here that does not change *p, but introduces an intermediate variable, and observe how this intermediate variable changes:
    func main(a) {
        x := 0
        p := &x
        pp := &p // Put the second level pointer into a variable, of course the result will not change, but can you draw where pp is?
        test(pp)
        fmt.Println(**pp) // What is **pp now and will it be changed?
    }
    
    func test(p **int) {
        x := 100
        *p = &x
    }
    Copy the code

    And the answer is: 100 will change. The graph is as follows:

    • Although pp is a new variable, using * returns the old variable.*ppThe equivalent ofp.**ppThe equivalent of*pThe equivalent ofx.

The original article started here

Today’s learning material is A Tour of Go.

1. Basic grammar

Some special syntax for go:

1.1 variable

  1. You can only declare variables outside of a function, you can’t write other code. And it doesn’t work outside of the function: =.
  2. Var can declare multiple variables and initializers. If you have an initializer, you don’t need to write the type.
  3. Values that are not initialized have a zero value, the number is 0, the Boolean is false, the string is “”, and the rest is nil.
  4. Constants don’t work: =That must beconst x = xxForm.

1.2 Judgment/loop

  1. For and if cannot be wrapped in parentheses, but this refers to the outermost layer. It’s okay to have brackets on the inside.
  2. There’s no while in go, and for works in both loops.
  3. If /switch can also initialize a variable like for, scoped only in if/switch/for.
  4. The infinite loop doesn’t have to be writtenfor true {}Directly onefor {}Can.
  5. In contrast to other languages, switch does not write break and adds a break automatically. Instead, if you want to run consecutive cases, addfallthroughThe keyword.
  6. Switch can be used directlyswitch {}You can then use case/default as multiple if/else constructs. You can optimize multiple if/else constructs.

1.3 the defer

  1. deferThe keyword is followed by a function call, which is delayed until the outermost function runs, although the parameters of the function call are evaluated immediately.
  2. If there are more than one defer, you go into a stack structure, which means first in, last out.

1.4 a pointer

  1. Go also has Pointers, and the syntax is the same as C. But there’s no pointer operation.

1.5 struct

  1. Go also has structs.
    type struct MyStruct {
        int A
        int B
    }
    func main(a) {
        m := MyStruct{1.2} / / or | | var m MyStruct
                            // || m.A = 1
                            // || m.B = 2
        pm := &m
        // The following three results are the same, all are 1, note the second one
        // The second one is logically incorrect, but go simplifies, using pm.A is equivalent to using (* PM).a
        m.A
        pm.A
        (*pm).A
        
    }
    Copy the code
    // Multiple declarations
    m1 = MyStruct{1.2}
    m2 = MyStruct{A: 1} // B is zero, in this case 0
    m3 = MyStruct{} // All values are zero
    pm4 = &MyStruct{1.2} // a struct pointer
    Copy the code

1.6 array/slice

  1. The brackets are written before, as invar a = [<number>]<type>Said a<number>A type of<type>Is an array of elements.
  2. Slice can be thought of as a mutable array to some extent, not written<number>Can.
    • But slice itself is similar to Slice in Python, which is a reference to part of an array, for example
      // Initialize an immutable array of type [5]int
      var a = [5]int{1.2.3.4.5}
      
      // The type of a[1:3] immediately becomes []int
      // If you write a number in parentheses before []int, you will get an error
      // Because a[1:3] is a slice, even though you know it has three elements, it is of type []int
      // []int and [3]int are different types. So you can't assign
      var b []int = a[1:3] // the same usage as python, also [1,3]
      
      // If you declare a slice instead of the old array, you still create a new array behind it
      var c = []int{1.2.3.4.5} // Do you think you created a slice directly? not
      //
      var c1 = [5]int{1.2.3.4.5}
      var c2 =c1[:]
      
      Copy the code
    • In the above code, b is still a reference to part of A. If A changes, B will change, and vice versa. And if more than one slice references the same part of A, it will also change.
  3. Slice has both length and volume. Len () and cap() are used to obtain:
    1. Length is the number of elements in slice
    2. The capacity is complicated, it’s related to the original array, it’s the position of the first element of the slice in the original array, to the length of the last element of the original array.

    If YOU slice the slices again,This is different from Python, need to use capacity:

    // Note that this example has only one variable, s, from beginning to end
    var s = []int{0.1.2.3.4}
    
    s = s[0:4] []int{0,1,2,3}
    
    s = s[2:5] [int]{2,3,4}, as if the previous step had not changed the slice
               // Do you think the index will cross the line?
               [int]{2,3,4}
               // ?
               // But this does not mean that s does not change, it does change, but the slice is the underlying array
    Copy the code

    Okay, you think you get it, but you don’tLet’s look at another example:

    // Same as before
    var s = []int{0.1.2.3.4}
    s = s[0:4]
    // Same for this step
    s = [2:5]
    // Now?
    s = [0:3] []int{0,1,2} []int{0,1,2
              []int{2,3,4}
              // ??     
    Copy the code

    Why is that?

    • Slice is actually a concept of wrapping left but not right. When slicing, the left elements are no longer included in theThe section ofYes, but the element on the right is still contained in the slice, just not printed or accessed, just sliced again. That’s the difference between length and volume.
    • But both length and capacity are Pointers. The slice that changes the slice, the underlying array, and all related slices change accordingly. Slice is always a reference.
  4. As mentioned earlier, the zero value of slice is nil. I want the length, the capacity to be zero,And there’s no underlying array.
  5. Create slices using make:make([]int, 0, 5)Create a slice with length 0 and capacity 5. The initial value is zero, this is int, so it’s all zero.
  6. Multiple slices, just two empty brackets.
  7. Go provides an append function. The first parameter is a slice, followed by multiple parameters, which can be successively added to the end of the slice, the type must be consistent. This function returns a new slice.
    • Note 1: After append, if the capacity is insufficient, go will automatically expand the capacity. This changes the underlying array, so changing the new slice does not change the old slice and the underlying array because the references are no longer the same array.
    • Note 2: As mentioned above, append expands its capacity, but not necessarily as much as it needs to. Sometimes the size of the slice is larger than its length, which means that the capacity is not controllable. For example, you now have a slice of length 1 and capacity 1. Use append and an element. There is not enough capacity, so we need to add another capacity. But append might add two capacities. For full control, use AppendByte.
  8. Range Returns two values, index and value, when traversing a slice
  9. Make can only create one dimensional slices, and two dimensional can only write that kind of junk code with a for loop.
  10. Slice is encapsulated as a pointer and does not require an *. The second level pointer is added.
    • Here are a few things to note when slice is used as a function argument:
      1. Slice is an address, so you can pass slice directly if you change only the elements in slice. Such as:func MyFunction(numbers []int) {}.
      2. When numbers comes in, the outside variable records an address (say 0x11111). When numbers comes in, the function copies the address. The two addresses are the same. The address itself is also a value, passed inside the function as a value, but it happens to be the same address inside and outside. By manipulating this address, the inside can also change the outside.
      3. But!!If an internal append operation is performed on Slice, even if similarnumbers = append(numbers, 1), you cannot change the external values.
      4. ????? why
      5. Because Append creates a new address (say 0x22222) and assigns that value to numbers. But the external variable still records the old address 0x11111. So nature doesn’t change.
      6. A normal user-defined structure, by contrast, does not have the syntax sugar of slice, so even changing its attributes requires adding a pointer to make it a first-level pointer. If you want to create a new structure to replace the original structure, you need to add two asterisks **MyStruct to become a second-level pointer. Take a closer look at these examples:
        package main
        
        import "fmt"
        
        type Person struct {
            name string
            age  int
        }
        
        func (p *Person) changeAge(a) {
            p.age = 22
        }
        
        // First level pointer method
        func (p *Person) changePersonFirstLevelPointer(a) {
            p = &Person{"shabi".25}}// Secondary pointer method
        func (p *Person) changePersonSecondLevelPointer(a) {
            *p = Person{"shabi".25}}func main(a) {
            var person = Person{"zouli".17}
            fmt.Println("Initialize to:", person)
        
            // A level pointer that can change properties
            // So here the age becomes 22
            // use person instead of &person, this is the syntax sugar of go, you can add it if you want.
            person.changeAge() // Can also be (&person).changeage ()
            fmt.Println("First-order Pointers can change properties:", person)
        
            // Level-1 Pointers cannot transform themselves
            // So this is still 22
            person.changePersonFirstLevelPointer()
            fmt.Println("First-order Pointers cannot change themselves:", person)
        
            // But second-level Pointers can change themselves
            // You can see that "zouli" becomes "shabi" and 17 becomes 25
            person.changePersonSecondLevelPointer()
            fmt.Println("Second-order Pointers can change themselves:", person)
        }
        Copy the code

1.7 Mapping, that is, map

  1. Use make to generate a map. Map zero is nil. Nil has no key-value pairs and can’t add key-value pairs.
  2. Colons are also used between key and value pairs.
  3. The basic usage is the same as python. Note, however, that looking for nonexistent key-value pairs in the map returns a zero value, not a nonexistent error.
  4. The value of map can be read by double assignment as follows:
    var map = make(map[int]int)
    map[1] = 2
    
    var res1 := map[1] // map[1] is int, so assigning to res1 is int
    var res2, ok := map[1] // map[1] is still int
                          // But this special syntax assigns a value to ok, false if the value does not exist
                          // This is not to say that map[1] is a special type, no, it is just a syntax
    // If map[1] is assigned to another variable, then double assignment, an error is reported saying that one value cannot be assigned to two values
    var temp = map[1]
    var res, ok = temp // Error because temp and map[1] are just int
    Copy the code

1.8 the function

  1. The function of go is also a value, you can use it as a value, you can use it as an argument, assignment and so on.
  2. Thanks to this, the go function can be closed, exactly like a javascript closure.
// Fibonacci closure
package main

import "fmt"

func fibonacci(a) func(a) int {
    var first, second = 0.1
    fmt.Println(0)
    fmt.Println(1)
    return func(a) int {
        var sum = first + second
        first = second
        second = sum
        return sum
    }
}

func main(a) {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}
Copy the code

Method 2.

2.1 the use of

Go is not a face object language and has no classes. But you can define methods for a structure. Methods in GO are a special class of functions, again defined in terms of func. There are just more acceptor arguments than normal functions, between the func keyword and the method name.

type MyStruct struct{}func (m MyStruct) Method(< method parameters >) <return type> {
    // do sth
}
Copy the code

Here we define a Method Method for the MyStruct structure. A method is no different from a function except that it takes an extra acceptor argument. You can set more than one method with the same name, as long as the recipient type is different.

2.2 To whom can methods be defined?

You can define methods for all types in the same package. You cannot define methods for types in other packages.Copy the code

That’s why you can’t create methods for built-in types like int, because the built-in type is in Go’s package, not yours. If you use type to create a type of built-in type, it is possible to create methods, such as:

type MyFloat64 float64
func (mf MyFloat64) Minus(a) MyFloat64 {
    // do sth
}
Copy the code

2.3 Pointer receiver method

Recipient parameters can be passed in as Pointers. The advantage of the pointer receiver method is that you can modify the receiver’s properties directly within the method. Because of this property, the pointerreceiver method is more commonly used than the receiver method. Using the pointer method is the same as using the value method. There is no need to actively add a pointer to the structure. Go automatically recognizes and adds the pointer.

type MyStruct struct {
    X int,
    Y int
}
// Value receiver
func (m MyStruct) Method1(a) int {
    return m.X + m.Y
}
// Pointer receiver
// Change the struct property value here
// If there is no *, the properties of the structure will not change
func (m *MyStruct) Method2 {
    m.X = 100
    m.Y = 200
}
var ms = MyStruct(1.2)
fmt.Println(ms.Method1()) / / 3

ms.Method2()
fmt.Println(ms.Method1()) / / 300
Copy the code

2.4 The difference between a pointer acceptor and a function with a pointer as an argument:

Such as:

func (m *MyStruct) Method1 {
    // do sth
}
func Method2(m *MyStruct) {
    // do sth
}
To use Method2, you must pass in a pointer argument
var ms = MyStruct{1.2}
var pms = &ms
Method2(ms) / / an error
Method2(pms) / / normal

// If you want to use Method1, you can call it with or without a pointer
ms.Method1() / / normal
pms.Method1() / / normal
/* * Because go finds that Method1 is a pointer taker, it automatically converts ms to &ms *
Copy the code

2.5 The difference between a value acceptor and a function whose value is an argument

It’s the same feeling, the value taker method, whether it’s a value or a pointer, the pointer will be interpreted as the *ms value. The function has to correspond.

2.6 Use value acceptor or pointer acceptor

Must be the pointer receiver. Not only is there one more function that the recipient can change. For large structures, it also saves space for replication. (The pointer is referenced directly, and the value is copied out.)

3. The interface

Interfaces matter, and GO calls itself interface-oriented programming.

An interface is a collection of methods. Used to hold values that implement the methods that implement them (what this means in the next section). The interface usage process is as follows:

  1. Start by defining an interface with some methods in it.
  2. Then we define a type with type. (Remember? There is no method because the built-in type is not in the current package.
  3. We then implement all the interface methods for this type against the interface.
  4. At this point, the type implicitly implements the interface without having to declare that the type implements the interface. (Go doesn’t even have an explicit declaration keyword because it’s unnecessary.)
  5. Because there is no explicit declaration of the relationship between type and interface, interface and type are decoupled.

Interface can be regarded as a rule, for example, the interface of a washing machine, we stipulate that there are two methods of washing and drying, as long as any type realizes these two methods, we call it washing machine. It doesn’t matter what the type is.

3.1 Interface Usage

What do I mean by saying that interfaces are used to hold values that implement these methods? Isn’t interface a rule?

  • In fact, if you look at the syntax of an interface, you can use the same thing as a struct, which uses type to create a new type. This new type can be thought of as a rule.
  • In practice, however, we use this type to create a variable that stores a value that must implement the method specified by the interface rules, otherwise an error will be reported.
/ / interface
type MyInterface interface {
    M()
}
// There is no type that implements M methods, so it is not MyInterface
type MyStruct1 struct { A int }
// The M method is implemented, so it belongs to MyInterface
// As long as the M method is implemented, it is not necessary (nor possible) to say that MyStruct2 is MyInterface
type MyStruct2 struct { A int }
func (ms *MyStruct2) M(a) {
    ms.A = 99999
}

func main(a) {
    var i MyInterface
    fmt.Printf("%v, %T\n", i, i) // "<nil>, <nil>"
    i = &MyStruct1{1} / / an error
    
    i = &MyStruct2{1} // ok
    fmt.Printf("%v, %T\n", i, i) // "&{1}, *main.MyStruct2"
    i.M()
    fmt.Printf("%v, %T\n", i, i) // "&{99999}, *main.MyStruct2"
}
Copy the code

3.2 interface value

Interfaces themselves are also values that can be passed in as arguments to functions and as return values.

  • When the interface is first initialized, the value is nil, the type is nil, you can’t call a method.

The interface has a value and type only after it has been saved to the interface. The value and type are exactly the same as the saved value.

  • In addition, methods can still be called when the value passed to the interface is nil.

3.3 empty interface

An interface that does not declare any methods is called a null interface. Type MyEmptyInterface interface{} By definition, empty interfaces naturally belong to all types. Because all types implement zero methods. All types, that is, including built-in types. What an empty interface does: Since any type implements the interface, it can store any value. Make (map[string]interface{}) allows a map to store arbitrary values.

3.4 Type Assertion

Type assertions provide a way to access the underlying concrete value of an interface value.

3.4.1 A return value

v = i.(<type>) // 
      
        is a concrete type
      
Copy the code

This statement asserts that the underlying value type of interface value I is

.

  • If not, this statement triggers panic.
  • If so, of course it works. The return value v is the underlying value of I, of type<type>

3.4.2 Two return values

In addition, if you do not want to trigger panic, you can use the following statement:

v, ok := i.(<type>)
Copy the code

Pay attention to

  • In that case, even though I is not<type>Type, and does not trigger panic. But OK will be assigned false.

    But v is still going to be<type>Type with a value of zero.
  • If so, ok is true.

3.5 Type Selection

- If type assertion is used to determine whether an interface value belongs to a certain type. - Then the type selection is processed differently according to the type of the interface value.Copy the code

The syntax is similar to that of the switch, except that the case is of type: Note 2: Type selection is not multiple type assertions. It is used to treat different types differently, not to determine whether a type is a desired type

switch v := i.(type) {case A:
        // The type of I is A
    case B:
        // I is of type B
    default:
        // I is not of any of the above types
}
Copy the code

It’s actually monitoring the type of I, and different types are treated differently. When you’re done, give v a value.

3.6 Different Application modes of Interfaces

Although the above mentioned interface is very useful, but it is really not beautiful to say it alone. Where is the decoupling? Why can’t I see that?

3.6.1 track Stringer interface

Here’s an example:

  1. In the FMT package there is an interface called Stringer, which defines a method String, which is used to print itself out as a String.
  2. ↑ What does that mean?
  3. For example, Println in FMT, suppose you want to print a struct, but you don’t want FMT to print it{xx, xx}This form, want to make a better look, how to do?

The Stringer interface’s String method does this, you just define a method called String for your struct that returns the String you want to print out. There is no need to mention specifically how this is written for the Stringer interface of FMT. Fmt.println () automatically calls the String method.

/* * Does not define the String method */
type Person {
    name string,
    age int
}
func main(a) {
    fmt.Println(Person{"zouli"."17"}) {zouli, 17}"
                                       // It's not interesting
}

/* * Defines the String method */

 func (p *Person) String(a) string { // Make sure the name is String
     return fmt.Sprintf("I'm not % V, you are, your whole family is.", p.name)
 }
 func main(a) {
    fmt.Println(Person{"zouli"."17"}) // Print "I'm not Zouli, you are, your whole family is."
                                       / / is very interesting
}
Copy the code

See, there’s no Stringer at all, just String. The interface is defined by FMT, and we just need to implement the method, which is decoupling. Aside from FMT, there are many packages printed with this String, so you can remember the name.

Imagine that you could write a package that defines an interface, but instead of defining the methods, you could write some rules, such as returning a string. Then you call this method in some exported function in your package, even if it doesn’t exist yet. And then someone else using your package, calling your function directly will get an error that there is no method, but once someone else has defined their own method, and it conforms to the interface rules that you defined, they can call it. Of course, it is best to define a default method in the package. Otherwise it would be boring to use a package and have to define your own methods.

In this way, FMT doesn’t care what the structure of the value is and just uses a user-defined method.

3.6.2 error interface

Error is also a built-in interface, but not in the FMT package, but in GO. Implementing the error interface requires implementing the error method, which also returns a string.

  1. The interface value needs to be mentioned again, as I mentioned earlier that the interface can be used as a function return value. That’s what we use a lot here.
  2. A common way to make an error is to define a function that returns multiple values, the last of which is of type error. So how do I get this error type? Of course, you can define a type, and then implement an Error method for that type.
    • Of course, if you don’t want to define and implement it yourself, you can use the errors.new () method in the errors package

Check it out on the page.

Also, fmt.Println looks for the error method when printing the error interface value, and looks for the String method if it doesn’t.

This approach is to use the interface as a type, of course, because Error is a built-in interface, not in other packages (FMT is the standard library, so Stringer is not a built-in interface).

3.6.3 Read interface

Go has a number of IO.Reader interfaces, all of which have a Read method, as follows:

func (r Reader) Read(b []byte) (int, error) 
Copy the code

It reads as many bytes from r as it can up to EOF, and stores them all in B. If B doesn’t fit, it waits for the next call. Return an int, which indicates how many bytes were read this time. This has to be precise, otherwise the output will be messy. Error doesn’t need explaining.

What’s a little bit tricky here is that when you use this method, you’re changing the parameter B, not the r that you’re calling, and you’re getting out of object-oriented thinking. Before it came in, B might have been an empty slice of 1024 size, but r had only 3 bytes in it, and after it was used up, B was only 3.

You can wrap a Reader inside a Reader, call the external reader.read () method, use the internal reader.read () method to overwrite b, do something to b, and return. This allows you to modify the data flow. See here for details.

This approach is through interface nesting, to achieve a similar effect of inheritance. (Think about why it’s like inheritance.)

3.6.4 radar echoes captured Image interface

This section is mainly about looking at the documentation.