Chapter04 - pslpune/golang-jumpstart GitHub Wiki

Pointers in Go work a bit different from the pointers in C, C++. This chapter gives a breezy walkthru on what is in stores. A pointer stores the address of another variable. In that its a level of redirection from one variable to another. It would be vital to understand how pointers work before we study structs and interfaces.

Declaring pointers

Just like any other declaration but with * before the data type indicates to the compiler that we wouldnt want a variable but a pointer to a variable.

package main

import (
	"fmt"
)

func main() {
	someRnd := 255
	var ptrToRnd *int = &someRnd
    fmt.Printf("Type of ptr is %T\n", ptrToRnd)
    // if we instead of dereferencing the pointer, just spit out the value of the pointer it'd only an address to another variable.
	fmt.Println("address that it points to is", ptrToRnd)
}
Type of a is *int
address of b is 0x1040a124

Zero value of the pointer, new pointer

Zero value of a pointer is nil it denotes it hasn't got any address of a variable. Nil pointers cannot be redirected or queried for their values. They can only be compared to the nil value.

package main

import (
	"fmt"
)

func main() {
	notNilPtr  := 25
	var b *int
	if b == nil {
		fmt.Println("b is", b)
		b = &notNilPtr
		fmt.Println("b after initialization is", b)
	}
}
b is <nil>
b after initialisation is 0x1040a124

Its possible to create a pointer using the new function. Go creates a pointer to the specified type, and assigns a zero value of the type that it points to. Its intuitive to think new function would create a nil pointer of the type, but contrary to that it does create a pointer and assigns zero value of the datatype it points to.

package main

import (
	"fmt"
)

func main() {
	size := new(int)
	fmt.Printf("Size value is %d, type is %T, address is %v\n", *size, size, size)
	*size = 85
	fmt.Println("New size value is", *size)
}
Size value is 0, type is *int, address is 0x414020
New size value is 85

De-referencing a pointer

Dereferencing a pointer means accessing the value of the variable to which the pointer points. *a is the syntax to deference a.

package main
import (  
    "fmt"
)

func main() {  
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)
}

Passing a pointer to & from a function

It is perfectly legal for a function to return a pointer of a local variable. The Go compiler is intelligent enough and it will allocate this variable on the heap.The behaviour of this code is undefined in programming languages such as C and C++ as the variable i goes out of scope once the function hello returns. But in the case of Go, the compiler does an escape analysis and allocates i on the heap as the address escapes the local scope. Hence this program will work and it will print:

package main

import (
	"fmt"
)

func change(val *int) {
	*val = 55
}
func hello() *int {
	i := 5
	return &i
}
func main() {
	a := 58
	fmt.Println("value of a before function call is",a)
	b := &a
	change(b)
	fmt.Println("value of a after function call is", a)

    d := hello()
	fmt.Println("Value of d", *d)
}

Use slices instead of array pointers

When you want to make changes to items in an array its possible to send a pointer of an array to the function and get it modified. But that is not the idiomatic way of doing things in Go, use slices instead to achive the same.

package main

import (  
    "fmt"
)

func modifySlc(slc []int) {
    slc[0] = 90
}

func modify(arr *[3]int) {
	(*arr)[0] = 90
    // or its also equivalent to saying 
    // arr[0] = 90
}

func main() {  
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)

    //  now using slices 
    modifySlc(a[:])
    fmt.Println(a)
}

No pointer arithmetic in Go

Following code does not compile and would result in a compilation error.

package main

func main() {
	b := [...]int{109, 110, 111}
	p := &b
	p++
}

Go does not support pointer arithmetic which is present in other languages like C and C++.

Structs

User defined type with collection of fields is what a struct is. Varaibles when clubbed together behind a logical boundary, set them apart as independent unit.

type Employee struct {
	firstName string
	lastName  string
	age       int
}

For instance, an employee has a firstName, lastName and age. It makes sense to group these three properties into a single struct named Employee. Using this name Employee new instances of this type can be created.

Creating instances

Either by named field, or by sequence of the fields as they appear in the declaration, structs can be initialized like below. The later approach is discouraged since adding fields in the already existing sequence of fields can lead to compile errors. Please also note, you cannot have a combination of both approaches.

package main

import (
	"fmt"
)

type Employee struct {
	firstName string
	lastName  string
	age       int
	salary    int
}

func main() {

	//creating struct specifying field names
	emp1 := Employee{
		firstName: "John",
		age:       25,
		salary:    1200,
		lastName:  "Doe",
	}

	//creating struct without specifying field names
	emp2 := Employee{"Thomas", "Paul", 29, 800}

	fmt.Println("Employee 1", emp1)
	fmt.Println("Employee 2", emp2)
}

Anonymous structs

It is possible to declare structs without creating a new data type. These types of structs are called anonymous structs.

package main

import (
	"fmt"
)

func main() {
	sensor := struct {
		id string
        label string
        calibration bool
        isAnalog bool
        opVolts float32 
        maxAmp float32 
	}{
        id :"01HJSC2SAR7AK0C1KGPH0HRM43",
        label :"Pfeffer Inc",
        calibration: true,
        isAnalog: false, 
        opVolts: float32(5.0),
        maxAmp: float32(0.6),
	}

	fmt.Println("Sensor details", sensor)
}

Zero value of structs

When a struct is defined and it is not explicitly initialized with any value, the fields of the struct are assigned their zero values by default. While accessing the fields of a struct instance is as simple as using the . operator

package main

import (
	"fmt"
)

type Employee struct {
	firstName string
	lastName  string
	age       int
	salary    int
}

func main() {
	var emp4 Employee //zero valued struct
	fmt.Println("First Name:", emp4.firstName)
	fmt.Println("Last Name:", emp4.lastName)
	fmt.Println("Age:", emp4.age)
	fmt.Println("Salary:", emp4.salary)

    emp5 := Employee{
		firstName: "John",
		lastName:  "Paul",
	}
	fmt.Println("First Name:", emp5.firstName)
	fmt.Println("Last Name:", emp5.lastName)
	fmt.Println("Age:", emp5.age)
	fmt.Println("Salary:", emp5.salary)
}
First Name: 
Last Name: 
Age: 0
Salary: 0

First Name: John
Last Name: Paul
Age: 0
Salary: 0

Pointers to a struct

package main

import (
	"fmt"
)

type Employee struct {
	firstName string
	lastName  string
	age       int
	salary    int
}

func main() {
	emp8 := &Employee{
		firstName: "Sam",
		lastName:  "Anderson",
		age:       55,
		salary:    6000,
	}
	fmt.Println("First Name:", (*emp8).firstName)
	fmt.Println("Age:", (*emp8).age)

    // Only in golang can you also do this
    // instead of de referencing explicitly you can directly use the dot operator on the pointers
    fmt.Println("First Name:", emp8.firstName)
	fmt.Println("Age:", emp8.age)
}

Nested fields (Aggregation)

Fields of a struct inturn can be structs themselves. This is very much like aggregation. See how the fields of the cascaded struct are accessed.

package main

import (
	"fmt"
)

type Address struct {
	city  string
	state string
}

type Person struct {
	name    string
	age     int
	address Address
}

func main() {
	p := Person{
		name: "Naveen",
		age:  50,
		address: Address{
			city:  "Chicago",
			state: "Illinois",
		},
	}

	fmt.Println("Name:", p.name)
	fmt.Println("Age:", p.age)
	fmt.Println("City:", p.address.city)
	fmt.Println("State:", p.address.state)
}

Promoted fields

Though its tempting to call it as inheritance / derivition Its NOT!. And it shall be clear in a minute why so. Its an anonynous field and such that it does not estalish any relation between the structs

package main

import(
    "fmt"
)


type Sensor struct{
    isAnalog bool
    opVolts float32 
    opAmp float32 
}

type DHT11 struct {
    Sensor 
    Humidity float32
    Temp float32
}

func main(){
    dht := DHT11{
        Sensor: Sensor{
            isAnalog : true
            opVolts : 5.0
            opAmp: 0.6
        }
        Humidity: 0.0
        Temp: 25.5
    }

    fmt.Println("DHT11 is an analog sensor: %t", dht.isAnalog)
}
type Sensor struct{
    isAnalog bool
    opVolts float32 
    opAmp float32 
}
type Device struct{
	OpSys string 
}
type DHT11 struct {
    Sensor
	Device // this is legal and the compiler does not flag this !

    Humidity float32
    Temp float32
}

Lets assume, Go does allow struct inheritance - and that above DHT11 does indeed derive from Sensor and Device The fact that the compiler did not flag this could mean only 2 things.

  • Go allows multi-parent inheritance
  • This is not inheritance at all

Here in another example things become quite clear - Go does not support inheritance

func Calibrate(typeOfSensor string)(*Sensor, error){
	if typeOfSensor == "DHT11" {
		return &DHT11{} // compiler will flag this 
	}
}

In the above example return type expected is *Sensor while if it were inheritance passing &DHT11 shouldn't have been flagged. Base class pointers can point to child class objects

What is seemingly inheritance is not, its just a field and nothing more. Just an anonymous field and hence there is more of an aggregation relation between the structs and nothing more.

References

⚠️ **GitHub.com Fallback** ⚠️