[Go] Pointers, Struct, Array

Pointers

Go has pointers. A pointer holds the memory address of a value.

The type *T is a pointer to a T value. Its zero value is nil

var p *int

The & operator generates a pointer to its operand.

1
2
i := 42
p = &i

The * operator denotes the pointer’s underlying value.

1
2
fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p

This is known as “dereferencing” or “indirecting”.

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
i, j := 42, 4701

p := &i // p point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // i is updated

p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // j is updated
}

Structs

A struct is a collection of fields.

Struct fields are accessed with a dot.

Struct fields can be accessed through a struct pointer.

To access the field X of a struct when we have the struct pointer p we could write (*p).X. However, that notation is cumbersome, so the language permits us instead to write just p.X, without the explicit dereference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Vertex struct {
X int
Y int
}

var (
v1 = Vertex{1, 2}
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // type *Vertex
)

func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)

p := &v
p.X = 1e9
fmt.Println(v)
}

Arrays

The type [n]T is an array of n values of type T.

var a [10]int declares a variable a as an array of ten integers. An array’s length is part of its type, so arrays cannot be resized.

1
2
3
4
5
6
7
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"

primes := [6]int{2, 3, 5, 7, 11, 13}
}

Slices

Check this: Go Slices: usage and internals

An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.

The type []T is a slice with elements of type T.

A slice is formed by specifying two indices, a low and high bound, separated by a colon:

a[low : high] (include low, exclude high)

1
2
3
4
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] // [3, 5, 7]
}

Slices are like references to arrays

A slice does not store any data, it just describes a section of an underlying array.

Changing the elements of a slice modifies the corresponding elements of its underlying array.

Other slices that share the same underlying array will see those changes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
a := names[0:2]
b := names[1:3]

b[0] = "XXX"
fmt.Prinln(a, b) // John, XXX, XXX, George
fmt.Prinln(names) // John, XXX, George..
}

A slice literal is like an array literal without the length.

Array literal: [3]bool{true, true, false}

And this creates the same array as above, then builds a slice that references it:

[]bool{true, true, false}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
q := []int{2, 3, 5, 7, 11, 13}
r := []bool{true, false, true, true, false, true}

s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true}
}
}

Slice defaults

When slicing, you may omit the high or low bounds to use their defaults instead.

The default is zero for the low bound and the length of the slice for the high bound.

var a [10]int

1
2
3
4
5
// Same below
a[0:10]
a[:10]
a[0:]
a[:]

A slice has both a length and a capacity

The length of a slice is the number of elements it contains.

The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.

The length and capacity of a slice s can be obtained using the expressions len(s) and cap(s).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it length = 0
s = s[:0]
printSlice(s)
// Extend its length
s = s[:4]
printSlice(s)
// Drop its first 2 elements
s = s[2:]
printSlice(s)
}

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

Nil slices

The zero value of a slice is nil.

A nil slice has a length and capacity of 0 and has no underlying array.

1
2
3
4
5
6
7
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}

Creating slice with make

To create dynamically-sized arrays: create slices with the built-in make function.

The make function allocates a zeroed array and returns a slice that refers to that array:

a := make([]int, 5) // len(a) = 5

To specify a capacity, pass a third argument to make.

1
2
3
b := make([]int, 0, 5) // len(b) = 0, cap(b) = 5
b = b[:cap(b)] // len(b) = 5, cap(b) = 5
b = b[1:] // len(b) = 4, cap(b) = 4
1
2
3
4
5
6
7
func main() {
a := make([]int, 5) // [0,0,0,0,0]
b := make([]int, 0, 5) // [] len=0, cap=5

c := b[:2] // [0,0] len=2, cap=5
c = c[3:3] // [] len=2, cap=2
}

Slice can contain any type, including other slices.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
// Create a tic-tac-toe board
board := [][] string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}

// They players take turns
board[0][0] = "X"
board[2][2] = "O"

for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " ")
}
}

Append to a slice

func appends(s []T, vs ...T) []T: The resulting value of append is a slice containing all the elements of the original slice plus the provided values.

If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.

1
2
3
4
5
6
7
8
func main() {
var s []int // []

// append on nil slices
s = append(s, 0) // [0]
s = append(s, 1) // [0,1]
s = append(s, 2, 3, 4) // [0,1,2,3,4]
}