Tutorial

Как создавать циклы For в Go

Published on January 24, 2020
Русский
Как создавать циклы For в Go

Введение

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

В 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("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 без условия. Внутри цикла 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("%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)
	}
}
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.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more