Введение
В компьютерном программировании циклом называется структура кода, представляющая собой петлю, которая повторяет выполнение куска кода, пока не будет выполнено определенное условие. Использование циклов в компьютерном программировании позволяет автоматизировать и повторять выполнение похожих задач многократно. Представьте, что у вас есть список файлов, которые вам нужно обрабатывать, или вы хотите подсчитать количество строк в статье. Вы можете использовать цикл в своем коде для решения подобных задач.
В Go цикл for
реализует повторяющееся исполнение кода на основании счетчика цикла или переменной цикла. В отличие от других языков программирования, где имеются разные виды циклов, например, while
, do
и т. д., в Go есть только цикл for
. Это делает ваш код чище и более читабельным, поскольку вам не нужно беспокоиться о нескольких стратегиях для получения цикла. Улучшенная читаемость и снижение когнитивной нагрузки во время разработки сделает ваш код менее подверженным ошибкам, чем в других языках.
В этом обучающем руководстве вы узнаете, как работают циклы for
в Go, включая три основных способа его использования. Начнем с демонстрации того, как можно создавать разные типы цикла for
, а затем узнаем, как создавать циклы с помощью последовательных типов данных в Go. А закончим на объяснении того, как использовать вложенные циклы.
Объявление цикла ForClause и циклов с условием
Чтобы учесть самые разные случаи использования, существует три разных способа создания циклов for
в Go, каждый из которых имеет свои возможности. Вы можете создать цикл for
с условием, ForClause и RangeClause. В этом разделе мы расскажем, как объявлять и использовать ForClause и цикл с условием.
Давайте посмотрим, как мы можем использовать цикл 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
на 1 каждый раз после каждой итерации с помощью оператора инкремента i++
.
При запуске этой программы вывод выглядит следующим образом:
Output
0
1
2
3
4
Цикл отработал 5 раз. Первоначально он задает для i
значение 0
, а затем проверяет, является ли значение i
меньше 5
. Так как значение i
меньше 5
, цикл выполняется и функция fmt.Println(i)
исполняется. После завершения цикла вызывается оператор i++
, а значение i
увеличивается на 1.
Примечание. Необходимо помнить, что в программировании мы обычно начинаем индексы с 0, поэтому хотя было выведено 5 чисел, они варьируются в диапазоне от 0 до 4.
Мы не обязательно должны начинать с 0 или заканчивать заданным значением. Мы можем присвоить любое значение для нашего инициирующего оператора и останавливать работу цикла на любом значении пост-оператора. Это позволяет нам создавать желаемый диапазон для работы цикла:
for i := 20; i < 25; i++ {
fmt.Println(i)
}
Здесь итерация выполняется с 20 (включая) до 25 (не включая), поэтому вывод выглядит следующим образом:
Output
20
21
22
23
24
Также мы можем использовать наш пост-оператор для инкрементирования на различных значениях. Это аналогично шагу
в других языках:
Сначала давайте воспользуемся пост-оператором с положительным значением:
for i := 0; i < 15; i += 3 {
fmt.Println(i)
}
В данном случае цикл for
задан таким образом, чтобы выводить числа с 0 до 15, но с увеличением на 3, так что на экран будет выводиться только каждое 3 число:
Output
0
3
6
9
12
Также мы можем использовать отрицательное значение для аргумента пост-оператора, чтобы выполнять итерацию в обратном направлении, но при этом нам нужно будет изменить инициирующий оператор и аргументы условий:
for i := 100; i > 0; i -= 10 {
fmt.Println(i)
}
Здесь мы зададим для i
начальное значение 100
, воспользуемся условием i < 0
для остановки при 0
, а в пост-операторе будем уменьшать значение на 10 с помощью оператора -=
. Цикл начинает работу при 100
и заканчивается при 0
, а значение уменьшается на 10 при каждой итерации. Вы можете увидеть, как это происходит, в выводе:
Output
100
90
80
70
60
50
40
30
20
10
Также вы можете исключить инициирующий оператор и пост-оператор из синтаксиса for
и использовать только условие. Это так называемый цикл с условием:
i := 0
for i < 5 {
fmt.Println(i)
i++
}
На этот раз мы объявили переменную i
отдельно от цикла for
в предшествующей строке кода. Цикл имеет только одно условие, которое проверяет, остается ли i
менее 5
. Если условие возвращает true
, цикл будет продолжаться.
Иногда вы можете не знать количество итераций, которое вам потребуется для выполнения определенной задачи. В этом случае вы можете пропустить все операторы и использовать ключевое слово break
для прекращения цикла:
for {
if someCondition {
break
}
// do action here
}
Подобный пример можно использовать, если мы будем выполнять чтение из структуры с неопределенным размером, например, из буффера, и мы не знаем, когда чтение будет завершено:
buffer.go
package main
import (
"bytes"
"fmt"
"io"
)
func main() {
buf := bytes.NewBufferString("onentwonthreenfourn")
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("onentwonthreenfourn")
объявляет буфер с некоторыми данными. Поскольку мы не знаем, когда буфер завершит чтение, мы создадим цикл for
без условия. Внутри цикла for
мы используем строку line, err := buf.ReadString('n')
для считывания строки из буфера и проверки на наличие ошибок при чтении из буфера. Если произошла ошибка, мы устраняем ее и используем ключевое слово break
для выхода из цикла. С этими точками break
вам не нужно добавлять условие, чтобы остановить цикл.
В этом разделе мы узнали, как объявить цикл ForClause и использовать его для прохождения по известному диапазону значений. Также мы научились использовать цикл с условием для повторения цикла до того момента, пока не будет выполняться определенное условие. Теперь мы научимся использовать RangeClause для итерации последовательных типов данных.
Прохождение циклом по последовательным типам данных с помощью RangeClause
В Go часто используются циклы for
для прохождения по элементам последовательных типов данных или коллекций, например, срезов, массивов и строк. Чтобы облегчить этот процесс, мы можем использовать цикл 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])
}
}
В результате вы получите следующий вывод, содержащий каждый элемент среза:
Output
hammerhead
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
, мы могли вызвать любую переменную с действительным именем переменной и получить тот же результат:
Output
0 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)
}
}
Output
src/range-error.go:8:6: i declared and not used
Поскольку i
объявляется внутри цикла for
, но не используется ни разу, компилятор вернет ошибку i declared and not used
. Такая же ошибка может быть получена в Go при любом объявлении переменной, которая не будет использована.
По этой причине Go имеет пустой идентификатор, являющийся символом нижнего подчеркивания (_
). В цикле 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)
}
}
Output
hammerhead
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("%qn", 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("%cn", letter)
}
}
Output
S
a
m
m
y
При прохождении по map оператор range
будет возвращать ключ и значение:
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)
}
}
Output
color: blue
location: ocean
name: Sammy
animal: shark
Примечание. Важно отметить, что порядок, в котором map возвращает значения, является случайным. Каждый раз, когда вы запускаете эту программу, вы можете получить разный результат.
Теперь, когда мы научились проходиться по последовательным данным с помощью range
для циклов for
, давайте посмотрим, как можно использовать циклы внутри циклов.
Вложенные циклы
В Go циклы можно вкладывать друг в друга, как и в других языках программирования. Вложенность — это размещение одного конструкта внутри другого. В данном случае вложенный цикл — это цикл, который работает внутри другого цикла. Это может быть полезно, когда вы хотите выполнять цикличное действие для каждого элемента набора данных.
Вложенные циклы структурно похожи на вложенные операторы if
. Они построены следующим образом:
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)
}
}
}
При запуске этой программы мы получим следующий вывод:
Output
1
a
b
c
2
a
b
c
3
a
b
c
Вывод показывает, что программа завершает первую итерацию внешнего цикла, выводя 1
, после чего запускается выполнение внутреннего цикла с выводом a
, b
и c
соответственно. Когда внутренний цикл завершен, программа возвращается вверх внешнего цикла, выводит 2
, а затем снова в полном объеме выводит внутренний цикл (a
, b
, c
) и т. д.
Вложенные циклы 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)
}
}
}
Output
0
1
2
-1
-2
-3
9
8
7
Когда мы используем вложенный цикл for
, мы можем пройтись по отдельным позициям внутри этих срезов.
Заключение
В этом обучающем руководстве мы узнали, как объявлять и использовать циклы for
для решения повторяющихся задач в Go. Также мы узнали три разных способа использования цикла for
и то, когда их можно использовать. Чтобы узнать больше о циклах for
и о том, как контролировать их работу, прочитайте статью Использование операторов break и continue при работе с циклами в Go.