#12 golang Цикл for (единственный на все случаи ;) range, break и особенности использования

Другие циклы нам не нужны ;)

В Go цикл представлен только одной конструкцией for, но она может принимать совершенно разные формы.

Пустой for

Есть конструкция, когда у вас нет условия — бесконечный цикл. Это аналог while(true) либо for (;;;):

// цикл без условия, while(true) OR for(;;;)
for {
	fmt.Println("Без break не закончится")
	break
}

-- в этом случае этот цикл нужно прервать самостоятельно. Для этого используется конструкция break.

Одна логическая проверка

Следующий вид — цикл с одиночным условием. То есть цикл будет выполняться до тех пор, пока это условие истинно:

// цикл c одиночным условием, while(isRun)
// цикл c одиночным условием, while(isRun)
isRun := 2 > 1
for isRun {
	fmt.Println("Не закочим, если не поменяем значение")
	isRun = false // или break
}

- понятно, что и тут break поможет выйти тоже.

Классический for из Си

Третьим вариантом цикла является классический for из Си с тремя блоками, когда вы указываете:

  1. блок инициализации,
  2. блок с условием
  3. и блок, который выполняется после итерации цикла.

Например:

// цикл с условием и блоком инициализации
for i := 0; i < 3; i++ {
	fmt.Println("Итерация номер", i)
}

continue - переход к следующему витку цикла

Чтобы не выпонять тело цикла до конца в каком-то из витков, можно использовать continue:

// цикл с условием и блоком инициализации
for i := 0; i < 3; i++ {
	fmt.Println("Итерация номер", i)
	if i == 1 {
		fmt.Println("Пропускаем", i)
		continue
	}
	fmt.Println("Без continue", i)
}

Итерации по перечислимым типам

Циклы, конечно же, можно использовать для перебора разных "списков" (в общем смысле этого слова), например по схеме проверки одного условия (аналоги while в других языках) перебрать слайс:

// ручная по slice
sl := []int{1, 2, 3}
idx := 0

for idx < len(sl) {
	fmt.Println("Индекс:", idx, "Значение:", sl[idx])
	idx++ // Накручиваем счетчик
}

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

// ручная по slice
sl := []int{1, 2, 3}
for i := 0; i < len(sl); i++ {
	fmt.Println("Индекс/значение", i, sl[i])
}

-- тут мы работаем счетчиками в заголовке цикла, но всё равно это ручная работа, в других языках бывает что-то более автоматическое (как foreach в PHP), в go для этого есть range.

range для перебора списочных типов

Мы можем получать как только индексы и потом уже работать с ними:

sl := []int{1, 2, 3}
// перебор индексов
for index := range sl {
	fmt.Println("Индекс: ", index, " Значение: ", sl[index])
}

так и сразу получать и индекс и значение для каждого элемента списочного типа (чтобы не обращаться по индексу явно):

sl := []int{1, 2, 3}
// перебор индексов и значений
for index, value := range sl {
	fmt.Println("Индекс: ", index,
		" Значение: ", value)
}

Интерация по мапам: Неопределенность порядка элементов в хэш-таблицах

ВАЖНО: в отображениях не определен порядок ключей (а значит и порядок, следования соответствующих им элементом).

Это значит, что в разных запусках программы ключи в памяти во внутренней структуре map могут располагаться совершенно по-разному из-за особенностей реализации.
Не ожидайте, что ключи будут в map ровно в том порядке, в котором вы их добавляли.

Ну а чисто позитивным моментом является то, что ассоциативные массивы также прекрасно перебираются с помощью range:

userData := map[string]string{
	"name":     "Vasily",
	"lastName": "Romanov",
}
for key, val := range userData {
	fmt.Println("Ключ Значение", key, val)
}
// или можно только по значению:
for _, val := range userData {
	fmt.Println("Значение", val)
}

Итерация по строкам

Под капотом строка является слайс-байтом, но range для строки определен отдельно.

Если итерироваться по строке по значению типа string, то мы будем перебирать не байты, а полноценные отдельные символы.

В том, что возвращает range для строки, будет: позиция (номер), на которой находится символ
и непосредственно символ (руна), который представляет собой utf-ный символ:

str := "Привет, Мир!"
for pos, char := range str {
	fmt.Printf("Символ %#U на позиции %d\n", char, pos)
}

Выход из цикла внутри switch

Ранее мы обсуждали работу оператора switch case.
Теперь посмотрим, как мы можем выйти из цикла в одном из case вариантов, для этого нам потребуется метка:

	userData := map[string]string{
		"name":     "Vasily",
		"lastName": "Romanov",
	}
Metka: // помечаем блок
	// из которого может захотим выйти
	for key, val := range userData {
		println("Проверяем: ", key, val)
		switch {
		case key == "lastName":
			println("Выходим тут")
			break
		case key == "firstName" && val == "Vasily":
			println("Выходим там")
			break Metka // целимся в метку

		}
	} // конец for

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

Видео-материалы

  • golang Цикл for и особенности его использования - часть 1: ВкВидео | Ютуб | Телеграм
  • golang Цикл for Итерация по перечислимым типам - часть 2: ВкВидео | Ютуб | Телеграм

Key Words for FKN + antitotal forum (CS VSU):