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.

Also, you must always use curly braces for the body of an if-statment, even when it contains a single statement. For example, this causes a compiler error:

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

You must instead write this:

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

Switch Statements

The Go switch statements come in two main varieties. The first kind is just an alternative syntax for if-else-if structures. For example:

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.")
}

Notice that no “break” statements are needed. In a C-style switch statement, we’d have to put break after each case body to avoid “fall-through”. But in Go the default is not to fall-through a switch statement, which is almost always what is wanted. If for some reason you do want fall-through behaviour, add the statement fallthrough as the last statement of case body to make the flow of control automatically go to the next case.

The second style of Go switch statements takes a variable and switches on its value. 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 that grade is a string variable, and so this shows that Go can switch on strings (as well as numbers and characters).

Also note the use of panic in the default clause. panic is a built-in Go function that, when called, intentionally crashes the program. It should be used in cases where a serious unrecoverable problems occurs.

C-like For Loops

Go has only one named loop: for. It is flexible enough to simulate both 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 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 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 compile and run this code fragment?

    // 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.