left-icon

Go Succinctly®
by Mark Lewin

Previous
Chapter

of
A
A
A

CHAPTER 6

Control Structures

Control Structures


At the end of the last chapter, we saw how we could use an if/else statement to affect the execution path of a program. This is known as conditional processing, in which the path the program takes depends on the result of an expression.

We’ll look at if/else in a little more depth in this chapter (you’ll be pleased to know that you have already seen most of what it has to offer), together with its cousin the switch statement.

Sometimes you’ll want your program to repeat a certain task a set number of times. Like most languages, Go provides a number of different alternatives for doing this, and we’ll consider these, too.

In doing both of these things, we’ll look at some other features of Go, such as writing functions and accepting arguments from the command line.

Using if/else

The if/else statement allows us to do one of two different things, depending on a particular condition:

if a == 1 {

  // a equals 1, so do this

} else {

  // a is not equal to 1, so do this instead

}

We can add further if/else statements to this if we want to conduct further tests. For example:

if a == 1 {

  // do this: a is 1

} else if a == 2 {

  // do this: a is 2

} else if b == 1 {

  // do this: b is 1, a is not 1 or 2

} else {

  // do this: b is not 1 and a is not 1 or 2

}

Let’s demonstrate this using an example, which will also give us a chance to cover a couple of features that Go supports, namely­ command-line arguments and functions.

Our program should allow us to input a set of four numbers, and it will tell us if each of those numbers is odd or even.

First, we need a way to input numbers. We can do this by passing the four numbers we are interested in to our Go program at runtime as arguments. For example:

go run main.go 12 4 7 93

To access those arguments, we need to use the os package and retrieve its Args property. Args is a special type of variable called a slice that we’ll consider in the next chapter. For now, just think of it as a collection of items that will include in its first position the directory path of our main.go program, and then, in subsequent positions, each of the arguments that we passed to the program at the command line.

Because we’re only interested in the arguments we passed, we can safely ignore the first element in Args. This is the element in position zero because, like strings, elements in Args are numbered starting at zero.

Code Listing 11

package main

import (

     "fmt"

     "os"

)

func main() {

   

    val1 := os.Args[1]

    val2 := os.Args[2]

    val3 := os.Args[3]

    val4 := os.Args[4]

    fmt.Printf("%s %s %s %s\n", val1, val2, val3, val4)

}

This program allows us to call our program with the four numbers we are interested in as parameters like so:

go run main.go 1 45 67 87

It then displays the numbers that we passed to it (as strings):

1 45 67 87

Now we can be sure that the program is accepting the numbers we are passing into it and storing those in variables we can use to access them.

The next thing we need to do is test each of those numbers to see if they are odd or even. To do that, we need to divide them by two and check to see if there is a remainder. Go provides the modulus (%) operator for that, which divides the first operand by the second operand and returns only the remainder. For example:

4 % 2 = 0

3 % 2 = 1

13 % 3 = 1

But we have four numbers, and it would be annoying to code the same test on each of them. Let’s suppose that at some stage we want to expand our program to accept a thousand numbers, or even a million (although we probably won’t be relying on the command line for that).

We’ll code the test just once and put it into a function, separate from but like our main() function, and we’ll call that instead. For now, we’ll just call the function once for each of our numbers, but later in this chapter, when we start looking at loops, we’ll tidy that up to accept an unlimited number of numbers as arguments.

Functions are defined by the keyword func, require a name and optionally a set of parameter values enclosed by parentheses and, if the function returns a value, the type of value it returns. For example:

func myFunction(myString string, myNum int) float32 {

     // some code that creates a float32 value called myFloat

     return myFloat

}

This function is called myFunction, accepts two parameters—a string called myString and an integer value called MyNum—and returns a value of float32 data type.

If your function does not require parameters, include the parentheses but leave them empty (like we have with main()). We need our function to accept a single parameter, which is the number we want to test.

We also want the function to return either odd or even when it is called, depending on the test results. We return a value to the calling function using the return keyword followed by the value we want to return. We’ll decide which value to return using an if/else statement. Here’s how we’ll code the function:

func oddOrEven(value string) string {

     num, _ strconv.Atoi(value)

     if num % 2 > 0 {

          return "even"

     } else {

          return "odd"

     }

}

Note how we’ve used the strconv.Atoi() function to convert the input parameter, which is of type string to an integer, so that we can use the modulus operator (%) on it.

Here’s what we end up with:

Code Listing 12

package main

import (

     "fmt"

     "os"

     "strconv"

)

func main() {   

  fmt.Printf("%s is %s\n", os.Args[1], oddOrEven(os.Args[1]))

  fmt.Printf("%s is %s\n", os.Args[2], oddOrEven(os.Args[2]))

  fmt.Printf("%s is %s\n", os.Args[3], oddOrEven(os.Args[3]))

  fmt.Printf("%s is %s\n", os.Args[4], oddOrEven(os.Args[4]))

}

func oddOrEven(value string) string {

     num, _ := strconv.Atoi(value)

     if num % 2 == 0 {

          return "even"

     } else {

          return "odd"

     }

}

go run main.go 12 78 99 37

12 is even

78 is even

99 is odd

37 is odd

Looping with the for statement

Looking at our program so far, it’s obvious that although we’re reusing code successfully by putting our odd/even detection logic in a function, we’re still manually calling that function for each of our input parameters. Furthermore, we have hard-coded the reference to each input parameter. It would be nice to let the program accept as many or as few input parameters as our users feel inclined to provide and let it conduct our test on each of them.

In order to do that, we need to know the number of parameters passed in for each execution of the program and a way to process each of them in turn.

Getting the number of parameters is easy, because you can use the standard len() function on Args to return the number of parameters it holds:

numParams := len(os.Args)

So all we need now is a way to iterate through each item in Args and do something with it. Enter the for statement.

The for statement allows you to repeat a block of statements multiple times. If you’ve used any C-style programming language, then you’re already familiar with it. The interesting thing is that, unlike most languages, the for statement is the only looping statement Go supports. However, it can be used in different ways to achieve the same thing as those other languages’ while and do-while constructs.

The basic syntax of the for loop is as follows:

for init; condition; post {

     // run commands until condition is true

}

Where both the init and post statements are optional:

  • The init statement is executed before the first iteration.
  • The condition expression is evaluated before every iteration.
  • The post statement is executed at the end of every iteration.

Note: Unlike with many C-style languages, there are no parentheses around the init, condition, and post components of the for statement, and you must always include the braces ({}).

Let’s look at the various forms. First of all, an infinite loop:

for {

     // The statements in this block execute forever

}

This form is probably of limited use unless the entire program repeats forever. For example, games often run in a continuous loop, processing all the action and responding to user input.

You can also use this form to perform the equivalent of a whiledo loop in other languages by adding a break statement to exit the loop when a condition is met:

total: = 1

for {

     if total > 1000 {

          break

     }

     total += 1

}

The following is the most common form and repeats the blocks in the statement from init until the condition is met, increasing the amount of init by the expression in post with each iteration:

for i := 1;  i <= 5; i++ {

     fmt.Printf("#%d\t", i)

}

It produces:

#1   #2   #3   #4   #5

The following replicates the while loop in other languages. It repeats the loop while the condition is met, but does not maintain its own loop counter like the previous example:

total := 1

for total < 1000 {

     total += total

}

The code in the for block repeats until total reaches 1,000.

For our purposes, the standard for init; condition; post {...} form will work well.

Code Listing 13

package main

import (

     "fmt"

     "os"

     "strconv"

)

func main() {   

     for i := 1; i < len(os.Args); i++ {

          fmt.Printf("%s is %s\n", os.Args[i],

                      oddOrEven(os.Args[i]))

     }

}

func oddOrEven(value string) string {

     num, _ := strconv.Atoi(value)

     if num % 2 == 0 {

          return "even"

     } else {

          return "odd"

     }

}

go run main.go 12 78 99 37

12 is even

78 is even

99 is odd

37 is odd

Note: By setting the initial loop counter i to 1 and not zero, we avoid the first element in Args (the path to the calling program).

The switch statement

Let’s consider a variation on the application we are building so that we can demonstrate the other main conditional processing construct in Go: the switch statement.

In this example, we’re going to allow the user to input a string. We’ll parse the string and count the number of letters from a to i, from j to r, and from s to z and any other characters, and display the information to the user.

We already know how to accept multiple command-line parameters to our program, so allowing just one will be child’s play!

We also know how to inspect the contents of a string. What’s more, we know how to iterate through the string, character by character, with our trusty for statement.

The only thing we really need to consider is how to count the number of characters in each range.

We could use a succession of if…else statements, but that would get really ugly, very quickly:

if letter = "a" {

     // increment the appropriate total

} else if letter = "b" {

...

} else if letter = "c" {

     ...

} ...

…and so on.

A better approach would be to use a switch statement. A switch statement consists of (optionally) a condition to test, then several case clauses where we specify the conditions we are trying to match against and write the code to deal with those conditions. We can also provide a default clause that executes code that will run if none of the previous case conditions have been met.

For example:

switch operatingSystem := userOS; operatingSystem {

     case: "Windows"

          fmt.Println("Made by Microsoft")

     case: "OS X"

          fmt.Println("Made by Apple")

     case: "Linux"

          fmt.Println("Made by a lot of clever volunteers")

     default:

          fmt.Println("I don't recognize that operating system. GEEK!")

}

Note: Here we have not only demonstrated the switch statement, but a common Go idiom: the ability to include an initialization statement in a control structure. We can do the same in an if statement.

If we just want to avoid a great big list of if…else statements, then we might not even need the condition:

numDaysInMonth := 30

switch {

     case numDaysInMonth >= 28 && numDaysInMonth <=29:

          fmt.Println("February")

     case numDaysInMonth == 30:

          fmt.Println("April, June, September, November")

     case numDaysInMonth == 31:

          fmt.Println("January, March, May, July, August, October,

 December")

}

Unlike most languages in which the code “falls through” to the next condition unless we provide a “break” statement at the end of each case code block, Go immediately exits the switch statement when a condition has been met and the code for the case has executed. If we want to continue checking subsequent conditions thereafter, we need to make this explicit through the use of the fallthrough keyword. This can be useful if we are testing data for which numerous conditions could apply.

But back to our example. We can use the switch statement to achieve our objective as follows:

Code Listing 14

package main

import (

     "fmt"

     "os"

)

func main() {   

  numAtoI, numJtoR, numStoZ, numSpaces, numOther := 0, 0, 0, 0, 0

  sentence := os.Args[1]

     

  for i := 1; i < len(sentence); i++ {

    letter := string(sentence[i])

    switch letter {

      case "a", "b", "c", "d", "e", "f", "g", "h", "i"               numAtoI += 1

      case "j", "k", "l", "m", "n", "o", "p", "q", "r"               numJtoR += 1

      case "s", "t", "u", "v", "w", "x", "y", "z"               

        numStoZ += 1

      case " ":

        numSpaces += 1

      default:

        numOther += 1

    }

  }

     

  fmt.Printf("A to I: %d\n", numAtoI)

  fmt.Printf("J to R: %d\n", numJtoR)

  fmt.Printf("S to Z: %d\n", numStoZ)

  fmt.Printf("Spaces: %d\n", numSpaces)

  fmt.Printf("Other: %d\n", numOther)

}

go run main.go "Everybody in the whole cell block (was dancing to the Jailhouse Rock)"

A to I: 25

J to R: 17

S to Z: 11

Spaces: 11

Other: 4

And we’re done! All this should look pretty familiar to you by now. There’s just one thing I’d like to point out: as we’re looping through the letters in the input string, what we’re getting back is a byte, not a string with a single character in it. That’s why we need to use an explicit cast to convert that byte to a string:

letter := string(sentence[i])

This allows us to get the character itself rather than the byte that represents it.

For example, if we did a byte-by-byte extract from the string Hello, it would look like this:

72 101 108 108 111

and not what we want, which is this:

H e l l o

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.