Pointers and Structs
Go has pointers, but no pointer arithmetic. Structs are the way to create complex data types.
Pointers
A pointer holds the memory address of a value. Think of it as a reference to where data lives in memory, rather than the data itself.
Key Syntax:
- The type
*T is a pointer to a T value. Its zero value is nil.
- The
& operator generates a pointer to its operand (“address of”).
- The
* operator denotes the pointer’s underlying value (“dereferencing”).
func main() {
i, j := 42, 2701
p := &i // p is a pointer to i (p stores i's memory address)
fmt.Println(*p) // read i through the pointer (prints 42)
*p = 21 // set i through the pointer (i is now 21)
fmt.Println(i) // see the new value of i (prints 21)
p = &j // point to j instead
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j (prints 73)
}
Why Use Pointers?
- Modify Function Arguments: Pass a pointer to allow a function to modify the original value.
- Avoid Copying Large Structs: Passing a pointer is more efficient than copying a large struct.
- Share Data: Multiple parts of your program can reference the same data.
No Pointer Arithmetic: Unlike C, Go has no pointer arithmetic. You cannot do p++ to move to the next memory address. This prevents common memory bugs.
Structs
A struct is a collection of fields.
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
Pointers to Structs
Struct fields can be accessed through a struct pointer.
v := Vertex{1, 2}
p := &v
p.X = 1e9 // Equivalent to (*p).X
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.
Struct Literals
You can list just a subset of fields by using the Name: syntax. (And the order of named fields is irrelevant.)
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
Methods
Go does not have classes. However, you can define methods on types.
A method is a function with a special receiver argument.
type Vertex struct {
X, Y float64
}
// Abs method has a receiver of type Vertex named v
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
Pointer Receivers
You can declare methods with pointer receivers.
This means the receiver type has the literal syntax *T for some type T. (Also, T cannot itself be a pointer such as *int.)
Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs()) // Output: 50
}
Choosing a Value or Pointer Receiver
There are two reasons to use a pointer receiver:
- Modify the receiver: The method needs to modify the value that the receiver points to.
- Performance: To avoid copying the value on each method call. This is more efficient if the struct is large.
Rule of Thumb: If in doubt, use a pointer receiver.