A Brief analysis of Range keywords in Go language

  • 2020-06-03 06:45:11
  • OfStack

preface

As anyone who has used Range knows, the range keyword in Go is very handy. It allows you to iterate over an slice or map with two arguments ( index and value ), and get the location of an element in slice or map, respectively index And its value.

For example, use it like this:


for index, value := range mySlice {
 fmt.Println("index: " + index)
 fmt.Println("value: " + value)
}

The above example is clear enough to describe the use of range, but in fact there are a few caveats when using the range keyword.

To illustrate these potholes, let's start with a slightly more complicated example:


type Foo struct {
 bar string
}
func main() {
 list := []Foo{
 {"A"},
 {"B"},
 {"C"},
 }
 list2 := make([]*Foo, len(list))
 for i, value := range list {
 list2[i] = &value
 }
 fmt.Println(list[0], list[1], list[2])
 fmt.Println(list2[0], list2[1], list2[2])
}

In this example, we did the following:

1. It defines a structure called Foo, in which there is an field called bar. We then created an slice based on the Foo structure called list

2. We also created an slice based on the Foo struct pointer type, called list2

3. In 1 for In the loop, we try to iterate over each element in list, get its pointer address, and assign a value to the corresponding location in list2 for index.

4. Finally, output each element in list and list2 respectively

From the point of view of the code, of course, we would expect to get something like this:


{A} {B} {C}
&{A} &{B} &{C}

But the unexpected result is that the output of the program looks like this:


{A} {B} {C}
&{C} &{C} &{C}

From the results, it looks as if the three elements in list2 all point to the last element in list. Why is that? The problem lies in the first paragraph above for…range In a loop.

In Go for…range In a loop, Go always USES a copy of the value in place of the element being traversed, which is, in short, the element itself for…range In the value , is a copy of the value, not the element itself. So the 1 comes when we expect to use theta & When you get the pointer address of an element, you're really just getting it value The pointer address of the temporary variable, not list The address of a pointer to an element that is actually traversed. In the whole for…range Loop, value This temporary variable is reused, so in the above example, list2 is populated with three identical pointer addresses, all pointing to each other value And in the last cycle, value Assigned to {c} Pointer address of. Therefore, the output of list2 shows three &{c} .

Once again, the following is the same thing for…range Examples are as follows:


var value Foo
for var i := 0; i < len(list); i++ {
 value = list[i]
 list2[i] = &value
}

If we output the three elements of list2, the result is the same: &{C} &{C} &{C}

So, what's the right way to write it? We should use index To access the for…range , and gets its pointer address:


for i, _ := range list {
 list2[i] = &list[i]
}

This way, we can output the elements in list2 and get the result we want index0 .

The experimental code is as follows:


package main

import "fmt"

type Foo struct {
 bar string
}

func main() {
 list := []Foo{
 {"A"},
 {"B"},
 {"C"},
 }

 list2 := make([]*Foo, len(list))

 // Wrong example 
 for i, value := range list {
 list2[i] = &value
 }

 // The right example 
 //for i, _ := range list {
 // list2[i] = &list[i]
 //}

 fmt.Println(list[0], list[1], list[2])
 fmt.Println(list2[0], list2[1], list2[2])
}

Knowing the correct posture of range, we can also solve the following example:


package main
import "fmt"
type MyType struct {
 field string
}
func main() {
 var array [10]MyType
 for _, e := range array {
 e.field = "foo"
 }
 for _, e := range array {
 fmt.Println(e.field)
 fmt.Println("--")
 }
}

The most common scenario for writing code is that we need to change the value of the traversal element in a loop. For example, the example above, we hope to use for…range Loop, one time setting field for each element in array to "foo". Again, because of the copy of the range value, the above program will output nothing...

The right thing to do is:


for i, _ := range array {
 array[i].field = "foo"
}

Each element is accessed through index and modified field so that a heap of "foo" can be output...

The experimental code is as follows:


package main

import "fmt"

type MyType struct {
 field string
}

func main() {
 var array [10]MyType

 for i, _ := range array {
 array[i].field = "foo"
 }

 for _, e := range array {
 fmt.Println(e.field)
 }
}

conclusion

Above is all about Go language Range keyword content, this article is introduced or very detailed, I believe this article will have a certain reference value for you to learn Go language, if there is any question you can leave a message to exchange, this site will give you a reply as soon as possible, please continue to support this site.


Related articles: