Control Structures

If Statements

If statements in Go are similar to those in C-like languages. For example:

package main  // every Go program must start with a package declaration

import "fmt"  // print functions and Scanf are in the fmt package

func main() {
    fmt.Print("Please enter your score (0 to 100): ")

    var score float64  // score is a 64-bit floating point number
                       // initialized to 0.0

    _, ok := fmt.Scanf("%f", &score)  // _ is the blank identifier,
                                      // and it is used here to discard the
                                      // first value (the number of items
                                      // scanned) returned by Scanf
    if ok != nil {
        panic("input error")
    }

    fmt.Printf("Your score: %.1f\n", score)

    if score < 0 {
        fmt.Println("Negative scores are not allowed.")
    } else if score < 50 {
        fmt.Println("Sorry, you failed.")
    } else if score < 100 {
        fmt.Println("Congratulations, you passed.")
    } else if score == 100 {
        fmt.Println("Perfect! You passed.")
    } else {
        fmt.Println("Scores over 100 are not allowed.")
    }
}

Notice that you don’t need to put the if-conditions inside ()-brackets.

One other small difference with if-statements are that they must always use curly braces, even for a body with only a single statement. For example, this causes a compiler error:

if ok != nil               // compiler error: missing braces
    panic("input error")

You must write this:

if ok != nil {
    panic("input error")
}

Switch Statements

The Go switch statement is similar to the one in C, although more flexible and with different default behavior. For instance, you can re-write the if-else-if structure from the previous example as a switch:

switch {          // notice that there is no variable here
case score < 0:
    fmt.Println("Negative scores are not allowed.")
case score < 50:
    fmt.Println("Sorry, you failed.")
case score < 100:
    fmt.Println("Congratulations, you passed.")
case score == 100:
    fmt.Println("Perfect! You passed.")
default:
    fmt.Println("Scores over 100 are not allowed.")
}

Switch statements that check values of a variable are not limited to characters and integers. For example, here is a switch on a string:

// ... grade is a previously assigned string variable ...

switch grade {
case "A+", "A", "A-":
    fmt.Println("excellent")
case "B+", "B", "B-":
    fmt.Println("good")
case "C+", "C", "C-":
    fmt.Println("average")
case "D":
    fmt.Println("below average")
case "F":
    fmt.Println("failure")
default:
    panic("unknown grade \"" + grade + "\"")
}

Note the use of panic in the default clause. panic is a built-in Go function that, when called, will intentionally crash the program. It should be used in cases where a very serious problems occurs that does not allow the program to continue executing normally.

Importantly, Go’s switch statements do not fall-through to the next case by default. If you want that behaviour, add the statement fallthrough as the last statement of case to make the flow of control go to the next case.

C-like For Loops

Go has only one looping structure:: for. It is flexible enough to simulate C-style while loops and for-loops, plus range-style loops.

Here’s a traditional C-style for loop that sums the squares of the numbers from 1 to 100:

var sum int
for i := 0; i < 100; i++ {
    sum += (i + 1) * (i + 1)
}
fmt.Printf("sum: %d\n", sum)

Notice there are no ()-brackets required around the statements just after for.

As with if-statements, the curly braces are always required:

for i := 1; i <= 100; i++   // compiler error: missing braces
    sum += i * i

C-like While Loops

Go can simulate C-style while-loops as by leaving out the initialization and increment statements of a for loop:

i := 1
for i <= 100 {
    sum += i * i
    i++
}

If you want an infinite loop, you can write this:

for {
  fmt.Println("and again and again ...")
}

Ranged Loops

Finally, ranged for-loops iterate through a collection. For example:

s := "apple"
for i, c := range s {    // := used so that i and c are created
    fmt.Printf("%d, %c\n", i, c)
}

This prints:

0, a
1, p
2, p
3, l
4, e

This style of loop also works with arrays, slices, and maps. In many cases, ranged loops are the preferred kind of loop because they handle the details of accessing all the elements of a collection.

Sometimes you might just want the values of the collection, and have no need for the index, e.g.:

s := "apple"
for i, c := range s {     // compiler error: i is not used!
    fmt.Printf("%c\n", c)
}

Unfortunately, this doesn’t compile because i is not used and Go doesn’t permit unused variables. The solution is to use the blank identifier _ in place of i:

s := "apple"
for _, c := range s {     // _ used instead of i
    fmt.Printf("%c\n", c)
}

This compiles and runs as expected.

Questions

  1. Give two examples of how the Go switch statement is more general than the traditional switch statement in C/C++.

  2. What is the blank identifier? Why is it useful?

  3. What happens when you try to run and compile this code:

    // assume fmt has been imported
    
    x := 6
    if x > 5
       fmt.Println("x is ")
       fmt.Println("greater ")
    
    fmt.Println("done")
    
  4. Write a loop that prints the numbers from 10 down to 1. Do it in Go using a C-like:

    1. for-loop
    2. while-loop
  5. Write a Go ranged loop that prints just the characters of a string s in reverse order. For example, if s is "apple", the loop should print:

    e
    l
    p
    p
    a
    
  6. Go does not have a do-while loop, i.e. a loop whose condition is checked at the end of the loop body instead of the beginning. Is this a good feature or bad feature of Go? Justify your answer.