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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// вежба 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