SoFunction
Updated on 2025-03-01

In-depth exploration of for range statements in Go language

1. Introduction

In Go language, we often need to traverse the data set. For arrays, it is easy to complete traversal using for statements. However, when we face other data types such as map, string, and channel, using a normal for loop cannot directly complete the traversal. In order to traverse these data types more conveniently, Go language introduces the for...range statement. This article will start with array traversal and gradually introduce the application of for...range statements in different data types.

2. Problem introduction

Suppose we have an array of integers and we want to iterate over each element in the array and process it. In this case, we can use the for statement to combine the length of the array to implement traversal, for example:

package main
import "fmt"
func main() {
    numbers := [5]int{1, 2, 3, 4, 5}
    for i := 0; i < len(numbers); i++ {
        (numbers[i])
    }
}

In the above code, we define an array of integersnumbers, iterates through the array through a normal for loop and prints each element. However, when we encounter other data types,mapstringorchannelWhen, use it at this timeforA statement will not be able to simply traverse it. So what is the way to complete it easilymapstringWhat about traversals of other types?

In fact,goExist in the languagefor....rangeStatements can realize traversal of these types. Let's introduce them carefully below.for...range

3. Basic introduction

In Go,for...rangeStatements provide a convenient way to traverse data structures such as arrays, slices, maps, and channels. It hides the details of the underlying index or iterator, and is an elegant and concise syntax sugar provided by Go for traversing various data structures, making traversal operations more convenient and intuitive. Below is a detailed introduction to usefor...rangeComplete the traversal operation of slices, maps, and channels.

3.1 Traversing slices

When usingfor...rangeWhen a statement traverses a slice, it will iterate over the elements in the slice one by one and assign the index and corresponding values ​​to the specified variable. The sample code is as follows:

numbers := [5]int{1, 2, 3, 4, 5}
for index, value := range numbers {
    // Handle index and value here}

innumbersIt is the slice we want to traverse.indexis a variable that is assigned to the index of the current element in each iteration (starting from 0).valueis a variable that is assigned to the value of the current element in each iteration.

If you only focus on the values ​​in the slice without the need for an index, you can use an underscore_Alternate to index variable name to ignore it:

numbers := []int{1, 2, 3, 4, 5}
for _, value := range numbers {
    ("Value:", value)
}

In this way, the loop body will only print out the values ​​in the slice without displaying the index.

passfor...rangeBy traversing the slice, we can access each element in the slice concisely and intuitively without manually managing the index, making the code more concise and easy to read.

3.2 traversing map

When usingfor...rangeStatement traversalmapWhen it iterates over each key-value pair in the map and assigns the key and corresponding values ​​to the specified variable. The sample code is as follows:

students := map[string]int{
    "Alice":   25,
    "Bob":     27,
    "Charlie": 23,
}
for key, value := range students {
    // Handle key and value here}

herefor...rangeIt will traverse all key-value pairs, and we can complete the traversal operation of the map without manually processing the iterator logic.

3.3 Traversing string

When usingfor...rangeWhen a statement traverses a string, it iterates over the characters in the string one by one, and assigns the index and value of each character to the specified variable. Here is an example code for traversing strings:

text := "Hello, the world!"
for index, character := range text {
    ("Index: %d, Character: %c\n", index, character)
}

The output result is:

Index: 0, Character: H
Index: 1, Character: e
Index: 2, Character: l
Index: 3, Character: l
Index: 4, Character: o
Index: 5, Character: ,
Index: 6, Character:  
Index: 7, Character: World
Index: 10, Character:

It should be noted that strings in Go are stored in UTF-8 encoding, which is a variable-length encoding, and different Unicode characters may occupy different numbers of bytes. andindexThe value of the character indicates the byte index position of each character in the string, so the index position of the character is not necessarily continuous.

Passed herefor...rangeWhen statements traverse strings, we can easily handle each character without manually managing index and character encoding issues, making the logic of handling strings more concise and easy to read.

3.4 Traversing the channel

When using the for...range statement traversalchannelWhen it it iterates over each value in the channel until the channel is closed. Here is a sample code:

ch := make(chan int)
// Example of writing data to the channelgo func() {
    ch &lt;- 1
    ch &lt;- 2
    ch &lt;- 3
    close(ch)
}()
// will output 1 2 3for value := range ch {
    ("Value:", value)
}

In the example, we write 3 integer values ​​to the channel. Then, usefor...rangeThe statement traverses the channel, obtains each value from it and processes it.

It should be noted that if no data is available in the channel,for...rangeStatements block until data is available or the channel is closed. Therefore, when there is no data in the channel, it will wait for the data to arrive.

passfor...rangeThe statement traversal can be very convenient to continuouslychanneland process the data.

4. Things to note

for...rangeThe statement can be considered asgoA syntactic sugar of the language simplifies our traversal operations on different data structures, but usesfor...rangeThere are still some precautions for statements. A full understanding of these precautions can enable us to better use this feature. We will describe it below.

4.1 Iterative variables will be reused

When usingfor...rangeWhen looping, iterative variables will be reused. This means that in each loop iteration the iteration variable will be reused instead of creating a new iteration variable in each iteration.

Here is a simple example code that demonstrates the reused iterative variables:

package main
import "fmt"
func main() {
        numbers := []int{1, 2, 3, 4, 5}
        for _, value := range numbers {
           go func() {
              ((value) + " ")
           }()
        }
}

In the above code, we usefor...rangeLoop through slicesnumbers, and create an anonymous function in each loop iteration and start a goroutine. This anonymous function prints the current iteration ofvaluevariable. Here is a possible result:

4 5 5 5 5

The reason for this result is that because iteration variables are reused, all goroutines will share the samevaluevariable. When goroutines start executing, they may read the result of the last iteration instead of the expected iteration order. This results in the output that may be duplicate numbers or not output in the expected order.

If it is not clear about the characteristics of the iterative variables being reused, this may lead to the occurrence of unexpected results in some scenarios. Therefore, iffor...rangeWhen there are concurrent operations, delay functions and other operations in the loop, it also depends on the value of iterated variables. At this time, it is necessary to ensure that a new copy is created in the loop iteration to avoid unexpected results.

4.2 Copy data of range expressions participating in iteration

forfor...rangeLoop is to iterate using copy data of range expressions. This means that the modification of the original data during the iteration process will not affect the results of the iteration. A simple code example is as follows:

package main
import "fmt"
func main() {
        numbers := [5]int{1, 2, 3, 4, 5}
        for i, v := range numbers {
           if i == 0 {
              numbers[1] = 100 // Modify the value of the original data              numbers[2] = 200
           }
           ("Index:", i, "Value:", v)
        }
}

In the above code, we usefor...rangeLoop through the arraynumbers, and then the value of the elements in the array is modified in the loop body. The traversal results are as follows:

Index: 0 Value: 1
Index: 1 Value: 2
Index: 2 Value: 3
Index: 3 Value: 4
Index: 4 Value: 5

It can be seen that although during the iteration process,numbersIt performs traversal, but does not affect the results of traversal. From this we can also prove that those involved in iteration arerangeReplica data of expression, not replica data.

If the operation in the loop needs to rely on the intermediate modified data results, it is best to divide it into two traversals, first traversal, modify the data in it, and then traversal the modified data. The above code is improved as follows:

numbers := [5]int{1, 2, 3, 4, 5}
// 1. The first traversal to modify the datafor i, _ := range numbers {
   if i == 0 {
      numbers[1] = 100 // Modify the value of the original data      numbers[2] = 200
   }
}
// 2. The second traversal output datafor i, v := range numbers {
   ("Index:", i, "Value:", v)
}

The result of this traversal is the modified data, as follows:

Index: 0 Value: 1
Index: 1 Value: 100
Index: 2 Value: 200
Index: 3 Value: 4
Index: 4 Value: 5

4.3 The map traversal order is uncertain

For map types in Go, the order of traversing their key-value pairs is uncertain. Here is an example of a simple code:

package main
import "fmt"
func main() {
        data := map[string]int{
                "apple":  1,
                "banana": 2,
                "cherry": 3,
        }
        for key, value := range data {
                (key, value)
        }
}

Run the above code and the result of each output may be different, that is, the order of key-value pairs is uncertain. It is possible that the result of the first run is:

banana 2
cherry 3
apple 1

Then the result of the second run is different from the result of the first run, which may be:

apple 1
banana 2
cherry 3

From this example, we can prove that the traversal order of traversal on maps is not fixed, so we need to note that we cannot rely on the traversal order of maps.

If the data in the map needs to be output in a certain order each time, you can save the keys to the slice first, sort the slices in the specified order, then iterate over the sorted slices, and use the keys in the slice to access the value in the map. At this time, the data in the map can be output in the specified order. The following is a simple code example:

package main
import (
        "fmt"
        "sort"
)
func main() {
        data := map[string]int{
                "apple":  1,
                "banana": 2,
                "cherry": 3,
        }
        // Create a slice of save key        keys := make([]string, 0, len(data))
        for key := range data {
                keys = append(keys, key)
        }
        // Sort the slices        (keys)
        // traverse map by sorted key        for _, key := range keys {
                value := data[key]
                (key, value)
        }
}

5. Summary

This article discusses Go languagefor...rangeAfter a basic introduction, we first start from a simple traversal problem and discover the basic onesforThe statement seems to be impossible to implement simplystringmaptraversal operations of other types lead tofor...rangeStatement.

Then we will introduce how to use itfor...rangerightstring,map,channeltraversal operations of other types. Then we'll introduce the usefor...rangeThree notes, such as the copy data participating in the iteration of the range expression. By understanding these precautions, we can use them betterfor...rangeStatements to avoid unexpected situations.

The above is an in-depth exploration of the detailed content of for range statements in Go. For more information about Go for range, please follow my other related articles!