Циклы for в Go
Development | Комментировать запись
В программировании циклом называется структура кода, которая повторяется циклически и многократно выполняет фрагмент кода – обычно до тех пор, пока не будет выполнено какое-либо условие. Использование циклов в программах позволяет автоматизировать повторение подобных задач нужное количество раз. Представьте, что у вас есть список файлов, которые нужно обработать, или вы хотите посчитать количество строк в статье – для решения подобных проблем в программах используются циклы.
В 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: Go, Golang