Циклы

Циклы нужны для того, чтобы в зависимости от некоторого условия или задачи выполнять некоторые действия множество раз.

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

for [инициализация счетчика]; [условие]; [изменение счетчика]{
    // действия
}

Давайте теперь рассмотрим пример программы с циклом for. Например, нам нужно проссумировать все числа от 1 до 10. Такую программу с легостью можно написать, используя цикл for:

package main
import "fmt"

func main() {
    sum := 0    // создаем переменную, в которой будет суммировать значения
    for i := 1; i < 10; i++ {
        sum += i
    }
    fmt.Println(sum)    // 45  (все проссумированные значения от 1 до 10)
}

Теперь давайте подробно разберем конструкцию цикла в нашей программе. Вначале происходит инциализация (присваивание) счетчика: i := 1 (начальное значение). По сути она представляет объявление переменной, которую мы будем использовать внутри цикла.

Условие i < 10 означает, что пока это условие выполняется, то мы будем инкремировать (3-я часть цикла - i++) наше значение i на 1 (i++ эквивалентно i += 1).

Соответственно, цикл прекращается тогда, когда значение i равно 11 (i == 11).

Вариации конструкции цикла for

На самом деле необязательно указывать все условия при объявлении цикла. Например, можно объявить перменную i раньше самой конструкции цикла:

package main
import "fmt"

func main() {
    var i int = 1

    for ; i < 5; i++ {
        fmt.Println(i * i)
    }
}

Можно оставить и вовсе только одно условие, тогда цикл for будет являться, по сути, аналогом цикла while из других языков программирование (самого слова while нет в Go):

package main
import "fmt"

func main() {
    var i int = 1

    for i < 5 {
        fmt.Println(i * i)
        i++
    }
}

Однако, в этом случае необходимо будет обозначить условие выхода цикла (условие завершения), т.к. в противном случае программа будет выполняться бесконечно. В данном случае, i++ является условием выхода, так как при каждой итерации (т.е. при каждом шаге выполнения цикла) мы увеличиваем i на 1.

Пример бесконечного цикла:

package main
import "fmt"

func main() {
    var i int = 1

    for i < 5 {
        fmt.Println(i * i)
    }
}

Еще бесконечный цикл можно указать таким образом:

for {

}

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

Вложенный цикл расположен в еще одном цикле:

package main
import "fmt"

func main() {

    for i := 1; i < 3; i++ {
        fmt.Println("i равно", i)
        for j := 1; j < 3; j++ {
            fmt.Println("J равно", j)
        }
    }
}

В результате мы получим следующий вывод на консоль:

i равно 1
J равно 1
J равно 2
i равно 2
J равно 1
J равно 2

Из результата мы видим что, для каждого значения i значение j выводилось ровно 2 раза!

Чтобы лучше понять работу вложенных циклов, вспомним по какому принципу работают наши часы. Мы знаем, что секундная, минутная и часовая стрелки вращаются вокруг циферблата. Часовая стрелка смещается всего на 1 шаг для каждых 60 шагов минутной стрелки. Минутная же стрелка вращается на 1 шаг для каждых 60 шагов секундных стрелок. Таким образом, это означает, что для каждого полного оборота часовой стрелки (12 шагов, т.е. часов), минутная стрелка сделает 720 шагов, а секундная 43200 шагов!

Clock logo

package main
import "fmt"

func main() {

    for hours := 0; hours < 12; hours++ {
        for minutes := 0; minutes < 60; minutes++ {
            for seconds := 0; seconds < 60; seconds++{
                fmt.Println(hours, ":", minutes, ":", seconds)
            }
        }
    }
}

Результатом работы такого кода будет:

0 : 0 : 0
0 : 0 : 1
0 : 0 : 2
...
11 : 59 : 57
11 : 59 : 58
11 : 59 : 59

Цикл for и массивы/срезы

Для перебора массивов или срезов можно использовать следующую форму цикла for:

for индекс, значение := range массив/срез {
    // действия
}

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

package main
import "fmt"

func main() {
    var numbs = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var even []int

    for i, v := range numbs {   // i, v сокр. от index, value
        if v % 2 == 0 {
            even = append(even, numbs[i])
        }
    }
    fmt.Println(even)   // [2 4 6 8 10]
}

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

package main
import "fmt"

func main() {
    var numbs = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var even []int

    for _, v := range numbs {
        if v % 2 == 0 {
            even = append(even, v)
        }
    }
    fmt.Println(even)   // [2 4 6 8 10]
}

Но также для перебора массива или среза можно использовать и стандартную версию цикла for:

package main
import "fmt"

func main() {
    var numbs = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var even []int

    for i := 0; i < len(numbs); i++ {
        if numbs[i] % 2 == 0 {
            even = append(even, numbs[i])
        }
    }
    fmt.Println(even)   // [2 4 6 8 10]
}

В этом примере счетчик i играет роль индекса массива. Цикл выполняется, пока счетчик i не станет равным длине массива. Как мы помним из предыдущего материала, значение длины массива возвращает функция len().

Операторы break и continue

Часто в цикле for используют такие операторы как break и continue. Начнем с оператора break.

break

Данный оператор полностью осуществляет выход. из цикла, несмотря даже на само условие в цикле. Например, нам неопходимо найти опредленное чилсо в массиве. Если это число найдено, то последующие итерации нас уже не интересует, а поэтому, можно (и даже нужно) использовать оператора break. Допустим, нам надо найти число 5 и вывести его:

package main
import "fmt"

func main() {
    var numbs = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    for i := 0; i < len(numbs); i++ {
        if numbs[i] == 5 {
            fmt.Println(numbs[i])   // 5
            break   // цикл останавливается
        }
    }
}

Да, во втором случае результат будет абсолютно таким же:

package main
import "fmt"

func main() {
    var numbs = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    for i := 0; i < len(numbs); i++ {
        if numbs[i] == 5 {
            fmt.Println(numbs[i])   // 5
        }
    }
}

Однако программа продолжит дальше итерироваться, не смотря на то, что мы уже нашли число 5. А это негативно сказывается на быстродействие программы.

continue

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

package main
import "fmt"

func main() {
    var numbs = [10]int{-1, 2, -3, 4, 5, 6, 7, 8, -9, -10}
    sum := 0

    for i := 0; i < len(numbs); i++ {
        if numbs[i] < 0 {
            continue
        }
        sum += numbs[i]
    }
    fmt.Println(sum)    // 32
}

Т.е. в данном случае, если выполняется условие numbs[i] < 0 (отрицательное значение), то срабатывает оператор continue которыей пропускает отрицательое значение.