Go in a nutshell
  • Go in a nutshell
  • Введение
  • Родословная или Should I Go?
  • Hello, world!
  • Базовые типы данных
  • Go - установка, сборка
  • Алфавит и пакеты
  • Синтаксис
  • Приведение типов
  • Операторы
  • Константы
  • Управление потоком
  • Массивы, срезы, отображения
  • Именованные типы и структуры
  • Указатели и оператор new
  • Функции
  • Работа с параметрами командной строки
  • Завершение работы программы
  • Методы
Powered by GitBook
On this page
  • Оператор for
  • Оператор goto
  • If...else - неразлучная пара
  • Оператор switch

Was this helpful?

Управление потоком

Мы уверенно движемся вперёд. Можно только позавидовать - впереди у нас много нового, неизведанного и удивительного. Любознательность, в определённой степени бескорыстная - в ней сущность нашего продвижения по ступенькам науки. Приступим!

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

Начинать будем с отличий. Первое и основное, что нужно понимать по данной теме в языке Go - это отсутствие начисто таких конструкций, как while, do while, foreach. Весь ассортимент сведен к одному единственному оператору - for. Во-вторых, следует запомнить, что, в отличие от большинства С-подобных языков, в языке Go параметры цикла for и условия операторов ветвления никогда не берутся в круглые скобки! И, наконец, нужно не забывать семантическое значение переноса строки, фактически, замещающее собою применение точки с запятой (semicolon). Именно поэтому, возможна только нижеприведенная форма записи:

оператор условие {
}

И будут всегда ошибочны любые другие формы:

//Такая:
оператор условие
{
}
//И вот такая:
оператор условие {}

Оператор for

Go - язык очень лаконичный и аккуратный. Поэтому отсутствие избыточности и разнообразия вполне в рамках его парадигмы. Отсутствие оператора while со товарищи с лихвой окупается полиморфностью оператора for.

Прежде всего, оператор for может принимать классическую форму:

package main

import "fmt"

func main() {

	//круглые скобки не требуются!
	for i := 0; i <= 10; i++ { //фигурная скобка в той же строке!
		fmt.Println(i)
	}

}
//Результат: вывод чисел от 0 до 10

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

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

package main

import "fmt"

func main() {

	var i int = 0
	for ; i <= 10; i++ {
		fmt.Println(i)
	}
	fmt.Println(i) // 11

}
//Результат: вывод чисел от 0 до 10
//Переменная-счётчик i продолжает свой жизненный цикл, со значением 11

А вот так for может мимикрировать под цикл while:

package main

import "fmt"

func main() {

	var i int = 0
	for i <= 10 { //no semicolons
		fmt.Println(i)
		i++
	}
	
}
//Результат: вывод чисел от 0 до 10

Обратите внимание, точки с запятой в данной ипостаси цикла for можно опустить, но их наличие не будет ошибкой.

И, наконец, бесконечный цикл:

package main

import "fmt"

func main() {

	var i int = 0
	for { //no semicolons
		fmt.Println(i)
		i++
		if i > 10 {
			break
		}
	}

}
//Результат: вывод чисел от 0 до 10

В данной конструкции точки с запятой также можно опустить, либо же использовать по желанию, чего я делать не рекомендую. Меньше знаков - чище код. Обратите внимание, нам пришлось использовать оператор break для выхода из бесконечного цикла. В случае использования оператора for в форме бесконечного цикла, это - единственная возможность из такого цикла выйти. При использовании подпрограмм Go (горутин), то есть многопоточности, зачастую используют бесконечные циклы, работа которых заканчивается только тогда, когда программа завершает свою работу. Рядом с оператором break стоит оператор continue, который прерывает лишь текущую итерацию цикла. При этом оператор for переходит к следующей итерации, не прерывая своей работы.

Выведем только чётные числа:

package main

import "fmt"

func main() {

	for i := 0; i <= 10; i++ {
		if i%2 != 0 {//если число нечётное
			continue
		}

		fmt.Println(i)
	}

}
//Результат: вывод только чётных чисел от 0 до 10
package main

import "fmt"

func main() {

	var result int = 0
handle:
	for i := 0; i < 10; i++ {

		result = result + i
		fmt.Println(result)
		if result%2 == 1 {
			continue handle
		}

	}
}
//Результат:
/*
0
1
3
6
10
15
21
28
36
45
*/

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

Выведем, к примеру, таблицу Пифагора:

package main

import "fmt"

func main() {

	for i := 1; i < 10; i++ {
		for j := 1; j < 10; j++ {
			fmt.Print(i*j, "\t")
		}
		fmt.Println()
	}
}
//Результат:
/*
1	2	3	4	5	6	7	8	9	
2	4	6	8	10	12	14	16	18	
3	6	9	12	15	18	21	24	27	
4	8	12	16	20	24	28	32	36	
5	10	15	20	25	30	35	40	45	
6	12	18	24	30	36	42	48	54	
7	14	21	28	35	42	49	56	63	
8	16	24	32	40	48	56	64	72	
9	18	27	36	45	54	63	72	81
*/

Обратите внимание - \t - как и во множестве C-подобных языков - символ табуляции. Подобные символы, а также символы форматирования, так называемые "verbs" мы рассмотрим позднее в разделе, посвящённом работе с текстом.

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

for key, value := range enumarableVar{
		fmt.Println(key, value)

Здесь range - ключевое слово. Имена же переменных key и value даны для наглядности и могут быть заменены любыми. Далее, для понимания происходящего, мы используем массив. Знаю, что не проходили. Да, обещаю вскоре исправиться.

package main

import "fmt"

func main() {

	var arr [4]string = [4]string{"a", "b", "c", "d"}
	
	fmt.Println("key\t value" )

	for key, value := range arr{

		fmt.Println(key,  "\t",  value)
	}
}
//Результат:
/*
key	 value
0 	 a
1 	 b
2 	 c
3 	 d
*/

Key можно опустить:

package main

import "fmt"

func main() {

	var arr [4]string = [4]string{"a", "b", "c", "d"}

	fmt.Println("value")

	for _, value := range arr {

		fmt.Println(value)
	}
}
//Результат:
/*
value
a
b
c
d
*/

Такова форма, если нужно опустить value:

package main

import "fmt"

func main() {

	var arr [4]string = [4]string{"a", "b", "c", "d"}

	fmt.Println("key")

	for key := range arr {

		fmt.Println(key)
	}
}
//Результат:
/*
key
0
1
2
3
*/

Не станем спорить о пользе бесконечного цикла по диапазону; выглядит он таким образом:

package main

import "fmt"

func main() {

	var arr [4]string = [4]string{"a", "b", "c", "d"}

	
	for range arr {
		fmt.Println("In FOR cycle...")
	}
}
//Результат:
/*
In FOR cycle...
In FOR cycle...
In FOR cycle...
In FOR cycle...
*/

Операторы break и continue в цикле по диапазону остаются в прежней силе. Пожалуй, это исчерпывающая информация о цикле for.

Оператор goto

Коль скоро с оператором for мы расстались на ноте break и continue, перейдём по горячему следу к оператору goto. Как и в языке C, используется данный оператор совместно с метками. Синтаксис нехитрый:

handle:
//некое условие
goto handle

Существует тривиальное утверждение, что использование оператора goto - дурной тон программирования. Автор вполне согласен с данным утверждением. Это приём, ведущий к плохой читаемости кода и неочевидности ветвлений управления потоком, замысла программиста. Если вы использовали данный оператор - остановитесь, размыслите. Чаще всего, вы сможете таким образом "перефразировать" ваш код, что позволит в конечном счёте избавиться от goto. Однако, это довольно "прилипчивый" оператор, и иногда цена избавления от него - также неудобочитаемый и неясный код. To be or not to be - решение остаётся за вами. Приведу здесь вполне правдоподобный пример использования оператора goto. Пускай это будет программа "ping". Используя пакет стороннего разработчика github.com/sparrc/go-ping, программа производит опрос хоста ipAddr. В случае неудачи, программа повторяет это действие трижды.

package main

import (
	"fmt"
	"github.com/sparrc/go-ping"
)

func main() {

	var rtrCounter = 0  //счётчик повторов
	var ipAddr = "192.168.1.1"
	pinger, err := ping.NewPinger(ipAddr)
	if err != nil {
		panic(err) //выход из программы!
	}
	pinger.SetPrivileged(true)
	pinger.Count = 2

Retry:
	pinger.Run()
	stats := pinger.Statistics() //get send/receive/rtt stats
	if stats.PacketLoss != 0 {
		fmt.Println("ERROR!")
		rtrCounter++
		if rtrCounter <= 3 {
			fmt.Println("Retrying... ", rtrCounter)
			goto Retry
		}
	} else {
		fmt.Println("SUCCESS!")
	}

}

Для оживления изложения, добавлю сюда, пожалуй, пример решения задачки из leetcode. Задание таково:

Write a function to find the longest common prefix string amongst an array of strings. If there is no common prefix, return an empty string "".

Example 1:

Input: strs = ["flower","flow","flight"]

Output: "fl"

Example 2:

Input: strs = ["dog","racecar","car"]

Output: "" Explanation: There is no common prefix among the input strings.

Решение:

func longestCommonPrefix(strs []string) string {
  prefix := strs[0]
	start:
	for _, str := range strs {
		if !strings.HasPrefix(str, prefix) {
			prefix = prefix[:len(prefix)-1]
			goto start
		}
	}
	return prefix    
}

Решение достаточно оригинальное, и к тому же демонстрирует высокое сравнительное быстродействие.

If...else - неразлучная пара

Если излагать предмет в том порядке, как это обычно делают во всех пособиях, то именно с операторов if и else следовало начинать повествование в этой главе. Однако применение этих операторов в языке Go достаточно тривиально, что и натолкнуло автора на мысль начать рассказ именно с наиболее интересного - с ярких отличий. Однако, по ходу изложения, мы уже столь часто были вынуждены прибегнуть к использованию if...else, что разобрать данный предмет пришло, кажется, самое время.

Итак, приведём пример обычного использования оператора if:

package main

import "fmt"

func main() {

	var flag = true
	
	//круглые скобки не нужны!
	if flag { //фигурная скобка в той же строке!  
		fmt.Println("Fizz!")
	}

}

Как обычно, оператор if дополняет комплиметнарный ему оператор else:

package main

import "fmt"

func main() {

	var flag = true

	//круглые скобки не нужны!
	if flag { //фигурная скобка в той же строке!
		fmt.Println("Fizz!")
	} else { //else в той же строке!
		fmt.Println("Buzz!")

	}

}

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

Как и во многих других языках, в Go допустима конструкция else if:

package main

import "fmt"

func main() {

	x := 5
	y := 5

	if x > y {
		fmt.Println("x > y")
	} else if y > x {
		fmt.Println("y > x")

	} else {
		fmt.Println("x = y")
	}

}
//Результат:
//x = y

Блоков else if может быть в коде несколько.

Особым приёмом, отличительным для языка Go, является объявление переменных и использование выражений присваивания внутри оператора if:

package main

import "fmt"

func main() {	
	
	
	if ok := false; !ok {   
		fmt.Println("Ooops, something is wrong...")
	}

}
//Результат:
//Ooops, something is wrong...

Обратите внимание на использование точки с запятой внутри такого выражения. Данная форма записи - очень экономная; она компактна, а также ограничивает область видимости и время существования объявленной таким образом переменной границами текущего блока кода. В уместном случае, рекомендуется пользоваться данным приёмом.

Необходимо сказать несколько слов об идеологии использования в языке Go операторов if...else. Повторим, что сердцевина этого языка - компактность и краткость. Именно поэтому, хорошим тоном программирования на Go считается так компоновать свой код, чтобы максимально сокращать все выражения, использовать как можно меньшее количество ветвлений, а в идеале - единственный оператор if.

Хорошо:

package main

import "fmt"

func main() {
	var flag = true

	if flag {
		fmt.Println("Allright!")
	} else {
		fmt.Println("Ooops, something is wrong...")
	}

}

А так лучше:

package main

import "fmt"

func main() {
	var flag = true
	
	if !flag {
		fmt.Println("Ooops, something is wrong...")
		return
	}

	fmt.Println("Allright!")	

}

Также отметим, что чаще всего, цепочка ветвлений if...else той или иной длины чаще всего может быть заменена эквивалентным оператором switch. Перейдём к этому оператору.

Оператор switch

Особенностью оператора switch в языке Go, отличающей его, к примеру, от таких языков, как C/C++, C# и Java, является отсутствие так называемого "проваливания" от одного оператора case к другому. Закономерным следствием этого есть отсутствие необходимости использования оператора break в каждом кейсе. Как обычно, допускается и приветствуется использование заключительного default.

Пример обычного switch:

package main

import "fmt"

func main() {

	var os = "Windows"

	switch os {//открывающая скобка в той же строке!
	case "Windows":
		fmt.Println("Windows")
	case "Linux":
		fmt.Println("Linux")
	default:
		fmt.Println("Unsupported OS")
	}

}
//Результат:
//Windows

Чтобы сработал default:

package main

import "fmt"

func main() {

	var os = "FreeBSD"

	switch os {
	case "Windows":
		fmt.Println("Windows")
	case "Linux":
		fmt.Println("Linux")
	default:
		fmt.Println("Unsupported OS")
	}

}
//Результат:
//Unsupported OS

Приведём более сложный пример (аргументом case является предикат):

package main

import "fmt"

func main() {

	var flag = true
	var x = 0

	switch flag {
	case x > 0:
		fmt.Println("x > 0")
	case x < 0:
		fmt.Println("x < 0")
	default:
		fmt.Println("Zero!")
	}

}
//Результат:
//Zero!

Аргументом switch также может быть выражение:

package main

import "fmt"

func main() {

	x := 2
	switch x + 2 {
	case 4:
		fmt.Println("2+2=4")
	case 5:
		fmt.Println("2+2=5")
	default:
		fmt.Println("I don`t know")

	}
}
//Результат:
//2+2=4

После оператора case можно указывать несколько значений, разделённых запятыми:

package main

import "fmt"

func main() {

	x := 2
	switch x {
	case 0, 1, 2, 3:
		fmt.Println("Число принадлежит диапазону 0...3")
	default:
		fmt.Println("Число не принадлежит заданному диапазону")

	}
}
//Результат:
//Число принадлежит диапазону 0...3

Таким образом, мы рассмотрели все аспекты применения оператора switch.

Подытожим. На данный момент, мы изучили все операторы управления потоком в языке Go, за исключением оператора select, который является своеобразным аналогом оператора switch для так называемых каналов, к которым мы обратимся только при изучении многопоточности.

Раунд!

PreviousКонстантыNextМассивы, срезы, отображения

Last updated 3 years ago

Was this helpful?

Операторы break и continue могут использоваться с метками, подобно оператору goto. Опишем синтаксис подобного явления. Для оживления изложения, построим так называемое "треугольное число" (последовательность в ):

A000217
OEIS