Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Latest commit

 

History

History
executable file
·
330 lines (226 loc) · 8.27 KB

02.3.md

File metadata and controls

executable file
·
330 lines (226 loc) · 8.27 KB

02.3 - Pointers, structs, arrays, slices and range

Pointers

Similar to C:

  • Point with *: var p *int == int *p;
  • Generate pointer (get address of) with &: i := 1 and p = &i

No pointer arithmetic.

Function arguments: variables vs. pointers

Functions/methods accept both variables and pointers. The golden rule is:

  • Pass pointers when function/method needs to modify the parameter.

When a variable is passed, the function/method gets a copy and the original copy is not modified. With pointers the underlying value is modified.

Structs

Go does not have classes. It has structs like C.

Exported field names need to be uppercase to be visible outside the defining package.

// 02.3-01-structs.go
package main

import "fmt"

type Student struct {
    FirstName string
    LastName  string
}

func main() {
    // Make an instance
    studentOne := Student{"Ender", "Wiggin"}

    // Now we can access fields
    fmt.Println(studentOne.FirstName)

    // We can just assign fields using names, anything not assigned will be
    // initialized with "zero" as we have seen before
    studentTwo := Student{FirstName: "Petra"}

    // We will print "{Petra }" notice the space after Petra which is supposed
    // to be the delimiter between the fields, LastName is nil because it is not
    // given a value
    fmt.Println(studentTwo)

    // Can also make a pointer to a struct
    p := &studentOne

    // Now instead of *p.LastName (doesn't work) we can just use p.LastName
    // fmt.Println((*p).LastName) will not work with error message: invalid indirect of p (type Student)
    fmt.Println(p.LastName)

    // Which is the same as
    fmt.Println(studentOne.LastName)

    // We can just create a pointer out of the blue
    p2 := &Student{"Hercule", "Poirot"}
    fmt.Println(p2)
}

Tour of Go says, we have to create a pointer to a struct to access fields while we can just do it directly as we saw in the code.

Arrays

var a [10]int == int a[10];.

Arrays cannot be resized.

// 02.3-02-array.go
package main

import "fmt"

func main() {

    var a [5]int
    a[0] = 10
    a[4] = 20

    fmt.Println(a) // [10 0 0 0 20]

    // Array can be initialized during creation
    // characters[2] is empty
    characters := [3]string{"Ender", "Pentra"}

    fmt.Println(characters) // [Ender Pentra ]
}

Slices

Slice is a dynamic view of an array. Slices don't store anything by themselves, they reference an array. If we change something via the slice, the array is modified.

Think of slices as dynamic arrays. When a slice is created out of the blue, an underlying array is also initialized and can be modified by the slice.

// 02.3-03-slice1.go
package main

import "fmt"

func main() {

    // Create an array of strings with 3 members
    characters := [3]string{"Ender", "Petra", "Mazer"}

    // Last index is exclusive
    // allMembers []string := characters[0:3]
    var allMembers []string = characters[0:3]
    fmt.Println("All members", allMembers)

    var lastTwo []string = characters[1:3]
    fmt.Println("Last two members", lastTwo)

    // Replace Mazer with Bean
    fmt.Println("Replacing Mazer with Bean")
    allMembers[2] = "Bean"

    fmt.Println("All members after Bean swap", characters)

    fmt.Println("Last two members after Bean swap", lastTwo)
}

We can create array and slice literals. Meaning we can just initialize them by their members instead of assigning a length and then add more members. If a slice literal is created, the underlying array is also created.

// 02.3-04-slice2.go
package main

import "fmt"

func main() {

    // Slice literal of type struct, the underlying array is created automatically
    sliceStruct := []struct {
        a, b int
    }{
        {1, 2},
        {3, 4},
        {5, 6}, // need this comma in the end otherwise it will not work
    }

    fmt.Println(sliceStruct)
}

If a length is not specified during array creation, the result is a slice literal as seen above.

If we do not want to specify a length we can use [...].

// 02.3-05-slice3.go
package main

import "fmt"

func main() {

    characters := [...]string{"Ender", "Petra", "Mazer"}

    fmt.Println(characters)
}

Slice length and capacity

Slices have length and capacity.

  • Length is the current number of items in the slice. Returned by len(slice).
  • Capacity is the maximum number of items in the slice. Returned by cap(slice). Capacity is determined by the number of items in the original array from the start of the slice and not the size of array. For example if the slice starts from the second item (index 1) of an array, slice capacity is len(array)-1. This ensures that the slice cannot go past the array.

In most cases, we do not care about capacity. Create slices and append to them.

// 02.3-06-slice4.go
package main

import "fmt"

func main() {

    ints := [...]int{0, 1, 2, 3, 4, 5}
    fmt.Println(ints)

    slice1 := ints[2:6]

    // len=4 and cap=4 (from 3rd item of the array until the end)
    printSlice(slice1)

    slice1 = ints[2:4]

    // len=2 but cap will remain 4
    printSlice(slice1)
}

// Copied from the tour
func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

make

To create dynamically-sized arrays use make. make creates a zero-ed array and returns a slice pointing to it.

  • slice1 := make([]int, 10) creates an int array of length 10.
  • slice2 := make([]int, 5, 10) creates an int array of length 5 and capacity of 10.

We can append stuff to slices and it grows as needed:

  • slice1 = append(slice1, 1)

We can append multiple elements:

  • slice1 = append(slice1, 1, 2, 3)

append

In order to append one slice to another (obviously they should be of the same type), we have to use ... as follows:

  • slice1 = append(slice1, slice2...)

append is a variadic function, meaning it can an arbitrary number of arguments. By passing slice2..., we are essentially passing each member of slice2 one by one to append.

This is pretty useful later on when we want to append two byte slices together.

// 02.3-07-slice-append.go
package main

import "fmt"

func main() {

    // Create a slice pointing to an int array
    s1 := make([]int, 5)

    fmt.Println(s1) // [0 0 0 0 0]

    for i := 0; i < len(s1); i++ {
        s1[i] = i
    }

    fmt.Println(s1) // [0 1 2 3 4]

    s2 := make([]int, 3)

    for i := 0; i < len(s2); i++ {
        s2[i] = i
    }

    fmt.Println(s2) // [0 1 2]

    s3 := append(s1, s2...)

    fmt.Println(s3) // [0 1 2 3 4 0 1 2]
}

range

range iterates over slices. It returns an index and a copy of the item stored at that index.

  • for index, value := range slice

value is optional but index is not. Ignore either with _.

// 02.3-08-range.go
package main

import "fmt"

func main() {
    characters := [3]string{"Ender", "Petra", "Mazer"}
    for i, v := range characters {
        fmt.Println(i, v)
    }

    // 0 Ender
    // 1 Petra
    // 2 Mazer

    fmt.Println("-----------")

    // Only using index
    for i := range characters {
        fmt.Println(i, characters[i])
    }

    fmt.Println("-----------")

    // Ignoring index
    for _, v := range characters {
        // No non-elaborate way to get index here
        fmt.Println(v)
    }

    // Ender
    // Petra
    // Mazer
}

Continue reading ⇒ 02.4 - Methods and interfaces