This post is an attempt at teaching programming to people who are almost entirely unfamiliar with it, I think the basic concepts aren’t too difficult but that may also be biased against my exposure to it.
I want to emphasize that programming is a skill where you’ll always be learning and always have challenges. A lot of problems can be unclear on where to even begin but try not to be deterred by this, it’s part of the process and when you’re working on difficult problems you will be thinking a lot more than writing code and that is perfectly normal.
Fundamental Concepts #
Introduction #
The idea of programming is essentially a set of instructions for the computer to follow, the instructions themselves are fairly basic but due to the nature of programming the scale of the quantity of instructions can be immense and that is where the complexity comes from.
To program is to imagine a system built with instructions given to the computer that will perform the task you would like to accomplish. It can be challenging, but the upside to this is you can often get direct feedback on if your program functions as intended or not, so experimentation and rapid iteration is a good way to get comfortable and to learn.
To program you’ll need to become familiar with a programming language, this is your interface to instructing the computer. Programming languages can differ in some ways but a lot of them share a lot of similarities, so once you’re comfortable with one you can often move to others without many issues.
I’ll start with going through some of the base concepts of programming languages and try to tie it together with an example at the end to hopefully give a good starting point. The language in use will be the Go language as I feel it is quite approachable and also has access to good features so you’re exposed to certain concepts that are good to be aware of.
Don’t worry if you dont understand everything right away, some things are important for context but this can be used as a reference later on so I wouldn’t stress about getting everything right away.
Compiling a program #
A program is typically compiled from code using a compiler, the process of “compiling” a program is typically about converting what is written into instructions that a CPU can directly interpret, it also often includes things like “optimizations” which would be noticing patterns in code that you’ve written and removing unneccessary parts from it to achieve the same effect with less instructions, thus making it faster and more efficient.
There is a distinction between things that happen at “compile time” compared to things that happen at “run time”, which is things that are figured out as the program is compiled and things that are figured out by the program as it runs, with the former being preferred as it means less code will be run on the computer when the program is actually run.
Some languages are not compiled but are instead interpreted which involves figuring out how to run the code as it needs to be run but I will not explain the details about this as it’s not needed to know right now and would probably make this even more confusing.
Don’t stress about understanding this right away, this is just here to give some context and you can safely ignore this until you’re more familiar with things later on.
Variables & Types #
One of the most fundamental parts of programming is variables, they hold data and data is often what drives the program and also the end result of what your program is trying to accomplish.
Before getting into variables though, you need to be aware of data types. Data types are all represented as binary data, they typically vary in size from 8 bits (1 byte) to 64 bits (8 bytes), while they’re all represented as binary data they will be interpreted differently.
In Go, there are multiple ways to declare variables:
var numberValueOne int32 // Declare a variable named "numberValueOne" of type "int32"
numberValueTwo := 55 // Declare a variable named numberValueTwo with an implied type of "int"
The types will be specified below, but knowing how these are declared will help with some upcoming examples.
There are a few fundamental types you will often see in programming:
Comments #
Anything after // in a line or between /* and */ across multiple lines will be classified as a “comment”, it’s basically removed from the program when it’s compiled and can be used to make notes throughout a program, I’ll be using these a bit throughout the examples.
// This is a comment
/*
Anything between these two markers is also a comment, it is used for multiple lines
*/
Integers #
Integers are basically whole numbers and they can be “signed” (can represent negative numbers) or “unsigned” (can only represent positive numbers). Basic arithmetic can be done with integers e.g. 5 + 5 will result in the value 10, when anything that could possibly result in a partial number such as division it would only result in a whole number instead with the fractional component discarded, e.g. 5 / 2 will result in 2.
In a simple explanation, integers are represented in binary as a power of two number system, with each value being represented by either 1 or 0 and being read from right to left with the first value being 1 and every value after that doubling the previous value.
e.g.
0001 = 1
0010 = 2
0100 = 4
1000 = 8
1111 = 1 + 2 + 4 + 8 = 15
With signed integers, which can be negative, the left most value for the range of bits is a negative instead of a positive. If we were to have a 4 bit integer then the above would be something like this:
0001 = 1
0010 = 2
0100 = 4
1000 = -8
1111 = 1 + 2 + 4 + -8 = -1
These details aren’t super important to memorize for the moment, but the details are here for context.
Integer types in Go are listed below which are just signed/unsigned versions of 8-64 bit integers, it will show the range as well as signed numbers will not be able to represent a higher number due to roughly half of their range covering negative numbers.
uint // generally 64 bits but sometimes 32, depending on the computer
uint8 // the set of all unsigned 8-bit integers (0 to 255)
uint16 // the set of all unsigned 16-bit integers (0 to 65535)
uint32 // the set of all unsigned 32-bit integers (0 to 4294967295)
uint64 // the set of all unsigned 64-bit integers (0 to 18446744073709551615)
byte // an alias of uint8, functions identically to it
int // generally 64 bits but sometimes 32, depending on the computer
int8 // the set of all signed 8-bit integers (-128 to 127)
int16 // the set of all signed 16-bit integers (-32768 to 32767)
int32 // the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64 // the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
Floating Point Numbers #
Floating point numbers are able to represent decimal numbers, floating point numbers are typically 32 or 64 bits with “float” usually referring to 32 bit floating point numbers (or single precision) and “double” referring to 64 bit floating point numbers (or double precision).
I won’t go into the full details of how floating point numbers are represented, but a key component of how floating point numbers work in hardware is that they can only be so specific. An example is if you were doing calculations and you tried to compare a variable holding a floating point number with the value 0.1, this will not work as the number will not be exactly 0.1 but instead 0.100000001490116119384765625. There are other methods that can be used to compare them though but I will leave it aside for the moment, but it’s good to keep this quality of floating point numbers in mind.
The types in Go are as follows,
float32
float64
I would also recommend checking out this site if you would like to see how the numbers are represented, though it isn’t important to understand how they work exactly for the moment.
Booleans #
Booleans are essentially just to hold the values true or false, with true equating to the value 1 and false equating to the value 0. These are used for conditional checks generally to decide if some code will be run or not, in most languages they are also typically 32 bits, this is generally due to performance being better compared to anything smaller despite the size not being needed.
There is only one boolean type in Go.
bool
Arrays #
Arrays aren’t a type in and of themselves but an array of a type such as an int32 will be a set of int32 values together, placed directly after each other in memory. Arrays are typically represented as square braces with a number in the middle representing the size of the array such as,
var intArray [10]int32 // an array of 10 int32 variables, this will be a size of 320 bits or alternatively 40 bytes.
To access intArray you will have to use the same square brackets to specify which of the 10 values you will access, the “index” you access of the array will start at 0 and the last index will be one less than the length which in this case will be 9.
intArray[0] = 1 // Set index 0's value to be 1
intArray[1] = 2 // Set index 1's value to be 2
Strings #
Strings are sets of characters that are typically used to represent human readable data, behind the scenes they are essentially just arrays of data typically being uint8 values which are single bytes. In Go you can also access a string by index like arrays shown above.
name := "This is a string" // declare a string variable
name[1] // accesses the byte 'a' which is a number value that when converted to represent a character will be 'a'
Characters #
Characters, otherwise known as a char are also synonymous with byte or uint8. This type has importance as it’s typically used to build strings through a uint8/byte/char array.
One thing to note is these types in languages will let you represent the value through a character literal,
var character byte = 'a' // equal to the value 97
character := 97 // identical to the above line
The reason it has a number value is because it’s the value of the a character in ASCII, you can use this for some useful things that I can get into a bit later after introducing some other concepts.
The site here is useful for looking up the ASCII table, something you can notice is A to Z are the values 65 to 90 and a to z are 97 to 122, so anything within those two ranges is going to be an alphabet character and 0 to 9 are the range 48 to 57 so anything within that range will be a numeric character. You can also subtract 32 from a lowercase character to make it uppercase and add 32 to an uppercase character to make it lowercase, these things may not be necessary to know as languages give you more comprehensive tools to achieve things like this outside of the characters a to z, but it can be useful to know in certain cases.
Arithmetic Operators #
Arithmetic operators are just the typical math operators which can be used on integer/floating point types:
3 + 2 // addition
3 - 2 // subtraction
3 * 2 // multiplication
3 / 2 // division
3 % 2 // modulo (remainder after division, in this case it would be 1 as 2 can be divided once from 3
// and the remainder after that would be 1, if it was 4 % 2 then the remainder would be 0 as it can divide evenly)
These can also be used with assignments as shorthand operators, this will basically include the variable itself in the equation on the right hand side of the operator:
There are also increment and decrement operators, these are useful in cases where you just need to adjust a value by one.
value := 0 // int value set to 0
value++ // post-increment, will increment the value while "returning" the value pre-increment
++value // pre-increment, will increment the value while "returning" the value post-increment
value-- // post-decrement, same as above but removes 1 instead of adding it
--value // pre-decrement, same as above but removes 1 instead of adding it
A few simple examples of how they can be used below.
value := 0
secondValue := value++ // secondValue will equal 0 while value equals 1
thirdValue := --value // value is decremented to 0 again and its value is returned to thirdValue, which is also 0 now
value++ // they can also be used without assignments, this will just increment value by 1
value-- // same for decrementing
var value int = 10
value += 5
// is equivalent to
value = value + 5
Both of the above would add 5 to the value, resulting in it first being 15 then becoming 20
Functions #
After introducing basic types I’m now able to introduce functions. Functions are basically blocks of code that can be called from within the program, starting off with the first function you will declare being the main function.
One thing to keep in mind is that code needs to be in functions, for simplicity I won’t always show code within functions but remember to keep this in mind.
A program can only have one main function and it is where the program will start running, in go it will look like this:
func main() {
// Code will go here...
}
And another function to demonstrate some other features would look like this:
func add(x int, y int) int {
return x + y
}
func main() {
addedValue := add(5, 10) // addedValue will be an "int" with the value "15"
}
In Go, function declarations are started with the keyword func followed by the function name which in this case is add. This is followed by parameters enclosed in parenthesis () which can optionally contain “parameters” which is data passed into the function that can then be used within the function, in this case x int, y int declares that it will have an input of two int variables. After the parenthesis close there’s an int which is the return type, which is data that the function will then have to return via the return keyword. After the return type there’s an opening brace { which on the other side of the function is closed by a closing brace }, these are used to group code, they’re mandatory in this case but can be used optionally in some other cases which I will demonstrate later on. Finally, within the function itself there’s return x + y which will return the value of x + y. The value is returned and stored in the variable addedValue, as it’s using the short assignment operator :=, it has an implicit type of int as that is the return type from the function used.
Hopefully this isn’t too overwhelming, but there are quite a few concepts at work.
Comparison operators #
Comparison operators are ways to compare values which will be useful shortly, but essentially they’re just checks between values that return a boolean value which is either true or false:
// equal
1 == 1 // returns true
1 == 2 // returns false
// not equal
1 != 1 // returns false
1 != 2 // returns true
// less than
1 < 1 // returns false
1 < 2 // returns true
// less than or equal to
1 <= 0 // returns false
1 <= 1 // returns true
1 <= 2 // returns true
// greater than
1 > 0 // returns true
1 > 1 // returns false
// greater than or equal to
1 >= 0 // returns true
1 >= 1 // returns true
1 >= 2 // returns false
A way to remember the direction of greater than or less than that you may have learned in school is the open side would point to the larger number if the condition returns true, the left side is the original operator which is what the “less than” or “greater than” is based off of, “less than” looks for the left side operator to be less than the right, and vice versa for “greater than”.
Logical Operators #
Logical operators can be combined with comparison operators to make more complicated conditional checks:
|| // or
&& // and
! // not
They work as they sound, or can be one condition or another, and will be both conditions and not will be a condition not matching, here are some examples:
1 == 2 || 1 == 1 // This would be true as 1 == 1 matches, as long as one of the conditions matches it is a true result
1 == 2 && 1 == 1 // This would return false as both conditions need to be true, the first is false so it would overall become false
(1 < 2 || 1 > 2) && 1 == 1 // These can also be combined. 1 == 1 is true and using parenthesis you can specify the boundaries for a specific comparison, so whats outside of the parenthesis will be compared against the result of whats inside the parenthesis
// (1 < 2 || 1 > 2) would become (true || false), which would become true as at least one of the values in an or comparison is true
// it then becomes true && 1 == 1, which is also true, so it would become true && true which becomes true as both values are true.
1 == 1 || 1 == 2 || 1 == 3 // Logical operators don't have a limit on how many can be chained together
Once again, hopefully this isn’t too overwhelming, don’t stress if you can’t grasp it right away.
Control Flow #
Control flow is how you dictate what code gets run in a program, as you want to control which code gets run to make a functional program.
If Statements #
The first control flow statement i’ll be introducing is if, it works as it sounds, if a condition is met then the code after it will be run.
var value int = 5
if value == 10 { // the value here is false so this branch won't be taken
value += 5 // this doesn't get run here so value will remain as 5
}
value += 5 // value becomes 10
if value == 10 { // the value here is true so it will branch and run the code in the block
value += 5 // value now becomes 15
}
Any comparison operators can be used for if statements, you can also chain multiple together as shown with logical operators above.
Else #
A second control flow statement that is used in conjunction with if is else. else can be used right after an if block to basically catch anything that doesn’t match the if condition.
var value int = 6
if value / 3 == 2 { // if value divided by 3 is equal to 2
value *= 2 // multiply value by 2
} else { // if it doesn't equal to 2
value *= 3 // multiply value by 3
}
else can also be paired with if, so if an earlier condition isn’t met then it will check further conditions
var value int = 10
if value > 15 { // value isn't greater than 15 so the first block will not be run
value += 1
} else if value > 5 { // value is greater than 5 so it will run this second block
value += 2
} else { // since an earlier condition was met any further blocks will not be checked or run
value += 3
}
Switch #
switch statements are a lot like if/if else/else chains, you call switch followed by a variable and give it the different conditions to match on with each condition being a possible value for it.
var character byte = 'a'
switch character {
case 'a':
character = 'b' // as character is `a` it will change character to `b`
break // break will break from the switch statement and continue any code after it
case 'b':
character = 'a'
break
default:
break
}
case followed by a value will add a condition to match on and break will exit the switch statement as mentioned in the comment. The default case is also what is matched when none of the previous conditions match, in Go these are not mandatory but in some languages they are considered necessary.
The above code can also be written as:
var character byte = 'a'
switch character {
case 'a':
character = 'b'
case 'b':
character = 'a'
}
Go has implicit break statements so they’re not necessary to include, a feature of switch statements is the ability to fallthrough to the next case, in Go these are explicit meaning if you want to fallthrough to the next condition you will need to use the fallthrough keyword.
var character byte = 'a'
switch character {
case 'a':
character += 1 // character will become 'b'
fallthrough // it will fallthrough to the 'b' case, this happens regardless of the check of `case 'c'`
case 'c':
character += 1 // character will become 'c', then it will break from the switch statement
}
There are some other ways to use switch statements in Go but I will leave them out as this is the typical usage across languages.
Loops #
Loops are a way of repeating a set of code, this can be useful with arrays as you can work through a set of data and perform an action on everything in it.
In go there’s only for loops but there are a few variations of it, I’ll only be going through the basic for loop that is also present in many other languages.
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
// sum is now 45
I’ll break down each part of it here,
- the first line just declares the
sumvariable fordeclares a loop, which is followed by several partsi := 0declared before the first semicolon is the code thats run before the loop starts, this is only ever run once and is usually used to declare the variable used to track the loop.i < 10before the second semicolon is the condition for the loop to continue, if this condition is true at the end of an iteration of a loop it will run another iterationi++after the parenthesis is the code that will run after an iteration of the loop has completed,sum += iwithin the braces is the code that will be run each iteration of the loop.
Altogether this loop will declare a variable i which is set to 0, every time the loop is run it will add the current value of i to sum. When a loop has finished it will increment i by 1, the condition i < 10 means that this loop will run 10 times, as it runs an iteration with the loop at 0 and then increments by 1 at the end of each iteration, once i is equal to 10 it will no longer continue the loop and will run any code after it.
There’s a few other ways to control how loops will run, the first I’ll be going through is the continue statement. continue basically means to stop the current iteration of the loop and go to straight to the end of it, either continuing into the next iteration or exiting the loop if the condition is no longer met.
sum := 0
for i := 0; i < 10; i++ {
if i % 2 == 0 {
continue
}
sum += i // i at this point can only be the values 1, 3, 5, 7, 9
}
// sum will be the value 25 once exiting the loop
In the above case, if i is an even number (as checked by the modulo result being 0) then sum will not be incremented by i.
The next way to control loops I’ll be showing will be the break statement. break is used for when you no longer want to continue the loop and end it at its current point.
arr := []int{22, 54, 29, 74, 123, 539}
found := 0;
for i := 0; i < len(arr); i++ {
if arr[i] > 100 {
found = arr[i]
break;
}
}
// found is now 123
This example is a little bit more complicated but also something you could reasonably see in a real program. I’ll run through it again,
arris declared as an int array literal, literal in the programming meaning is a declared value essentially rather than something that is generated when the program runs (its value is known when the program is compiled is another way of putting it).foundis declared as anintwith the value0- a loop is declared as in the previous example but with a difference, being that the condition is
i < len(arr), which isiless than the length of the array, which in this case is6, each iteration of the loop will increment by one as per thei++statement. - for the
ifstatement there’s a condition ofarr[i] > 100, as shown previously with arrays this is indexing the array.istarts at0and increments by1every time it loops,0will access the first element of the array,1will access the 2nd element, etc. This condition is checking if each element of the array is greater than100 - If an element of the array is greater than
100then it will store that value infoundand usebreak, which will exit the loop and do no further iterations.
Structs #
A struct is essentially a set of data packed together into one larger variable, with it containing other variables within it.
type Rectangle struct {
Width int
Height int
}
The above struct is named Rectangle and has the members (variables stored within a struct) Width and Height which are both int types. You can access struct members through the dot . accessor.
A struct can be used as shown below,
var rect1 Rectangle
rect1.Width = 50
rect1.Height = 100
rect2 := Rectangle{Width: 50, Height: 100}
The above ways of declaring a struct are identical, the 2nd one is more concise and is usually preferred.
Structs can also be used in arrays as per regular types.
rects := []Rectangle{
{Width: 20, Height: 40},
{Width: 50, Height: 60},
}
rects[0].Width = rects[1].Height
// Rectangle at index 0 now has a width of 60
Structs are useful as a way of keeping related data together, and in Go you’re able to compare structs for equality or inequality but you can’t do things like compare if a struct is “greater than” another struct due to that not having any clear method of being determined.
rects[0] == rects[1] // Would equal false using the above Rectangle structs
rects[0] > rects[1] // Invalid comparison, doesn't compile
Structs can also be nested within each other,
type Point struct {
x int
y int
}
type Rectangle struct {
point1 Point
point2 Point
}
rect := Rectangle{
point1: {x: 10, y: 5},
point2: {x: 30, y: 50},
}
rect.point1.x = 20 // They can be accessed in the same way as regular members but through multiple accesses.
Structs can also be passed in as parameters to functions and returned as values.
type Rectangle struct {
Width int
Height int
}
func CreateRectangle(width int, height int) Rectangle {
return Rectangle{Width: width, Height: height}
}
func DoubleSize(rect Rectangle) Rectangle {
return Rectangle{Width: rect.Width * 2, Height: rect.Height * 2}
}
rect := CreateRectangle(50, 100) // rect is now Rectangle
rect = DoubleSize(rect) // rect has been passed in and the variable has been overwritten with a new value
// rect now has a width of 100 and a height of 200
// also notice how there is no colon before the assignment, the variable is reused with the new return value
Pointers #
Pointers are often seen as a bit complicated but I think they don’t have to be, I’ll try my best to keep this simple though.
The idea of a pointer is basically that it “points” to a memory address that holds a variable, with this you get more control over modifying things elsewhere instead of having to copy data around.
Here’s a basic example of making and using a pointer,
var number int = 5 // An int variable with the value 5
var ptr *int = &value // A pointer to an int variable, its pointer is set to "point" to the variable "number"
*ptr += 1 // The asterisk is used to "dereference" the pointer, essentially this will add one to the value in the variable "number"
// number is now equal to 6
I’ll clarify a few things, using an asterisk * when specifying a type will specify that it is a pointer to that type, in the above case *int is a pointer to an int value.
The pointer value itself is basically a number that specifies a memory address, so if you were to try and do ptr += 1 in the Go language you will get an invalid error as this is trying to add 1 to the pointer address which isn’t valid in Go but could be valid in other languages but for simplicity I won’t go into this for now.
The address operator which is the ampersand & will get the address of a variable, it’s used to get the address of the variable to store in the pointer.
The dereference operator which is also an asterisk *, when used on a pointer you will be able to access the value of the variable that the pointer points to rather than the pointer address itself, in the above example *ptr += 1 is adding one to the number variable which is pointed to.
One thing to be aware of is that it isn’t valid to try and dereference a pointer that points to a NULL (known as nil in Go) or invalid value (something that isn’t valid memory), basically all that means is that you should make sure a pointer points to something. You can use conditions like ptr != nil to check agaisnt this.
Hopefully that wasn’t too confusing, that is most of what you need to know about pointers, next I’ll show some uses of this and some differences between non pointer values in some cases.
func AddToValue(value int) { // Function that adds to a variable passed by value
value += 1
}
func AddToPtr(ptr *int) { // Function that adds to a variable passed as a pointer, sometimes known as "passing by reference"
*value += 1
}
var number int = 5
AddToValue(number) // Since the variable number is passed by value, it will make a copy of this variable and provide it to the `AddToValue` function,
// this means any changes to `number` are not reflected in the variable as its copy is distinct from it
AddToPtr(&number) // However, passing it as a pointer will give the function direct access to the variable itself
// and any modifications made to the variable being pointed to by the pointer will reflect externally
// After passing it to `AddToPtr` the variable `number` is now equal to `6`
In a different case that might go against what you expect, you can include pointer types in structs which can have some differences.
type PointerContainer struct {
ptr *int
}
func AddToPtr(container PointerContainer) {
*container.ptr += 1
}
var number int = 5
var container PointerContainer = {ptr: &number}
AddToPtr(container) // Notice that we're passing the struct by value
// But afterwards, the number pointer that we passed in and modified the value to
// would now be `6` instead of `5` even though we passed the struct by value
This interaction above might be a bit confusing but putting it simply, even though we passed the struct by value the pointer itself was still passed in so it was free to be modified.
If we were to modify the pointer inside the struct passed in by value the pointer would still point to number outside of that function call though, this is because a copy of the struct, including the pointer, was passed to the function.
Structs can also be passed in as a pointer, this allows you to modify its members from within a function.
type Rectangle struct {
Width int
Height int
}
func DoubleByValue(rect Rectangle) {
rect.Width *= 2
rect.Height *= 2
}
func Double(rect *Rectangle) {
rect.Width *= 2 // Pointers to structs are automatically dereferenced in Go,
rect.Height *= 2 // so you don't need to dereference when accessing a struct member through a pointer
}
rect := Rectangle{Width: 50, Height: 50}
DoubleByValue(rect) // rect will be passed in by value,
// with changes only happening to the copy passed in to the function, with the rect variable unmodified
Double(&rect) // rect will be passed in by pointer
// And after this point it now has the values of Width: 100 and Height: 100
As stated above, the struct will be automatically dereferenced when accessing one of its member variables, if you were to access a pointer variable within the struct it will need to be dereferenced as the above example with PointerContainer. Accessing a struct pointer within another struct pointer is the same without needing dereferences.
Imports/Packages #
This will differ quite a bit between languages so I will specify how it works in Go and if you get far enough to use a different language you can learn how to use it elsewhere.
In programming languages they will typically offer a “standard library”, which is a set of functions built in for general utility so you can get things done without implementing it yourself.
I’ll go through how it works in Go and show an example using the “fmt” package, which is used to format strings and also print to the console.
In Go every file needs to have a package declaration at the top of it, the package declaration must have an identical identifier across every .go file in a directory.
Packages in Go are shared across folders, so keep in mind that every file within a folder will have everything in its scope, if there’s nested folders with more .go files you will also have to access it much like folder paths.
package main
As shown above, typically the top level package with the main function will have the package main.
To import packages you just need to include the package name.
import "main"
import "main/utility" // Accesses the utility package thats in a folder within the same folder as the `main` package
You can also use parenthesis after the import to include multiple import declarations.
import (
"main"
"fmt"
)
In Go any type or function starting with an uppercase letter will be accessible externally to the package, the same thing goes for struct members. For simplicity I would specify everything to be public (I typically program with nothing made private even now so it isn’t a bad way of doing things).
To access a function within a package you can access it much like you would access a struct member.
import "fmt"
fmt.Println("Hello, World!")
Above I accessed the function Println within the package fmt, this function prints a line to the console and above I printed the infamous “Hello, World!”.
Scope #
The final thing to mention to put everything together is the idea of scopes.
A “scope” in programming is basically the accessibility of a variable and in some cases this also determines how long a variable will be available for.
To start with, a program will always have a global scope, this allows you to declare variables that exist outside of functions, these variables will be active for the entire duration of your program and can be accessed from anywhere, though in the case of Go they are only accessible if the variable is in the same package or it’s publicly visible from another package.
package main
import "fmt"
var value uint = 5000
func main() {
value += 1
fmt.Println(value) // this will print 5001 to the console
}
As you can see above, you’re able to access the variable and use it freely.
Another thing for scope is basically whenever braces {} come into play, for functions, if/else if/else blocks, switch cases and even just placing open braces within a function will create a “scope”.
Within any scope everything declared within it is accessible, but anything above that scope as well is also accessible.
func main() {
var1 := 1
if var1 > 5 {
var2 := 2 // var2 is accessible within here
var1 += var2 // var1 is also accessible within this scope
} // this is where the scope of this if block ends, var2 is no longer accessible after this point
switch var1 {
case 1: // if var1 matches case 1 then a scope would start here
var2 := 2 // normally you cant declare a variable name twice, but as var2 no longer exists you're able to declare it again
var1 *= var2 // var1 and var2 are once again available in here
case 3: // if var1 matches case 3 then a scope would start here
var3 := 3 // var3 is available here, var2 is not as it has not been declared or if it has then it's gone out of scope
var1 -= var3 // again, var1 is available in this scope
} // the scope for case 3 ends here
var2 := 0 // var2 has been declared in the highest level scope
{
var3 := 2 // var3 now accessible
var4 := 3 // var4 now accessible
var2 = var3 + var4
} // After this point var3 and var4 will no longer exist
fmt.Println(var1 + var2) // var1 and var2 are accessible, adding them together and printing the result will print 7 to the screen
}
This one I think should be pretty straight forward to follow.
Memory Allocations #
In programming you need to allocate memory, in Go the memory allocations are handled by a Garbage Collector, this term just means that you don’t need to worry about “free"ing memory, which is the process of releasing memory back to the computer that has once been allocated.
There are two cases for allocating memory, the first one I’ll cover is allocating an individual struct or data type
type Rectangle struct {
Width int
Height int
}
func CreateRectangle(width int, height int) *Rectangle {
var rect *Rectangle = new(Rectangle) // This allocates a Rectangle and returns a pointer to it
rect.Width = width // width and height can be set on the value behind the pointer
rect.Height = height
return rect // The pointer is returned
}
rectPtr := CreateRectangle(50, 100) // rectPtr is now a *Rectangle type (or Rectangle pointer)
In a language like Go this might not be all that common of a thing to do, but it’s good to be aware of it
The other case for memory allocations is arrays (a slight variation of arrays is commonly used in Go known as slices, I’ll leave this out for now but you can think of them as more flexible versions of arrays).
If you need to create an array but you don’t know how long it’ll need to be until your program is running you will need to allocate an array at run time using the make built in function, the first parameter it accepts is a type array e.g. []int, the second parameter is the length of the array, and an optional 3rd parameter is the capacity. You can think of the length as how many elements you’ve set in it, and the capacity as how many elements it can hold before it needs to reallocate memory to expand the array, the len() function takes an array and returns a length, the cap() function takes an array and returns the capacity. The append() function is used to add a value to an array, the first parameter it accepts is the array and it can optionally take any amount of other parameters of base type of the array to add to the array and it returns the updated array.
arr := make([]int, 0, 5) // An array with no elements but the capacity to hold 5
arr = append(arr, 1) // the array now has a length of 1 which would look like [1]
arr = append(arr, 2, 3) // the array now has a length of 3 as two elements got added, it would look like [1, 2, 3]
arr = append(arr, 20, 30, 40, 50) // the array now has a length of 7 and a capacity of 10, it has been doubled as Go would just double the capacity in this case, but depending on the size it may not be double
fmt.Println(arr[0]) // Prints 1
fmt.Println(arr[5]) // Prints 40
You’re able to pass these arrays around freely, though one caveat is when passing an array around to function as a parameter, if you’re not passing it in as a pointer then any changes to the length/capacity will not be reflected externally but changes to any data in the pointer would be changed.
func AppendPtr(arr *[]int) {
*arr = append(*arr, 1, 2, 3)
}
func AppendValue(arr []int) {
arr = append(arr, 1, 2, 3)
}
func SetFirstPtr(arr *[]int) {
(*arr)[0] = 555 // To index an array pointer you will need to wrap it in parenthesis and derefernce it, then index the wrapped value
}
func SetFirstValue(arr []int) {
arr[0] = 1111
}
func main() {
arr := make([]int, 0) // this allocates an empty array, though prefer to specify a capacity where possible
AppendPtr(&arr) // array now looks like [1, 2, 3] as it was modified within the function as it had access to the slice pointer
AppendValue(arr) // array still looks like [1, 2, 3] from what you're able to access,
// but there is a separate pointer that has been created to a different array that looks like [1, 2, 3, 1, 2, 3]
// however, this isn't visible externally and the Garbage Collector will clean this up at some point
SetFirstPtr(&arr) // the first value at index [0] will now equal 555 as set in the function above
SetFirstValue(arr) // the first value at index [0] will now equal 1111 as set in the function above
// this might be unexpected, but as shown in the pointer section previously,
// when passing in a pointer by value via a struct (which an array/slice is behind the scenes)
// the values in that pointer can be modified
}
Putting Everything Together #
TODO: Will make an example later on
Where To Go Next #
As for where to go next, it depends on how well any of this clicked with you.
If you don’t yet feel comfortable diving in to programming I would suggest the Scratch programming language, it’s made for children to learn programming but don’t view this negatively as it’s designed to be an introduction and it has a more intuitive structure with dragging blocks around to make programs that are much more visual than beginner programming would be, it’s a great way to learn the foundational concepts of programming.
Alternatively you can go to Exercism’s Go language course, it will introduce different language concepts to you one by one and it can be a great incremental way to learn programming or to learn a new language, it helped me quite a lot early on. I would mostly suggest languages on the site that have the “learning mode” label and only going through the lessons shown in the “learn” tab. Anything beyond that often turns into something like programming challenges which can be quite difficult early on, these can be frustrating if you’re not very familiar with programming and could deter you from learning further, but not being able to tackle these yet doesn’t mean you can never tackle them, you just need to keep learning and work your way up which will definitely come as you get more familiar with programming.
If you’ve gotten through Exercism and are comfortable programming I would just try working on what interests you, it’s the easiest way to keep going and learn more about programming, trying to create something you want to make like a game is usually a very appealing idea for me.
I also want to say again, don’t get deterred by not knowing how to do certain things in programming or getting stuck when you’re uncertain about what to do, sometimes you just need to think about things and that is prefectly fine. There will always be difficult tasks in programming but the difference is over time you will be able to handle more and more complicated programming tasks, I often had things that seemed borderline impossible but later on they now seem trivial.
Good luck and remember to have fun with computers.