Циклы for в Go

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

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

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

Читайте также: Работа с массивами в Go

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

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

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

Цикл ForClause определяется с помощью начальное значение, за ним идет условие, а затем изменение счетчика. Они складываются в следующий синтаксис:

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.

В конце идет изменение счетчика:

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

Согласно ему, переменная i при каждом прохождении цикла увеличивается на единицу (для этого используется оператор i++).

Вывод этой программы выглядит так:

0
1
2
3
4

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

Примечание: В программировании, как правило, индексация начинается с 0, поэтому на экране 5 чисел – от 0 до 4.

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

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

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

20
21
22
23
24

Изменение счетчика также может быть любым – это называется шагом в других языках.

Для начала попробуйте использовать изменение счетчика с положительным значением:

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

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

0
3
6
9
12

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

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

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

100
90
80
70
60
50
40
30
20
10

Также можно исключить начальное значение и шаг из синтаксиса for и использовать только условие. Такой цикл называется условным:

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

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

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

for {
if someCondition {

break


}

// do action here
}

Например, этот цикл будет полезен при чтении структуры неопределенного размера, такой как буфер:

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\ntwo\nthree\nfour\n») объявляет буфер с некоторыми данными. Поскольку мы не знаем, когда буфер завершится, мы создаем цикл for только с условием, без начального значения и шага. Внутри цикла мы используем строку line, err := buf.ReadString(‘\n’), чтобы цикл мог прочитать строку из буфера и проверить, не было ли при этом ошибки. Если ошибка есть, мы исправляем ее и используем ключевое слово break для выхода из цикла for. Благодаря break не нужно включать условие для остановки цикла.

Читайте также: Операторы break и continue в циклах Go

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

Итерация последовательных типов данных с помощью RangeClause

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

Читайте также: Основы работы со строками в Go

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

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])


}

}

Этот цикл выдаст следующий вывод, отображая каждый элемент среза:

hammerhead
great white
dogfish
frilled
bullhead
requiem

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

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, но им можно было присвоить другие допустимые имена и получить такой же результат.

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

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

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

package main
import "fmt"
func main() {
sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}
for i, shark := range sharks {
fmt.Println(shark)
}
}
src/range-error.go:8:6: i declared and not used

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

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

package main
import "fmt"
func main() {
sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}
for _, shark := range sharks {
fmt.Println(shark)
}
}
hammerhead
great white
dogfish
frilled
bullhead
requiem

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

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

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)
}
['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark']

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

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

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

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 устанавливает все значения в списке:

[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 для перебора всех символов в строке:

package main
import "fmt"
func main() {
sammy := "Sammy"
for _, letter := range sammy {
fmt.Printf("%c\n", letter)
}
}
S
a
m
m
y

При выполнении итерации по карте range будет возвращать и ключ, и значение:

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)
}
}
color: blue
location: ocean
name: Sammy
animal: shark

Читайте также: Основы работы с картами в Go

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

Вложенные циклы for

В Go, как и в других языках программирования, бывают вложенные циклы – при этом одна конструкция находится внутри другой. Проще говоря, вложенный цикл – это цикл, который выполняется внутри другого цикла. Это может быть полезно в ситуациях, когда для каждого элемента набора данных должно выполняться зацикленное действие.

Вложенные циклы структурно аналогичны вложенным операторам if. Они построены по такому принципу:

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

Читайте также: Условные операторы в Go

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

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

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)
}
}
}

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

1
a
b
c
2
a
b
c
3
a
b
c

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

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

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)
}
}
[0 1 2]
[-1 -2 -3]
[9 8 7]

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

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)
}
}
}
0
1
2
-1
-2
-3
9
8
7

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

Заключение

В этом мануале вы узнали, как объявлять и использовать циклы for для решения повторяющихся задач в Go. Вы также изучили три различных типа циклов for и узнали, когда их использовать. Чтобы узнать больше о циклах for и о том, как управлять их потоком, читайте наш мануал Операторы break и continue в циклах Go.

Tags: ,