Table of Contents
Overview
In this post, I’ll mainly explain what pointers
and structures
are. Pointers
and Structures
can be very hard concepts to grasp for people who previously only learned script languages like Ruby
, Python
and so on.
Pointers
How to declare a pointer
In brief, a pointer
is a value which points to the memory address of another value. A pointer
can be declared using the *
followed by the type of the stored value in Go.
The variable pointer1
is a pointer to an int
type value and pointer2
is a pointer to a string
type value in the following code.
package main
import "fmt"
func main() {
var pointer1 *int
var pointer2 *string
fmt.Println(pointer1, pointer2) //=> nil nil // A pointer's zero value is `nil`
}
How to retrieve the memory address of a variable
In Go, you can use the &
operator to retrieve the memory address of a variable. In the following code, &str
represents the memory address of the variable str
.
package main
import "fmt"
func main() {
var str = "Go"
fmt.Println(&str) //=> 0xc42000e1e0
}
How to use pointers
You can assign the memory address of a variable to a pointer
just like the code below.
package main
import "fmt"
func main() {
var pointer1 *int
var pointer2 *string
var str = "Go"
var num = 10
pointer1 = &num
pointer2 = &str
fmt.Println(pointer1, pointer2) //=> 0xc420016078 0xc42000e1e0
}
You can also declare a pointer
more succinctly like the following code.
package main
import "fmt"
func main() {
var num2 = 20
pointer3 := &num2
fmt.Println(pointer3) //=> 0xc420016080
}
pointer1
and pointer3
can only store the memory address of int
type values and pointer2
can only store the memory address of a string
type value respectively.
package main
import "fmt"
func main() {
var pointer1 *int
var str = "Go"
pointer1 = &str //=> cannot use &str (type *string) as type *int in assignment
}
Accessing the value of a variable through its pointer
The *
operator denotes the pointer’s underlying value. By using this feature, you can set, read and change the value of a variable through its pointer.
package main
import "fmt"
func main() {
var pointer1 *int
var num = 10
pointer1 = &num
fmt.Println(*pointer1) //=> 10 //read var1 through the pointer.
*pointer1 = 20 //change the value of var1 through the pointer.
fmt.Println(num) //=> 20 //the new value of num
}
Structs
The concept of structs
in Go is kind of similar to the concept of classes
in object-oriented programming languages. A struct
is a typed collection of fields. They’re useful for grouping data together to form records.
How to declare a struct
You can declare a struct
using the type
and struct
keywords.
package main
import "fmt"
type person struct {
firstName string
age int
}
In the example above, person
struct contains firstName
and age
as its fields
.
Different ways of Struct Instantiation
There are multiple ways to instantiate a struct
, I’ll demonstrate the following 4 methods here.
The var
keyword and Dot Notation
A struct
uses a .
to access the values stored in fields.
package main
import "fmt"
type person struct {
firstName string
age int
}
func main() {
var mike person
mike.firstName = "Mike"
mike.age = 20
fmt.Println(mike.firstName, mike.age) //=> Mike 20
}
The var
keyword and :=
operator
The following 2 sets of code illustrates struct instantiation using var
and :=
.
package main
import "fmt"
type person struct {
firstName string
age int
}
func main() {
bob := person{ "Bob", 30 }
fmt.Println(bob.firstName, bob.age) //=> Bob 30
}
You can also explicitly refer to a field and assign a value to it.
package main
import "fmt"
type person struct {
firstName string
age int
}
func main() {
sam := person{ age: 40, firstName: "Sam" }
fmt.Println(sam.firstName, sam.age) //=> Sam 40
}
Using the new
keyword
The following code shows struct instantiation using the new
keyword.
package main
import "fmt"
type person struct {
firstName string
age int
}
func main() {
jen := new(person)
jen.firstName = "Jennifer"
jen.age = 10
fmt.Println(jen.firstName, jen.age) //=> Jennifer 10
}
Structs and Pointers
Struct fields
can be accessed through a struct pointer.
package main
import "fmt"
type person struct {
firstName string
age int
}
func main() {
var mike person
sPointer := &mike
sPointer.firstName = "Mike"
sPointer.age = 20
fmt.Println(mike) //=> {Mike 20}
}
Methods
As I mentioned earlier, Go does not have classes
. However, you can define methods on struct types.
A method
is a function with a special receiver
argument.
How to define a method
You can declare a method like the following code snippet.
func(receiver receiverType) nameOfFunction(arg) typeOfReturnValue {
//content of the function
}
In the example below, intro
method has a receiver
of type person
named named onePerson
.
package main
import "fmt"
type person struct {
firstName string
age int
}
func(onePerson person) intro(arg string) string {
return "Hello I'm" + " " + onePerson.firstName + " " + arg
}
func main() {
mike := person{ "Mike", 20 }
fmt.Println(mike.intro("!")) //=>Hello I'm Mike !
}
Pointer receivers
You can declare methods
with pointer receivers. This means the receiverType
has the literal syntax *Type
for some type Type
.
There are several differences between pointer receivers
and normal value reveivers
. One circumstance you should use pointer receivers
over value receivers
is when you want to change the state of the receiver
in a method.
With pointer receivers
, you can modify(read/write) the receiver
in a method just like the code below.
package main
import "fmt"
type Num struct {
x, y int
}
func (p Num) stay() {
p.x = 10
p.y = 20
}
func (p *Num) modify() {
p.x = 10
p.y = 20
}
func main() {
numOne := Num {0, 0}
numOne.stay()
fmt.Println(numOne) //=> {0 0}
numOne.modify()
fmt.Println(numOne) //=> {10 20}
}
As you can see in the code above, The method stay()
does not change the values of Num
struct. On the other hand, The method modify()
changes the values of Num
struct.
If you want to know more about the differences of pointer receivers
from value receivers
, you can check here
Type Embedding
Go does not provide the typical, type-driven notion of sub-classing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.
package main
import "fmt"
type Person struct {
firstName string
}
func (a Person) name() string{ //Person typed method
return a.firstName
}
type User struct {
Person
}
func (a User) name() string { //User typed method
return a.firstName
}
func main(){
bob := Person{"Bob"}
mike := User{}
mike.firstName = "Mike"
fmt.Println(bob.name()) //=> Bob
fmt.Println(mike.name()) //=> Mike
}