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.
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 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 = ¬NilPtr
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
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)
}
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)
}
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)
}
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++.
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.
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)
}
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)
}
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
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)
}
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)
}
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.