Skip to content

Latest commit

 

History

History
executable file
·
407 lines (299 loc) · 11.3 KB

02.5.md

File metadata and controls

executable file
·
407 lines (299 loc) · 11.3 KB

02.5 - Printf, Scanf, bufio readers and maps

Print

The fmt package contains printf. It's very similar to the C equivalent.

These three need a format string:

  • fmt.Sprintf returns a string.
  • fmt.Fprintf takes any objects that implements io.Writer for example os.Stdout and os.Stderr.
  • fmt.Printf prints to stdout.

The following are similar but do not need a format string:

  • fmt.Print and fmt.Println
  • fmt.Fprint
  • fmt.Sprint

Print verbs

To format strings we can use verbs (also known as switches). For more information on switches, see the fmt package source.

Decimals

  • %d: digits = numbers.
  • %nd: n = width of number. Right justified and padded with spaces. To left justify use - like %-nd. If n is less than the number of digits nothing happens.
  • %b: number in binary.
  • %c: chr(int), prints the character corresponding to the number.
  • %x: hex.

Floats

  • %f: float.
  • %n.mf: n = decimal width, m = float width. Right justified. To left justify use - like %-n.mf. If n is less than the number of digits nothing happens.
  • %e and %E: scientific notation (output is a bit different from each other).

Value

  • %v or value: catch all format. Will print based on value.
  • %+v: will print struct's field names if we are printing a struct. Has no effect on anything else.
  • %#v: prints code that will generate that output. For example for a struct instance it will give code that creates such a struct instance and initializes it with the current values of the struct instance.

Strings

  • %q: double-quotes the strings before printing and also prints any invisible characters.
  • %s: string.
  • %ns: control width of string. Right justified, padded with spaces. To left justify use - like %-ns. If n is less than the length of the string, nothing happens.

Others

  • %t: boolean.
  • %T: prints the type of a value. For example int or main.myType.

Print verbs in action

// 02.5-01-print-verbs.gos
package main

import "fmt"

type myType struct {
    field1 int
    field2 string
    field3 float64
}

func main() {

    // int
    fmt.Println("int:")
    int1 := 123
    fmt.Printf("%v\n", int1)     // 123
    fmt.Printf("%d\n", int1)     // 123
    fmt.Printf("|%6d|\n", int1)  // |   123|
    fmt.Printf("|%-6d|\n", int1) // |123   |
    fmt.Printf("%T\n", int1)     // int
    fmt.Printf("%x\n", int1)     // 7b
    fmt.Printf("%b\n", int1)     // 1111011
    fmt.Printf("%e\n", int1)     // %!e(int=123)
    fmt.Printf("%c\n", int1)     // { - 0x7B = 123
    fmt.Println()

    // float
    fmt.Println("float:")
    float1 := 1234.56
    fmt.Printf("%f\n", float1)       // 1234.560000
    fmt.Printf("|%3.2f|\n", float1)  // |1234.56|
    fmt.Printf("|%-3.2f|\n", float1) // |1234.56|
    fmt.Printf("%e\n", float1)       // 1.234560e+03
    fmt.Printf("%E\n", float1)       // 1.234560E+03
    fmt.Println()

    // string
    fmt.Println("string:")
    string1 := "Petra"
    fmt.Printf("%s\n", string1)      // Petra
    fmt.Printf("|%10s|\n", string1)  // |     Petra|
    fmt.Printf("|%-10s|\n", string1) // |Petra     |
    fmt.Printf("%T\n", string1)      // string
    fmt.Println()

    // boolean
    fmt.Println("boolean:")
    boolean1 := true
    fmt.Printf("%t\n", boolean1) // true
    fmt.Printf("%T\n", boolean1) // bool
    fmt.Println()

    // struct type
    fmt.Println("struct:")
    struct1 := myType{10, "Ender", -10.2}
    fmt.Printf("%v\n", struct1)  // {10 Ender -10.2}
    fmt.Printf("%+v\n", struct1) // {field1:10 field2:Ender field3:-10.2}
    fmt.Printf("%#v\n", struct1) // main.myType{field1:10, field2:"Ender", field3:-10.2}
    fmt.Printf("%T\n", struct1)  // main.myType
}

Scan

As expected Go has Scan functions for reading input. Like Printf the package description is comprehensive.

The functions read from standard input (os.Stdin):

  • Scan: treats new lines as spaces.
  • Scanf: parses arguments according to a format string.
  • Scanln: reads one line.

These read from io.Readers:

  • Fscan
  • Fscanf
  • Fscanln

These read from an argument string:

  • Sscan
  • Sscanf
  • Sscanln

As you can guess, the following stop at the first new line or EOF:

  • Scanln
  • Fscanln
  • Sscanln

While these treat new lines as spaces:

  • Scan
  • Fscan
  • Sscan

Similar to Printf we can use format strings with these functions:

  • Scanf
  • Fscanf
  • Sscanf

Scan verbs

Scan verbs are the same as Print. %p, %T and # + flags are not implemented.

Apart from %c every other verb discards leading whitespace (except new lines).

Reading user input with Scanln

Let's start by something simple like reading a line from input:

// 02.5-02-scan1.go
package main

import "fmt"

func main() {

    var s string
    n, err := fmt.Scanln(&s)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Entered %d word(s): %s", n, s)
}

All is well when input does not have any whitespace (e.g. space):

$ go run 02.5-02-scan1.go
HelloHello
Entered 1 word(s): HelloHello

But When input has whitespace:

$ go run 02.5-02-scan1.go
Hello Hello
panic: expected newline

goroutine 1 [running]:
main.main()
        Z:/Go/src/Hacking-with-Go/code/02/02.5/02.5-02-scan1.go:10 +0x1ae
exit status 2

What's wrong with Scanln?

  1. Scan does not return the number of characters as we expect from the C equivalent. It returns the number of words entered.
  2. Scan and friends separate words by whitespace. Meaning when we entered Hello Hello, they are counted as two words. Scanln stored the first Hello in s and was expecting a new line or EOF to finish that. Instead it got a new word and panicked.

If we wanted to just read a number or anything without whitespace, it would have worked.

If we replace Scanln with Scan in the code, the program will not panic but will ignore anything after the first whitespace.

Lesson learned Don't use Scan for reading user input with whitespace.

bufio.Reader

An easier way to read user input is through bufio readers. We are looking for the quickest ways to get things done after all.

// 02.5-03-bufioreadstring.go
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {

    reader := bufio.NewReader(os.Stdin)
    // ReadString will read until first new line
    input, err := reader.ReadString('\n') // Need to pass '\n' as char (byte)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Entered %s", input)
}

ReadString reads until the first occurrence of its argument (delimiter). The delimiter should be a byte hence we need to pass a char using single quotes (\n). "\n" is a string and will not work.

bufio.Reader has more methods for reading different types. For example we can directly read user input and convert it to bytes with ReadBytes.

// 02.5-04-bufioreadbytes.go
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {

    reader := bufio.NewReader(os.Stdin)
    // Read bytes until the new line
    input, err := reader.ReadBytes('\n') // Need to pass '\n' as char (byte)
    if err != nil {
        panic(err)
    }

    // Print type of "input" and its value
    fmt.Printf("Entered type %T, %v\n", input, input)
    // Print bytes as string
    fmt.Printf("Print bytes as string with %%s %s", input)
}

We are printing the type of input and its value as-is first. Then we print the bytes as string with %s.

$ go run 02.5-04-bufioreadbytes.go
Hello 0123456789
Entered type []uint8, [72 101 108 108 111 32 48 49 50 51 52 53 54 55 56 57 13 10]
Print bytes as string with %s Hello 0123456789

As you can see bytes are just uint8 (unsigned ints) and printing them yields decimal values and not hex. Don't worry about bytes and strings now. We will have a byte manipulation chapter.

Maps

Fast lookup/add/delete. Each key is associated with a value (similar to Python dict).

Declare an initialized map:

  • mapName := make(map[KeyType]ValueType).

KeyType needs to be a comparable type. ValueType can be anything.

If a key does not exist, the result is a zero value. For example 0 for int.

To check if a key exists or not (0 might be a valid value in the map) use:

  • value, ok := mapName[key]

If ok is true then the key exists (and false if the key is not there).

To test for a key without getting the value drop the value like this _, ok := mapName[key] and then just check ok.

range iterates over the contents of a map like arrays/slices. But we get keys instead of indexes. Typically we use the range with a for:

  • for key, value := range mapName

We can create a map using data:

  • m := map[string]int{"key0": 0, "key1": 1}

Delete a key/value pair with delete:

  • delete(m, "key0")
// 02.5-05-maps.go
package main

import "fmt"

type intMap map[int]int

// Create a Stringer for this map type
func (i intMap) String() string {

    var s string
    s += fmt.Sprintf("Map type %T\n", i)
    s += fmt.Sprintf("Length: %d\n", len(i))

    // Iterate through all key/value pairs
    for k, v := range i {
        s += fmt.Sprintf("[%v] = %v\n", k, v)
    }
    return s
}

func main() {

    // Create a map
    map1 := make(intMap)

    // Add key/value pairs
    map1[0] = 10
    map1[5] = 20

    // Print map - Stringer will be called
    fmt.Println(map1)
    // Map type main.intMap
    // Length: 2
    // [0] = 10
    // [5] = 20

    // Delete a key/value pair
    delete(map1, 0)

    fmt.Println(map1)
    // Map type main.intMap
    // Length: 1
    // [5] = 20

    // Create a map on the spot using members
    map2 := map[string]string{"key1": "value1", "key2": "value2"}

    fmt.Println(map2)
    // map[key1:value1 key2:value2]
}

Continue reading ⇒ 02.6 - Goroutines and channels