Skip to content

Instantly share code, notes, and snippets.

@bezbrizniZmaj
Last active February 17, 2021 23:30
Illustrates the use of pointers in function calls and returns. See also the two variations of the same program: one using named functions and passing values - https://gist.github.com/bezbrizniZmaj/587abe8a835064efe262d9eda5c52964 and the other using anonymous functions within func main - https://gist.github.com/bezbrizniZmaj/9c148cc6a792d5112c7c…
// вежба 17б
// Print first n Fibonacci numbers to the console, using named functions
// and passing into or out of them the "addresses of" variables.
// This semantics is also known as 'passing the pointers'. In this way, the
// "values of" variables (the data) are SHARED between the calling and the
// called functions.
// In some other languages, it is a standard jargon to say that variables
// passed to or from functions in this way are passed "by reference", but
// in Go, the addresses of variables are treated as variables themselves
// (and they happen to be of a pointer type), and so their values are what is
// being copied and passed. (-- Also see the final note. --)
// Note: It is not a good practice, nor is it a gain, to pass pointers to the
// types int and bool; this is done here just to illustrate the concept.
package main
import (
"fmt"
)
// Declaring a custom type 'myArr', so I can use it as a shorthand for the
// type literal.
type myArr [2]uint64
//
// Defining named functions.
//
// Defining a function that RETURNS the result being of the type pointer to int.
// This function gets the positive integer value from the console and returns
// its memory address to the calling function ('main' in this example).
func input() *int {
// Declaring and instantiating the variable to be returned, as being of
// a pointer type.
var num *int = new(int)
// Note: Simple declaration: var num *int doesn't instantiate the value
// that 'num' points to and would make the value of 'num' to be 'nill'.
// The built-in 'new' function instantiates the dereferenced (unnamed)
// variable and sets its value to zero-value for the respective type
// (0 for int).
fmt.Printf("\nEnter the number of first elements of Fibonacci sequence after zero: ")
// The value that 'num' points to is updated by the fmt's 'Scanln'
// function, which, by its own semantics, takes the argument of a
// pointer type.
fmt.Scanln(num)
// A for-loop, which, in its condition, uses the current value that the
// 'num' variable points to, i.e. the scanned data (dereferencing). Only
// if condition evaluates to 'true', does the value that 'num' points to
// get updated again by scanning the new input.
for *num <= 0 {
fmt.Printf("\n\n!!! The entry should be greater than zero. Try again: ")
fmt.Scanln(num)
}
// Returning a pointer-type result. The value of the "ADDRESS of" the
// scanned data will be copied to the calling function, not those data
// themselves, but only if the calling functon asigns it to a variable
// (of the type *int) (or asigns the dereferenced data themselves to a
// variable (of the type int)).
// Those data now have to sit in the heap memory, because of this
// semantics of return, so that accessing them will be possible after
// this function executes and its memory frame on the stack gets invalid.
return num
}
// Defining a function that TAKES the argument being of the type
// pointer to myArr.
// This function takes the memory address which allows it to access an array
// of the type [2]uint64 and then updates its two elements according to the rule
// for generating the next number in the Fibonacci sequence. The argument
// (the array in this example) is thus SHARED between the calling function
// ('main' in this example) and this function.
func fibSeqGenerator(ptFib *myArr) {
// In the definition above:
// the value of the "ADDRESS of" the original array is passed into this
// function, and that value, not the current "value of" the original
// array, is copied to the local 'ptFib' variable (the parameter), which
// is of a pointer type.
// This function can now access the current values in the original array,
// to which this parameter now points to.
// Below: asigning new values to members of the original array (in this
// example: 'fib'), i.e. updating that array.
// For that, the parameter must first be dereferenced to get the value
// of the respective type (myArr in this case).
// However, dereferencing of pointers to arrays (and structs) is done
// automatically by Go compiler, so we can safely write:
last := ptFib[1]
ptFib[1] += ptFib[0]
ptFib[0] = last
//which actually means this:
// var last uint64 = (*ptFib)[1]
// (*ptFib)[1] += (*ptFib)[0]
// (*ptFib)[0] = last
// and this is also a valid, but clumsy syntax.
// There are no return variables, because the changes in the array
// are done "in place".
}
// Defining a function that TAKES the argument being of the type pointer to int.
// This function inserts a new line in the console printout of Fibonacci
// numbers, after every eight printed numbers.
func newLine(ptT *int) {
// In the definition above:
// the value of the "ADDRESS of" a variable (in this example, it is
// going to be the 'tracker' integer) is passed into this function as
// the argument, and that value, not the current "value of" that
// variable, is copied to the local 'ptT' variable (the parameter),
// which is of a pointer type.
// This function can now access the current "value of" the variable to
// which this parameter now points to ('tracker' in this example),
// and for that, the parameter must first be dereferenced to get the
// value of the respective type (the type int).
// Evaluating the condition and, only if 'true', printing a new line.
if *ptT%8 == 0 {
fmt.Println()
}
}
// Defining a function that RETURNS the result being of the type pointer to bool.
// This function prompts the user to enter his or her choice of whether the
// program needs to be repeated or exited from, and returns the user's choice
// as the memory address of the generated boolean value.
func repeat() *bool {
// Note: I am deliberately using different style here than in the
// similar function 'input', above, to show different possibilities.
// In general, the style presented here results in the more readable
// code, and expresses the code logic quite directly, so it probably
// should be preffered over declaring and creating an instance of a
// pointer type, unless the latter style makes the overal semantics
// more consistent.
//
// Declaring and instantiating the variable which memory ADDRESS is to
// be returned, as being of the boolean type. This variable is also
// intialized with zero-value for the respective type ('false' for bool).
var tf bool
// Declaring the local variable 'choice'.
var choice string
fmt.Printf("To exit, press [enter], to start again, type 'q' and press [enter]: ")
fmt.Scanln(&choice)
fmt.Println()
if choice == "q" || choice == "Q" {
// Asigning new value to the 'tf' variable (updating)
// in the branch.
tf = true
}
// Returning a pointer-type result. The value of the "ADDRESS of" the
// returned boolean value ('true' or 'false') will be copied to the
// calling function ('main' in this example), not that boolean value
// itself.
// This latter value now sits in the heap memory, because of this
// semantics of return, so that accessing it will be possible after this
// function executes and its memory frame on the stack gets ivalid.
return &tf
// In the mental model of the code, the ampersand operator should be
// read as: "shared" or "sharing", so the above statement simply means:
// returning the shared 'tf' (data) or "sharing 'tf' with the caller".
}
//End of definitions
//
func main() {
var (
fib myArr
tracker int
ptN *int
)
Start:
// Setting the initial value for the two-element array.
fib = myArr{0, 1}
// Obtaining the number of Fibonacci numbers to be generated and
// printed out, via the user's input. For this:
// Calling 'input' function, and asigning (copying) the returned pointer
// to a variable of the same pointer type (*int in this case).
ptN = input()
// This pointer variable now points to the scanned data, which had been
// allocated to the heap memory.
// Note: we might be tempted to write this code:
// var n int = *input()
// i.e. dereferencing the returned pointer, and thus getting accsses
// to the data themselves, but remember that each asignment in Go
// represents copying, so we might have, just as well, simply returned
// "the value of", not the "address of" these data. If we want to avoid
// copying of the data, we must be consistent with the types.
// Note: Go does not let us asign a new value to the the memory address
// of the existing variable, e.g. '&n', just as it doesn't allow
// arithmetic expressions using pointer-variables. Hence, we are not
// allowed to right:
// var n int
// &n = input() // Invalid code - will not compile.
// This is because, in Go, that new address would not be coupled to 'n'
// but to some other variable that holds the data at it, in this example,
// created by the function and put on the heap.
// In Go, each variable has a unique memory address at which its value
// is stored. And each memory address that holds some value belongs to a
// unique variable. That's the rule. (-- Also see the final note. --)
//
// Printing the user's input and the first number (0) on a separate line.
// The obtained memory address, now stored in the 'ptN' variable, must
// be dereferenced to get the shared value of the scanned data.
fmt.Printf("\n\nThe first %v Fibonacci numbers are:\n\n%v,\n", *ptN+1, fib[0])
// Generating the requested number of Fibonacci numbers greater than zero,
// and printing them to console, one by one, except for printing the last
// generated number.
for tracker = 1; tracker < *ptN; tracker++ {
// Calling 'fibSeqGenerator' function and passing to it a pointer-
// type argument, i.e. the memory address of the 'fib' array,
// which points to the current, but also any future values in
// that array.
fibSeqGenerator(&fib)
// In the mental model: "sharing 'fib' with the called function".
fmt.Printf("%v, ", fib[0])
// Printing a new line when needed. For this:
// Calling 'newLine' function and passing to it a pointer-type
// argument, i.e. the memory address of the 'tracker' integer,
// which points to the current, but also any future value of
// that integer.
newLine(&tracker)
}
// Printing the last generated number.
fmt.Printf("%v\n\n", fib[1])
// Getting the user's input and accordingly branching the program to
// start over or terminate. For this:
// Constructing a boolean condition for an if-statement by a declaration-
// and-asignment statement, in which we call 'repeat' function and asign
// the returned memory address to a pointer variable.
var ptR *bool = repeat()
// Then use the boolean value to which this variable points to
// (dereferencing) - as the condition.
if *ptR {
goto Start
}
// The local short declaration will work fine, as well:
// if ptR := repeat(); *ptR {
// goto Start
// }
// and finally, we can also abstract 'ptR' completely, and evaluate the
// dereferenced function result as the condition itself:
// if *repeat() {
// goto Start
// }.
}
// Note: in the mental model of the code, the '*' operator (not to be confused
// with the '*' in the pointer-type signature) should be interpreted as
// "getting the value that was shared with me (the data)".
//
// ON A FINAL NOTE: why did I put the "address of" and the "value of" some
// variable inside the quotation marks? Each variable, e.g.'x', has its value
// (the data) that is stored somewhere in the memory, starting at the certain
// address. The value of that address is written somewhere else in the memory,
// namely, in that variable's definition (the x's 'header'). The field within
// x's header in which that memory address is written - is here being aliased
// as the "address of".
// The content of that field represents the value of another variable coupled to
// x and aliased as '&x'. That sister variable is by definition of a pointer type.
// We can understand that, just as x, the &x, conceptually at least, has its own
// definition field, in which the memory address of the x's header is written -
// it thus points to x. In the compiled program, it is this memory address that
// replaces 'x'.
// If we make a new pointer variable 'p' and copy &x to it, we are copying the
// value that is written in the "address of" field of the x's header. That copy
// will be somewhere in the memory and the address of that place will be written
// in the p's header. Therefore, &x and p are different variables with their
// values being in different places in the memory, but these two values are the
// same, and so &x and p both point to the same data - the x's value.
// For the consistency, but also because there are, as we just saw, different
// kinds of values associated with 'x', which have different meanings, I also put
// the "value of" inside the quotation marks, to signify the x's data that the
// "address of" field points to. This is just the regular algebraic meaning of
// 'value of x.'
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment