Look at an example

In our usual code scenarios, we often need to change the value of an element in the slice. Let’s take a look at the common code implementation:

package main

import "fmt"

func test1(a) {
 slice1 := []int{1.2.3.4}  for _, val := range slice1 {  val++  }   fmt.Println(slice1) }  func test2(a) {  slice2 := []int{1.2.3.4}  for k, _ := range slice2 {  slice2[k]++  }   fmt.Println(slice2) }  func test3(a) {  slice3 := []int{1.2.3.4}  for i := 0; i < len(slice3); i++ {  slice3[i]++  }   fmt.Println(slice3) }  func main(a) {  test1()  test2()  test3() } Copy the code

Quite simply, the changes in test1() do not affect the original data, whereas the changes in test2() and test3() do change the original data. Let’s look at the printed result:

[1 2 3 4]
[2 3 4 5]
[2 3 4 5]
Copy the code

The final output is exactly what we expected.

The main reasons are:

  • Val is a copy of the elements in slice1, and changes to val do not change the elements in Slice1
  • In test2() and test3(), the slice is indexed directly, changing the underlying array

Why does this happen? So let’s look at the for-range principle

For-range principle

Implementation of for range

// Arrange to do a loop appropriate for the type. We will produce
// for INIT ; COND ; POST {
// ITER_INIT
// INDEX = INDEX_TEMP
// VALUE = VALUE_TEMP // If there is a value
// original statements // } Copy the code

The compilation method for slice is as follows:

// The loop we generate:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp // value = value_temp // original body // } Copy the code

Specific code details are available at https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5384 source code

Slice for range does the following for slice:

  • Make a copy of the Slice to traverse
  • Get length size
  • Iterate through using a regular for loop, returning a copy of the value and storing the value in the global variables index and value

That is, for k, val := range(slice) loop val is always the same global variable within the loop

With the above conclusion, let’s look at another question:

package main

import "fmt"

func test4(a) {
 s := []int{0.1.2.3}  m := make(map[int] *int)   for index, value := range s {  m[index] = &value  }   printMap(m) }  func printtMap(m map[int] *int) {  for key, value := range m {  fmt.Printf("map[%v]=%v\n", key, *value)  } }  func main(a) {  test4() } Copy the code

Printout:

map[2] =3
map[3] =3
map[0] =3
map[1] =3
Copy the code

For index, value := range s; for index, value := range s; for index := range s; The value stored at this address is 3, the last element on the slice

What if I changed the test4() method to the following form


func test4(a) {
    s := []int{0.1.2.3}
    m := make(map[int] *int)

 for index, value := range s {  valueCopy := value  m[index] = &valueCopy  }   printtMap(m) } Copy the code

ValueCopy is a new variable, valueCopy is a new variable, valueCopy is a new variable, valueCopy is a new variable, valueCopy is a new variable, valueCopy is a new variable

Printout:

map[0] =0
map[1] =1
map[2] =2
map[3] =3
Copy the code

The reason is that each time the loop declares a new variable, the corresponding address is also different.

Let’s look at a closure that works the same way

package main
import (
    "fmt"
    "time"
)
func main(a) {  str := []string{"I"."am".Uncle "Echo"}  for _, v := range str{  // Each goroutine's v has the same address as the external V's address  go func(a) {  // where v is the address referencing the external variable v  fmt.Println(v) } () }   time.Sleep(3 * time.Second) } Copy the code

In fact, the above code will print:

Echo uncleEcho uncleEcho uncleCopy the code

See note for reasons

To print different values, we can use the value transfer property of the function:

package main
import (
    "fmt"
    "time"
)
func main(a) {  str := []string{"I"."am".Uncle "Echo"}  for _, v := range str{  // copy the external value of v to the internal value of v  go func(v string) {  fmt.Println(v)  }(v)  }   time.Sleep(3 * time.Second) } Copy the code

Print out (print order may not be the same) :

I
am
Echo uncleCopy the code

For slice

From the principle of for range, we know that for I, v: = range x, the length of x will be taken a snapshot before entering the loop, deciding to loop len(x) so many times. The subsequent change in x does not change the number of cycles. Assign x[I] to V by I and, most recently, x.

package main

import (
    "fmt"
)
 func main(a) {  x := []int{1.3.5.7.9.11.13.15}  fmt.Println("start with ", x)   for i, v := range x {  fmt.Println("The current value is", v)  x = append(x[:i], x[i+1:]...). fmt.Println("And after it is removed, we get", x)  } } Copy the code

In the code above, as we traverse the slice, we delete the element from the slice each time we traverse

Printout:

The current value is 1
And after it is removed, we get [3 5 7 9 11 13 15]
The current value is 5
And after it is removed, we get [3 7 9 11 13 15]
The current value is 9
And after it is removed, we get [3 7 11 13 15] The current value is 13 And after it is removed, we get [3 7 11 15] The current value is 15 panic: runtime error: slice bounds out of range [5:4]  goroutine 1 [running]: main.main()  /data1/htdocs/go_project/src/github.com/cnyygj/go_practice/Interview/for_range.go:13 +0x398 exit status 2 Copy the code

From the output, we can see that the number of loops for range depends on the snapshot of the traversal target before the loop and will not change with the modification of the traversal target length. So you end up with a panic where slices overflow

homework

Finally, I’ll leave you with a problem

package main

import (
   "fmt"
)
 type Guest struct {  id int  name string  surname string  friends []int }  func (self Guest) removeFriend(id int) {  for i, other := range self.friends {  if other == id {  self.friends = append(self.friends[:i], self.friends[i+1:]...). break  }  } }  func main(a) {  test := Guest{0."Echo"."Uncle"And []int{1.2.3.4.5}}  fmt.Println(test)  test.removeFriend(4)  fmt.Println(test) } Copy the code

It will eventually print:

{0Uncle Echo [1 2 3 4 5]}
{0Uncle Echo [1 2 3 5 5]}
Copy the code

Do you know why? Comments are welcome

Follow the public account “Uncle said code” for more dry goods. See you next time