03. Flow Control

if условие

Условное выражение if в Go:

func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

Like for, the if statement can start with a short statement to execute before the condition.

  • Variables declared by the statement are only in scope until the end of the if.

  • Variables declared inside an if short statement are also available inside any of the else blocks.

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    } else {
        fmt.Printf("%g >= %g\n", v, lim)
    }
    return lim
}

for loop

Go has only one looping construct, the for loop. The basic for loop has three components separated by semicolons:

  • the init statement: executed before the first iteration

  • the condition expression: evaluated before every iteration

  • the post statement: executed at the end of every iteration

for i := 0; i < 20; i++ {
    fmt.Println(i)
}

Классический цикл по массиву или слайсу:

for i := 0; i < len(sl); i++ {
    println("c-style loop", i, sl[i])
}

Note: Unlike other languages like C, Java, or JavaScript there are no parentheses surrounding the three components of the for statement and the braces { } are always required.

The init and post statement are optional. At that point you can drop the semicolons:

sum := 1
for sum < 1000 {
    sum += sum
}

If you omit the loop condition it loops forever, so an infinite loop is compactly expressed.

for {
    println("бесконечный цикл")
    break
}

Range for loop

  • The range form of the for loop iterates over a slice, map or channel.

  • When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.

  • The range loop evaluates the provided expression only once, before the beginning of the loop, by doing a copy (regardless of the type).

  • When a range loop iterates over a data structure, it performs a copy of each element to the value variable (the second item). To avoid copy either use a classic for loop or a range loop using the index instead of the value variable.

  • Iterating over using the range loop, regardless of the number of elements, creates a single variable for the value with a fixed address. This value is being reassigned on each iteration, but it keeps the same address.

for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
}

You can skip the index or value by assigning to _.

pow := make([]int, 10)
for i := range pow {
    pow[i] = 1 << uint(i) // == 2**i
}

for _, value := range pow {
    fmt.Printf("%d\n", value)
}

It’s important to know that range is making a copy of the value, not returning a reference. If you use the address of the value variable as a pointer to each element, you’ll be making a mistake.

Iterating over map you shouldn’t make any ordering assumptions at all:

  • It doesn’t keep the data sorted by key (a map isn’t based on a binary tree).

  • It doesn’t preserve the order in which the data was added.

  • Randomize order for each iteration.

  • An element being produced during the same iteration in which it’s added.

switch

A switch statement is a shorter way to write a sequence of if - else statements. It runs the first case whose value is equal to the condition expression.

  • Go only runs the selected case, not all the cases that follow.

  • Go's switch cases need not be constants, and the values involved need not be integers.

  • Switch cases evaluate cases from top to bottom, stopping when a case succeeds.

  • the order of the default branch in a switch-case control flow block can be arbitrary

  • Switch without a condition is the same as switch true.

  • break inside case interrupt current case

  • fallthrough keyword used to indicate that next case should be proceed.

    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
      case "darwin":
          fmt.Println("OS X.")
      case "linux":
          fmt.Println("Linux.")
      case  "windows":
          if mm["flag"] == "Ok" {
              break // выходим из switch, чтобы не выполнять переход в другой вариант
          }
          fmt.Println("Windows.")
          fallthrough // переходим в следующий вариант
      default:
          // freebsd, openbsd,
          // plan9, windows...
          fmt.Printf("%s.", os)
    }

Case could contain multiple options:

func isShellSpecialVar(c uint8) bool {
    switch c {
    case '*', '#', '$', '@', '!', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
        return true
    }
    return false
}

Blank switch: If the switch has no expression it switches on true. It's therefore possible and idiomatic—to write an if-else-if-else chain as a switch.

func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}
switch wordLen := len(word); { 
case wordLen < 5:
        fmt.Println(word, "is a short word!") 
case wordLen > 10:
        fmt.Println(word, "is a long word!") 
default:
        fmt.Println(word, "is exactly the right length.")
}

Favor blank switch statements over if/else chains when you have multiple related cases. Using a switch makes the comparisons more visible and reinforces that they are a related set of concerns.

Labels and goto

  • A goto keyword must be followed by a label to form a statement.

  • A label which name is not the blank identifier must be used at least once.

  • A goto statement will make the execution jump to the next statement following the declaration of the label used in the goto statement.

  • if a label is declared within the scope of a variable, then the uses of the label can't appear before the declaration of the variable.

Loop:
    for key, val := range mm {
        println("switch in loop", key, val)
        switch {
        case key == "firstName" && val == "Vasily":
            println("switch - break loop here")
            break Loop
        }
    }
func main() {
	i := 0
Next:
	if i >= 5 {
		goto Exit
	}
	// Create an explicit code block to
	// shrink the scope of k.
	{
		k := i + i
		fmt.Println(k)
	}
	i++
	goto Next
Exit:
}

Метка может находится и ниже, но переход по ней не должен означать вводить новых переменых (что является side effect-ом).

Last updated