Как построить петли в го

Вступление

В компьютерном программированииloop - это структура кода, которая циклически повторяется для многократного выполнения фрагмента кода, часто до тех пор, пока не будет выполнено какое-либо условие. Использование циклов в компьютерном программировании позволяет автоматизировать и повторять подобные задачи несколько раз. Представьте, есть ли у вас список файлов, которые вам нужно обработать, или если вы хотите посчитать количество строк в статье. Вы бы использовали цикл в вашем коде для решения подобных проблем.

В Go циклfor реализует повторное выполнение кода на основе счетчика цикла или переменной цикла. В отличие от других языков программирования, которые имеют несколько конструкций циклов, таких какwhile,do и т. Д., Go имеет только циклfor. Это служит для того, чтобы сделать ваш код более понятным и читаемым, поскольку вам не нужно беспокоиться о нескольких стратегиях для достижения одной и той же конструкции цикла. Эта улучшенная читаемость и уменьшенная когнитивная нагрузка во время разработки также сделают ваш код менее подверженным ошибкам, чем в других языках.

В этом руководстве вы узнаете, как работает циклfor в Go, включая три основных варианта его использования. Мы начнем с того, что покажем, как создавать различные типы цикловfor, а затем покажем, как выполнять цикл черезsequential data types in Go. В конце мы объясним, как использовать вложенные циклы.

Объявление циклов ForClause и Condition

Чтобы учесть различные варианты использования, существует три различных способа создания цикловfor в Go, каждый со своими возможностями. Они предназначены для создания циклаfor сCondition, aForClause илиRangeClause. В этом разделе мы объясним, как объявлять и использовать варианты ForClause и Condition.

Давайте сначала посмотрим, как мы можем использовать циклfor с ForClause.

ForClause loop определяется как имеющийinitial statement, за которым следуетcondition, а затемpost statement. Они расположены в следующем синтаксисе:

for [ Initial Statement ] ; [ Condition ] ; [ Post Statement ] {
    [Action]
}

Чтобы объяснить, что делают предыдущие компоненты, давайте рассмотрим циклfor, который увеличивается через указанный диапазон значений с использованием синтаксиса ForClause:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

Давайте разберем этот цикл и определим каждую часть.

Первая часть цикла -i := 0. Это начальное утверждение:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

В нем говорится, что мы объявляем переменную с именемi и устанавливаем начальное значение на0.

Следующее условие:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

В этом состоянии мы заявили, что, хотяi меньше значения5, цикл должен продолжаться.

Наконец, у нас есть сообщение post:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

В операторе post мы увеличиваем переменную циклаi на единицу каждый раз, когда происходит итерация, с помощью оператораi++increment.

Когда мы запускаем эту программу, вывод выглядит так:

Output0
1
2
3
4

Цикл побежал 5 раз. Первоначально он установилi в0, а затем проверил, было лиi меньше, чем5. Поскольку значениеi было меньше, чем5, цикл был выполнен, и действиеfmt.Println(i) было выполнено. После завершения цикла был вызван оператор posti++, и значениеi увеличилось на 1.

[.note] #Note: Имейте в виду, что в программировании мы склонны начинать с индекса 0, поэтому, несмотря на то, что печатаются 5 чисел, они варьируются от 0 до 4.
#

Мы не ограничены, начиная с 0 или заканчивая указанным значением. Мы можем присвоить любое значение нашему начальному утверждению, а также остановиться на любом значении в нашем пост-утверждении. Это позволяет нам создать любой желаемый диапазон для циклического прохождения:

for i := 20; i < 25; i++ {
    fmt.Println(i)
}

Здесь итерация переходит от 20 (включительно) к 25 (исключая), поэтому вывод выглядит так:

Output20
21
22
23
24

Мы также можем использовать наш пост-оператор для увеличения при различных значениях. Это похоже наstep на других языках:

Во-первых, давайте использовать оператор post с положительным значением:

for i := 0; i < 15; i += 3 {
    fmt.Println(i)
}

В этом случае циклfor настроен так, что числа от 0 до 15 распечатываются, но с приращением 3, так что печатается только каждое третье число, например:

Output0
3
6
9
12

Мы также можем использовать отрицательное значение для нашего аргумента после оператора, чтобы выполнить итерацию в обратном направлении, но мы должны соответствующим образом скорректировать наш начальный оператор и аргументы условия:

for i := 100; i > 0; i -= 10 {
    fmt.Println(i)
}

Здесь мы устанавливаемi на начальное значение100, используем условиеi < 0 для остановки на0, а оператор post уменьшает значение на 10 с помощью-= оператор. Цикл начинается с100 и заканчивается на0, уменьшаясь на 10 с каждой итерацией. Мы можем видеть это в выходных данных:

Output100
90
80
70
60
50
40
30
20
10

Вы также можете исключить начальный оператор и оператор post из синтаксисаfor и использовать только условие. Это то, что известно какCondition loop:

i := 0
for i < 5 {
    fmt.Println(i)
    i++
}

На этот раз мы объявили переменнуюi отдельно от циклаfor в предыдущей строке кода. В цикле есть только условие, которое проверяет, меньше лиi, чем5. Пока условие оценивается какtrue, цикл будет продолжать повторяться.

Иногда вы можете не знать, сколько итераций вам потребуется для выполнения определенной задачи. В этом случае вы можете опустить все операторы и использовать ключевое словоbreak для выхода из выполнения:

for {
    if someCondition {
        break
    }
    // do action here
}

Примером этого может быть, если мы читаем из структуры неопределенного размера, такой какbuffer, и мы не знаем, когда закончим чтение:

buffer.go

package main

import (
    "bytes"
    "fmt"
    "io"
)

func main() {
    buf := bytes.NewBufferString("one\ntwo\nthree\nfour\n")

    for {
        line, err := buf.ReadString('\n')
        if err != nil {
            if err == io.EOF {

                fmt.Print(line)
                break
            }
            fmt.Println(err)
            break
        }
        fmt.Print(line)
    }
}

В предыдущем кодеbuf :=bytes.NewBufferString("one two three four ") объявляет буфер с некоторыми данными. Поскольку мы не знаем, когда буфер закончит чтение, мы создаем циклfor без предложения. Внутри циклаfor мы используемline, err := buf.ReadString(' ') для чтения строки из буфера и проверки, не произошла ли ошибка чтения из буфера. Если было, мы исправляем ошибку иuse the break keyword to exit the for loop. С этими точкамиbreak вам не нужно включать условие для остановки цикла.

В этом разделе мы узнали, как объявить цикл ForClause и использовать его для итерации по известному диапазону значений. Мы также узнали, как использовать цикл Условие для итерации, пока не будет выполнено определенное условие. Далее мы узнаем, как RangeClause используется для перебора последовательных типов данных.

Цикл по последовательным типам данных с RangeClause

В Go часто используются циклыfor для перебора элементов последовательных или сборных типов данных, таких какslices, arrays иstrings. Чтобы упростить это, мы можем использовать циклfor с синтаксисомRangeClause. Несмотря на то, что вы можете циклически проходить по типам данных с использованием синтаксиса ForClause, RangeClause более чист и удобен для чтения.

Прежде чем мы рассмотрим использование RangeClause, давайте посмотрим, как мы можем перебрать фрагмент, используя синтаксис ForClause:

main.go

package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for i := 0; i < len(sharks); i++ {
        fmt.Println(sharks[i])
    }
}

Выполнение этого даст следующий вывод, распечатывая каждый элемент среза:

Outputhammerhead
great white
dogfish
frilled
bullhead
requiem

Теперь давайте используем RangeClause для выполнения того же набора действий:

main.go

package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for i, shark := range sharks {
        fmt.Println(i, shark)
    }
}

В этом случае мы распечатываем каждый элемент в списке. Хотя мы использовали переменныеi иshark, мы могли бы назвать переменную любым другимvalid variable name, и мы получили бы тот же результат:

Output0 hammerhead
1 great white
2 dogfish
3 frilled
4 bullhead
5 requiem

При использованииrange на срезе он всегда возвращает два значения. Первое значение будет индексом, в котором находится текущая итерация цикла, а второе - значением этого индекса. В этом случае для первой итерации индекс был0, а значение былоhammerhead.

Иногда нам нужно только значение внутри элементов слайса, а не индекс. Если мы изменим предыдущий код, чтобы только распечатать значение, однако, мы получим ошибку времени компиляции:

main.go

package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for i, shark := range sharks {
        fmt.Println(shark)
    }
}
Outputsrc/range-error.go:8:6: i declared and not used

Посколькуi объявлен в циклеfor, но никогда не используется, компилятор ответит с ошибкойi declared and not used. Это та же ошибка, что вы получите в Go каждый раз, когда вы объявляете переменную и не используете ее.

Из-за этого Go имеет символblank identifier, который является знаком подчеркивания (_). В циклеfor вы можете использовать пустой идентификатор, чтобы игнорировать любое значение, возвращаемое ключевым словомrange. В этом случае мы хотим игнорировать индекс, который является первым возвращаемым аргументом.

main.go

package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for _, shark := range sharks {
        fmt.Println(shark)
    }
}
Outputhammerhead
great white
dogfish
frilled
bullhead
requiem

Эти выходные данные показывают, что циклfor повторял цикл строк и печатал каждый элемент из этого слоя без индекса.

Вы также можете использоватьrange для добавления элементов в список:

main.go

package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for range sharks {
        sharks = append(sharks, "shark")
    }

    fmt.Printf("%q\n", sharks)
}
Output['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark']

Здесь мы добавили строку-заполнитель"shark" для каждого элемента длины срезаsharks.

Обратите внимание, что нам не нужно было использовать пустой идентификатор_, чтобы игнорировать какое-либо из значений, возвращаемых операторомrange. Go позволяет нам опустить всю часть объявления в инструкцииrange, если нам не нужно использовать какое-либо из возвращаемых значений.

Мы также можем использовать операторrange для заполнения значений среза:

main.go

package main

import "fmt"

func main() {
    integers := make([]int, 10)
    fmt.Println(integers)

    for i := range integers {
        integers[i] = i
    }

    fmt.Println(integers)
}

В этом примере срезintegers инициализируется десятью пустыми значениями, но циклfor устанавливает все значения в списке следующим образом:

Output[0 0 0 0 0 0 0 0 0 0]
[0 1 2 3 4 5 6 7 8 9]

В первый раз, когда мы печатаем значение срезаintegers, мы видим все нули. Затем мы перебираем каждый индекс и устанавливаем значение для текущего индекса. Затем, когда мы печатаем значениеintegers во второй раз, показывая, что все они теперь имеют значение от0 до9.

Мы также можем использовать операторrange для перебора каждого символа в строке:

main.go

package main

import "fmt"

func main() {
    sammy := "Sammy"

    for _, letter := range sammy {
        fmt.Printf("%c\n", letter)
    }
}
OutputS
a
m
m
y

При итерации черезmap,range вернет какkey, так иvalue:

main.go

package main

import "fmt"

func main() {
    sammyShark := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

    for key, value := range sammyShark {
        fmt.Println(key + ": " + value)
    }
}
Outputcolor: blue
location: ocean
name: Sammy
animal: shark

[.note] #Note: Важно отметить, что порядок, в котором возвращается карта, является случайным. Каждый раз, когда вы запускаете эту программу, вы можете получать разные результаты.
#

Теперь, когда мы узнали, как перебирать последовательные данные с помощью цикловrangefor, давайте посмотрим, как использовать циклы внутри циклов.

Вложено для циклов

Циклы могут быть вложены в Go, как и в других языках программирования. Nesting - это когда одна конструкция находится внутри другой. В этом случае вложенный цикл - это цикл, который происходит внутри другого цикла. Это может быть полезно, когда вы хотите, чтобы зацикленное действие выполнялось для каждого элемента набора данных.

Вложенные циклы структурно похожи наnested if statements. Они построены так:

for {
    [Action]
    for {
        [Action]
    }
}

Программа сначала встречает внешний цикл, выполняя свою первую итерацию. Эта первая итерация запускает внутренний вложенный цикл, который затем выполняется до конца. Затем программа возвращается обратно к вершине внешнего цикла, завершая вторую итерацию и снова вызывая вложенный цикл. Опять же, вложенный цикл выполняется до завершения, и программа возвращается обратно к вершине внешнего цикла, пока последовательность не будет завершена, или оператор break или другой оператор не нарушит процесс.

Давайте реализуем вложенный циклfor, чтобы мы могли рассмотреть его поближе. В этом примере внешний цикл будет перебирать срез целых чисел с именемnumList, а внутренний цикл будет перебирать срез строк с именемalphaList.

main.go

package main

import "fmt"

func main() {
    numList := []int{1, 2, 3}
    alphaList := []string{"a", "b", "c"}

    for _, i := range numList {
        fmt.Println(i)
        for _, letter := range alphaList {
            fmt.Println(letter)
        }
    }
}

Когда мы запустим эту программу, мы получим следующий вывод:

Output1
a
b
c
2
a
b
c
3
a
b
c

Выходные данные показывают, что программа завершает первую итерацию внешнего цикла, печатая1, который затем запускает завершение внутреннего цикла, последовательно выводяa,b,c. . После завершения внутреннего цикла программа возвращается к началу внешнего цикла, печатает2, затем снова печатает весь внутренний цикл (a,b,c) s) и т. д.

Вложенные циклыfor могут быть полезны для перебора элементов внутри фрагментов, состоящих из фрагментов. В срезе, состоящем из срезов, если мы используем только один циклfor, программа выведет каждый внутренний список как элемент:

main.go

package main

import "fmt"

func main() {
    ints := [][]int{
        []int{0, 1, 2},
        []int{-1, -2, -3},
        []int{9, 8, 7},
    }

    for _, i := range ints {
        fmt.Println(i)
    }
}
Output[0 1 2]
[-1 -2 -3]
[9 8 7]

Чтобы получить доступ к каждому отдельному элементу внутренних срезов, мы реализуем вложенный циклfor:

main.go

package main

import "fmt"

func main() {
    ints := [][]int{
        []int{0, 1, 2},
        []int{-1, -2, -3},
        []int{9, 8, 7},
    }

    for _, i := range ints {
        for _, j := range i {
            fmt.Println(j)
        }
    }
}
Output0
1
2
-1
-2
-3
9
8
7

Когда мы используем здесь вложенный циклfor, мы можем перебирать отдельные элементы, содержащиеся в срезах.

Заключение

В этом руководстве мы узнали, как объявлять и использовать циклыfor для решения повторяющихся задач в Go. Мы также узнали о трех различных вариантах циклаfor и о том, когда их использовать. Чтобы узнать больше о циклахfor и о том, как управлять их потоком, прочтитеUsing Break and Continue Statements When Working with Loops in Go.