Go language is a programming language initially developed at Google in the year 2007 by Robert Griesemer, Rob Pike, and Ken Thompson. It is a statically-typed language having syntax similar to that of C. It provides garbage collection, type safety, dynamic-typing capability, many advanced built-in types such as variable length arrays and key-value maps. It also provides a rich standard library. The Go programming language was launched in November 2009 and is used in some of the Google’s production systems.

Table of Contents

Introduction

This post emphasizes on brush up of the fundamentals of the Go Programming language. It will talk about variables, pointer, structure, loops, recursion, array, slice, map etc. We assume that the reader is familiar with the syntax of the Go programming language and knows the basics of Object- Orientation.

So I won’t go over the Go programming environment installation and configuration operations. If you don’t have and don’t want to install and configure a Go programming environment, then I recommend you use the online platform The Go Playground .

First Go Program

First-Go-Program

It is tradition to discuss a HelloWorld program in the start,but which will print the phrase vim-go to the output screen. Why? Because the editor I use is vim, and I use the vim-go plugin, everything except comments is provided by the plugin template. ^_^

So let us start discussing it. This is a small program, but it contains many common features of all the Go programs.

  • The Go language uses packages to organize the associated program code together, and the main() function must be in the main package
  • The Go language uses the import statement to include third-party modules. After importing the corresponding package, the program can use the functions / variables exported by the corresponding package. We had imported “fmt” package and will be using its Println() function.
  • The main() function is the entry point of the Go program. Every function definition starts with func keyword.
  • Comments are ignored by Go compiler.There are two types of comments in Go. Single line comments which start with // symbol and ends with end of line and Multiline comment which start with /* and ends with */ anything between two *’s are ignored by compiler.

Variables & Constants

“Variables” are simply storage locations for data. For every variable, some memory is allocated. The size of this memory depends on the type of the variable.

In Go programming variables can be declared using the var keyword. Go is a statically typed programming language. Which means that variables always have type associated with it and their type cannot be changed.

Example:

func main() {
	var v1 int
	var v2 int
	v1 = 100
	fmt.Println("Value stored in variable v1:", v1)
	fmt.Println("Value stored in variable v2:", v2)
}

Output:

Value stored in variable v1: 100
Value stored in variable v2: 0

Analysis:

Two variable v1 and v2 of integer type are created. Value 100 is stored in v1. Finally, value stored in v1 and v2 is printed to screen.

There is no uninitialized variable in Go, The compiler will give the default value to the variables according to their type. For example, Integer variables are automatically initialized to 0. String variables are initialized to empty "" string, pointers are initialized to nil etc.

In Go Programming, constants are declared using const keyword. A constant must be initialized at the time of declaration, and a constant cannot be changed once it is created.

const name = "trump"

:= operator allows short declaration of variable. This does declaration and initialization of variable at once. The type of the variable is automatically determined by the compiler according to the type of value assigned.

Example:

func main() {
	president := "biden"
	fmt.Println("Mr. President:", president)
}

Output:

Mr. President: biden

Analysis:

Go automatically determine that variable president is of type strings, as string “biden” is assigned to it.

Basic Data Types

Basic data types are the data types, which are defined in the Go language.

There are eight important family of primitive data types – Integer, Boolean, Byte, Unsigned Integer, Float, String, Rune, and Complex:

Boolean

These used to store True or False. Ex: var b bool = true

Operations on Boolean Types:

Logical Operators:

  • &&: logical conjunction, “and”
  • ||: logical disjunction, “or”
  • !: logical negation

Equality and Inequality:

  • ==
  • !=

Integer

Int or Integer represent whole numbers. Integer can be negative or positive. When an integral literal is assigned to a variable. There are various type of integers depending upon the number of bytes they contain and how much big data they can contain. uint or Unsigned Integer are special type of integers, which can store only positive values.

Signed Integers:

Type Size Range
uint8 8 bits -128 to 127
int16 16 bits $-2^{15}$ to $2^{15} - 1$
int32 32 bits $-2^{31}$ to $2^{31} - 1$
int64 64 bits $-2^{63}$ to $2^{63} - 1$
int Platform dependent Platform dependent

Unsigned Integers:

Type Size Range
uint8 8 bits 0 to 255
uint16 16 bits 0 to $2^{16} - 1$
uint32 32 bits 0 to $2^{32} - 1$
uint64 64 bits 0 to $2^{64} - 1$
uint Platform dependent Platform dependent
uintptr Platform dependent Platform dependent

The int, uint, and uintptr types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. When you need an integer value you should use int unless you have a specific reason to use a sized or unsigned integer type.

In Golang, you can declare octal numbers using prefix 0 and hexadecimal numbers using the prefix 0x or 0X.

Example:

func main() {
	var myInt8 int8 = 97

	/*
	  When you don't declare any type explicitly, the type inferred is `int`
	  (The default type for integers)
	*/
	var myInt = 1200

	var myUint uint = 500

	var myHexNumber = 0xFF  // Use prefix '0x' or '0X' for declaring hexadecimal numbers
	var myOctalNumber = 034 // Use prefix '0' for declaring octal numbers

	fmt.Printf("%d, %d, %d, %#x, %#o\n", myInt8, myInt, myUint, myHexNumber, myOctalNumber)
}

Output:

97, 1200, 500, 0xff, 034

Float

Decimal point number are stored inside Floating Point variable. Go has two floating point types - float32 and float64.

  • float32 occupies 32 bits in memory and stores values in single-precision floating point format.
  • float64 occupies 64 bits in memory and stores values in double-precision floating point format.

The default type for floating point values is float64.

Operations on Numeric Types

  • Arithmetic Operators: +, -, *, /, %
  • Comparison Operators: ==, !=, <, >, <=, >=
  • Bitwise Operators: &, |, ^, <<, >>
  • Increment and Decrement Operators: ++, --
  • Assignment Operators: +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^=

Integer Type aliases

Rune: alias of uint8

Byte: alias of int32 and are used to stored Unicode characters.

Type Alias
byte uint8
rune int32

Strings

In Go, strings are sequence of Unicode characters, String are immutable. Strings in Golang are declared either using double quotes as in “vim-go” or back ticks as in `vim-go`. Double-quoted strings cannot contain newlines, and they can have escape characters like \n, \t etc. In double-quoted strings, a \n character is replaced with a newline, and a \t character is replaced with a tab space, and so on…

Strings are immutable so you cannot change its content once created. You need to first convert into a slice of rune then do the changes and in the end convert it back to string.

Example:

func main() {
	s := "vim-go"
	r := []rune(s)
	r[0] = 'V'
	s2 := string(r)
	fmt.Println(s2)
}

Output:

Vim-go

Below is table, which explain some of the operations on string.

myStr := "vim go"

Expression Value Explanation
len(myStr) 6 Used to find the number of characters in myStr
“vim” + “go” “vimgo” Two strings are concatenated into a single string
“vim” == “go”“vim” == “vim” FalseTrue Equality can be tested using “==” sign
“a” < “b”“b” < “a” TrueFalse Unicode value can also be used to compare
myStr[0] “v” Indexing: String are indexed same as array
myStr[1:4] “im " Slicing

Complex

Complex are used to store complex number. Go has two complex types of different sizes:

  • complex64: both real and imaginary parts are of float32 type.
  • complex128: both real and imaginary parts are of float64 type.

Example:

package main

import (
	"fmt"
	"math/cmplx"
)

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
	str    string     = "Go"
)

func main() {
	fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
	fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
	fmt.Printf("Type: %T Value: %v\n", z, z)
	fmt.Printf("Type: %T Value: %v\n", str, str)
}

Output:

Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)
Type: string Value: Go

Conditions and Loops

IF Condition

If condition consists of a Boolean condition followed by one or more statements. it allows you to take different paths of logic, depending on a given Boolean condition.

if <Boolean> {
	<Statements>
}

If statement can be followed by an optional else statement which is executed when the Boolean condition is false.

if <Boolean expression> {
	<Statements>
} else {
	<Statements>
}

Example: Basic if

func more(a, b int) bool {
	// return a > b
	if a > b {
		return true
	}
	return false
}

Analysis: In the above code two variables a and b are passed as argument to more() function. When a>b, true is returned else false is returned.

Example: Basic if with else

func max(a, b int) int {
    //if a > b{
    //	return a
    //}
    //return b
	var max int
	if a > b {
		max = a
	} else {
		max = b
	}
	return max
}

Analysis: In the above code two variables a & b are passed as argument to max() function. When a > b, value stored in a is assigned to max else value stored in y is assigned to max and finally value stored in max is returned.

Go language if condition allow precondition along with Boolean condition.

Example: If with precondition

func maxAreaCheck(length, width, limit int) bool {
	if area := length * width; area < limit {
		return true
	}
	return false
}

Switch

switch statement is easy to read and understand when used to handle multiple conditional branches.

The syntax of switch basic form:

switch <Initialization>;<condition> {
    case <Value1>:
        <Statements>
    case <Value1>:
        <Statements>
    /* We can have any number of case statement */
    default: /*Optional */
	    <Statements>
}

The syntax of switch conditions:

switch {
	case <condition>:
		<Statements>
	case <condition>:
		<Statements>
	default:
		<Statements>
	}

The syntax of switch for type:

switch {
	case <type>:
		<Statements>
	case <type>:
		<Statements>
	default:
		<Statements>
	}

When a case is match, all the statements under that case are executed and switch statement is over.

Example:

func main() {
	n := 2
	switch n {
	case 1:
		fmt.Println("one")
	case 2:
		fmt.Println("two")
	case 3:
		fmt.Println("three")
	default:
		fmt.Println("something else")
	}
}

Analysis: Above program, demonstrate basic switch use. The value of variable n is checked in each case. n has value 2 so second case will be executed and “two” will be printed to screen.

Example:

func main() {
	n := 2
	switch n {
	case 1, 2, 3:
		fmt.Println("one, two, three")
	default:
		fmt.Println("something else")
	}
}

Analysis: Above program, demonstrate that various cases can be merged. The value of variable n is checked in each case. n has value 2 so “one, two or three” line will be printed to screen.

Example:

func isEven(value int) {
	switch {
	case value&1 == 0:
		fmt.Println(value, "is even")
	default:
		fmt.Println(value, "is odd")
	}
}

Analysis: The above program demonstrate switch conditions, which is an alternative of multiple if-else.

For Loop

For loop help to iterate through a group of statements multiple times.

The Go for loop has four forms:

  • for <initialization>; <condition>; <increment/decrement> {}
  • for <condition> {} - like a while loop
  • for {} - an infinite while loop
  • for with range

It is the most standard and normal usage. Used in initial value / conditional expression / incremental expression form.

Example:

func main() {
	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sum := 0
	for i := 0; i < len(nums); i++ {
		sum += nums[i]
	}
	fmt.Println("Sum is:", sum)
}

If you just have conditional expression, it works as a while loop which runs until condition statement is false.

Example:

func main() {
	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sum := 0
	i := 0
	n := len(nums)
	for i < n {
		sum += nums[i]
		i++
	}
	fmt.Println("Sum is:", sum)
}

If you omit the initial value / conditional expression / increment or decrement expression, it works as an infinite loop until the break statement is encountered.

Example:

func main() {
	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sum := 0
	i := 0
	n := len(nums)
	for {
		sum += nums[i]
		i++
		if i >=n {
			break
		}
	}
	fmt.Println("Sum is:", sum)
}

Using for statement with a range statement is used to traverse various elements of array, slice etc.

Example:

func main() {
	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sum := 0
	for _, val := range nums {
		sum += val
	}
	fmt.Println("Sum is:", sum)
}

Analysis: Various elements of array numbers is traversed using range in for loop.

Range

The keyword is used in for loop to iterate data in data structures (arrays, slices, string, maps etc.). Behaviour of range keyword for different data structures:

  • With arrays and slices, range provides indexes and values.
  • With maps, range provide key-value pairs.
  • With string, range provide index of each Unicode characters in string and their corresponding Unicode characters.
  • If index values are not needed then they can be discarded using _.

Example:

func main() {
	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sum := 0
	for i, val := range nums {
		sum += val
		fmt.Print("[", i, ",", val, "]")
	}
	fmt.Println("Sum is:", sum)

	kvs := map[int]string{1: "apple", 2: "banana"}
	for k, v := range kvs {
		fmt.Println(k, ">", v)
	}

	str := "vim, go!"
	for i, c := range str {
		fmt.Println("[", i, ",", string(c), "]")
	}
}

Analysis:

  • A slice is created which contain numbers from 1 to 10.
  • Slice is traversed and its content is printed to screen. Sum of all the elements of slice is calculated and printed to screen.
  • If we are not interested in index then we can use single underscore _ to tell the compiler that we do not need this variable.
  • We had created a map and its content is traversed and printed to screen.
  • We had created a string str and its characters are traversed and printed to screen.

Function

Functions are used to provide modularity to the program. By using function, we can divide complex tasks into smaller manageable tasks. The use of the function is also to avoid duplicate code. For example, we can define a function max() to find bigger value. Then we can use this function multiple times whenever we want compare two integer values.

Example:

func max(a, b int) int {
	if a > b {
		return a
	}

	return b
}

func main() {
    fmt.Println(max(10, 20))
}

Analysis:

  • Function max() is declared, which will compare two integer variable a and b. This function will return greater value among two.
  • Inside main() function value 10 and 20 are passed to max() function.
  • Variable passed to max function are copied into a and b local variables.
  • The values are compared and grater among two is returned. Finally, 20 is returned by the function and printed to screen.

Parameter passing, Call by value

Arguments can be passed from one function to other using parameters. By default, all the parameters are passed by value. That means a separate copy is created inside the called function and the variable in the calling function remains unchanged.

Example:

func main() {
	i := 10
	fmt.Println("Value if i before increment is:", i)
	incrementPassByValue(i)
	fmt.Println("Value if i after increment is:", i)
}

func incrementPassByValue(x int) {
	x++
}

Analysis:

  • Variable “i” is declared and value 10 is initialized to it.
  • Value of “i” is printed.
  • Increment function is called. When a function is called the value of the parameter is copied into another variable of the called function. Flow of control goes to line no 1.
  • Value of var is incremented by 1. However, remember, it is just a copy inside the increment function.
  • When the function exits, the value of “i” is still 10.

Points to remember:

  • Pass by value just creates a copy of variable.
  • Pass by value, value before and after the function call remain same.

Pointers

Pointers are nothing more than variables that store memory addresses of another variable and can be used to access the value stored at those addresses. Various operators such as *, &, and [], enable us to use pointers.

Example:

func main() {
	data := 10
	ptr := &data
	fmt.Println("Value stored at variable var is:", data)
	fmt.Println("Value stored at variable var is:", *ptr)
	fmt.Println("The address of variable var is:", &data)
	fmt.Println("The address of variable var is:", ptr)
}

Output:

Value stored at variable var is: 10
Value stored at variable var is: 10
The address of variable var is: 0x1400001a0f8
The address of variable var is: 0x1400001a0f8

Analysis:

  • An integer type variable var is created, which store value 10.
  • A pointer ptr is created, which store address of variable var.
  • Value stored in variable var is printed to screen. Using * Operator value stored at the pointer location is accessed.
  • Memory address of var is printed to the screen. & operator is used to get the address of a variable.

Parameter passing, Call by Pointer / Reference

If you need to change the value of the parameter inside the function, then you should use call by reference. C language by default passes by value. Therefore, to make it happen, you need to pass the address of a variable and changing the value of the variable using this address inside the called function.

Example:

func incrementPassByPointer(ptr *int) {
	*ptr++
}

func main() {
	i := 10
	fmt.Println("Value of i before increment is:", i)
	incrementPassByPointer(&i)
	fmt.Println("Value of i after increment is:", i)
}

Output:

Value of i before increment is: 10
Value of i after increment is: 11

Analysis:

  • Address of “i” is passed to the function increment. Function increment takes a pointer to int as argument.
  • Variable at the address ptr is accessed and its value is incremented.
  • Finally, incremented value is printed to screen.

Points to remember:

  • Call by reference is implemented indirectly by passing the address of a variable to the function.

Structures

Go language supports structures, which are a collection of multiple data types as a single entity.

Example:

type student struct {
	rollNo int
	name   string
}

func main() {
	stud := student{rollNo: 1, name: "Biden"}
	fmt.Println(stud)
	fmt.Println("Student name:", stud.name) // Accessing inner fields
	pstud := &stud
	fmt.Println("Student name:", pstud.name)   // Accessing inner fields
	fmt.Println(student{rollNo: 2, name: "Ann"}) // Named initialization
	fmt.Println(student{name: "Ann", rollNo: 2}) // Order does not matter
	fmt.Println(student{name: "Alice"})          // Default initialization of rollNo
}

Output:

{1 Biden}
Student name: Biden
Student name: Biden
{2 Ann}
{2 Ann}
{0 Alice}

Analysis:

  • We have created a student stud. Whose name is “Biden” and roll number os “1”
  • Printed structure stud to the output screen.
  • We can access the fields of structure using dot . Operator
  • Structure pointers can also be used to access inner fields using same dot . Operator. Go language automatically access the inner fields by dereferencing.
  • When we create structure, we can initialize fields using field name.
  • When we initialize fields using field name. The order does not matter.
  • When we omit some fields name in the structure while creating it. The omitted fields are initialized to their default zero value. Example. 0 for integers, nil for pointers or empty string "" for strings.

Methods

Go is an object-oriented language. However, it does not have any class keyword. We can associate functions directly by structures and other data types. Between func keyword and name of function, we add data type called receiver. Once function is associated by a receiver, we can directly call function over structure using dot . operator.

Example:

type Rect struct {
	width  float64
	height float64
}

func (r Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}
func main() {
	r := Rect{width: 10, height: 8}
	fmt.Println("Area:", r.Area())
	fmt.Println("Perimeter:", r.Perimeter())

	ptr := &Rect{width: 10, height: 5}
	fmt.Println("Area:", ptr.Area())
	fmt.Println("Perimeter:", r.Perimeter())
}

Analysis:

  • In the above program, we have Rect structure, which represent rectangle. Rect has width and height fields.
  • We associate two function Area() and Parameter() and associate them with Rect data type.
  • In the main() function we had created an instance of Rect, with width 10 and height 8.
  • Then Area() and Perimeter() functions are called using dot. operator over Rect instance r.
  • Then another instance of Rect is created and its address is stored in pointer ptr.
  • Again Area() and Perimeter() function is called using dot. operator but this time we are calling the associated function over pointer. The go language automatically convert the pointer to value and called the associated functions.

There are two ways of defining associated function of a data type.

  • Accessor function, which take receiver as value. Go passes a copy of the instance this function (Just like pass by value). Any change done over the object is not reflected in the calling object.

The syntax of accessor function:

func (r <Receiver Data type>) <Function Name>(<Parameter List>) (<Return List>)

  • Modifier function, which take receiver as pointer. Go passes actual instance of the object to this function (Just like pass by pointer.) Any change done over the object is reflected on the original object.

The syntax of modifier function:

func (r *<Receiver Data type>) <Function Name>(<Parameter List>) (<Return List>)

Example:

type myInt int

func (data myInt) increment1() {
	data = data + 1
}

func (data *myInt) increment2() {
	*data = *data + 1
}

func main() {
	var data myInt = 1
	fmt.Println("value before increment1() call:", data)
	data.increment1()
	fmt.Println("value after increment1() call:", data)
	data.increment2()
	fmt.Println("value after increment2() call:", data)
}

Output:

value before increment1() call: 1
value after increment1() call: 1
value after increment2() call: 2

Analysis:

  • Accessor function increment1() does change on a local copy of the object. Therefore, changes done are lost.
  • Modifier function increment2() does change on the actual instance so changes done are preserved.

Interface

Interfaces are defined as a set of methods.

Syntax of interface:

Type <Interface name> interface {
	<Method name> <Return type>
}

In Go, to implement an interface an object just need to implement all methods of interface. When an object implement a particular interface, its object can be assigned to an interface type variable.

Example:

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

type Circle struct {
	Radius float64
}

func (r Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

func (c Circle) Area() float64 {
	return c.Radius * c.Radius * math.Pi
}

func (c Circle) Perimeter() float64 {
	return 2 * c.Radius * math.Pi
}

func TotalArea(shapes ...Shape) float64 {
	var area float64
	for _, s := range shapes {
		area += s.Area()
	}

	return area
}

func TotalPerimeter(shapes ...Shape) float64 {
	var peri float64
	for _, p := range shapes {
		peri += p.Perimeter()
	}

	return peri
}

func main() {
    r := Rect{width: 10, height: 10}
    c := Circle{radius: 10}
    fmt.Println("Total Area: ", TotalArea(r, c))
    fmt.Println("Total Perimeter: ", TotalPerimeter(r, c))
}

Analysis:

  • A Shape interface is created which contain two methods Area() and Perimeter().
  • Rect and Circle implements Shape interface as they implement Area() and Perimeter() methods.
  • TotalArea() and TotalPerimeter() are two functions which expect list of object of type Shape.

Array

An array is a collection of variables of the same data type.

Example:

func main() {
	var arr [10]int
	fmt.Println(arr)
	for i := 0; i < 10; i++ {
		arr[i] = i
	}
	fmt.Println(arr)
	count := len(arr)
	fmt.Println("Length of array:", count)
}

Analysis:

  • We had declared array arr of size 10. In Go language, the array size is a part of array. The array name is whole array and not pointer to first element like that in C, C++.
  • By default, all the elements of array are initialized to their default value. For our example, the default value of int type is 0.
  • We can read and set each element of array.
  • We can get size of array by using built-in function len().

Slice

Go Array is a collection of variables of same data type. Arrays are fixed in length and does not have any inbuilt method to increase their size.

Go Slice is an abstraction over Array. It actually uses arrays as an underlying structure. The various operations over slice are:

  • Inbuilt append() function is used to add the elements to a slice. If the size of underlying array is not enough then automatically a new array is created and content of the old array is copied to it.
  • The len() function returns the number of elements presents in the slice.
  • The cap() function returns the capacity of the underlying array of the slice.
  • The copy() function, the contents of a source slice are copied to a destination slice.
  • Re-slices the slice, the syntax <SliceName>[start:end], It returns a slice object containing the elements of base slice from index start to end- 1. Length of the new slice will be (end - start), and capacity will be cap (base slice) - start.

Slice provides these utility functions because of which it is widely used in Go programming.

To define a slice, you can declare it as an array without specifying its size. Alternatively, you can use make function to create a slice.

Example:

func main() {
	var s []int
	for i := 1; i <= 17; i++ {
		s = append(s, i)
		printSlice(s)
	}
}

func printSlice(data []int) {
	fmt.Printf("%v: len=%d cap=%d\n", data, len(data), cap(data))
}

Output:

[1]: len=1 cap=1
[1 2]: len=2 cap=2
[1 2 3]: len=3 cap=4
[1 2 3 4]: len=4 cap=4
[1 2 3 4 5]: len=5 cap=8
[1 2 3 4 5 6]: len=6 cap=8
[1 2 3 4 5 6 7]: len=7 cap=8
[1 2 3 4 5 6 7 8]: len=8 cap=8
[1 2 3 4 5 6 7 8 9]: len=9 cap=16
[1 2 3 4 5 6 7 8 9 10]: len=10 cap=16
...

Analysis:

  • First, we had created an empty slice s.
  • Using append() function we are adding elements to the slice.
  • The capacity of the underlying array is increasing from 1 to 2. Then from 2 to 4. Then 8 to 16 and finally 32.
  • As we keep adding elements to the slice, its capacity is managed automatically.

Example:

func main() {
	s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	printSlice(s) // len=10, cap=10

	a := make([]int, 10)
	printSlice(a) // len=10, cap=10

	b := make([]int, 0, 10)
	printSlice(b) // len=0, cap=10

	c := s[0:4]
	printSlice(c) // len=4, cap=10

	d := s[2:5]
	printSlice(d) // len=3, cap=8
}

Analysis:

  • First, we had created a slice s using array like initialization.
  • Then we had created slice “a” with 10 elements (Capacity is also 10) which are all initialized to 0
  • Then we had created slice “b” with 0 elements with capacity of 10 elements.
  • Then we are using re-slicing to create another slice “c”, which contain elements of slice “s” from index 0 to 3. Capacity of the slice “c” remain 10.
  • Then we are using re-slicing to create another slice “d”, which contain elements of slice “c” from index 2 to 5. Capacity of the slice “d” reduced to 8.

Map / Dictionary

A map is a collection of Key-Value pairs. Hash-Table is used to store elements in a Map, so it is unordered.

Syntax of map:

var <variable> map[<key datatype>]<value datatype>

Maps have to be initialized using make() before they can be used.

var <variable> map[<key datatype>]<value datatype> = make(map[<key datatype>]<value datatype>

<variable> := make(map[<key datatype>]<value datatype>)

Various operation on map:

  • Assignment: <variable>[<key>] = <value>
  • Delete: delete(<varibale>, <key>)
  • Access: value, ok = <variable>[<key>], the first value will have the value of key in the map. If the key is not present in the map, it will return zero value corresponding to the value data-type. The second argument returns whether the map contains the key.

Example:

func main() {
	m := make(map[string]int)
	m["Obama"] = 44
	m["Trump"] = 45
	m["Biden"] = 46

	for key, val := range m {
		fmt.Print("[", key, "->", val, "]")
	}
	fmt.Println("U.S President:", m["Biden"])
	delete(m, "Biden")

	value, ok := m["Biden"]
	fmt.Println("U.S President:", value, "Present:", ok)

	value2, ok2 := m["Trump"]
	fmt.Println("U.S President:", value2, "Present:", ok2)
}

Note: Dictionaries are implemented using Hash Table, so order of keys is not guaranteed.

Conclusion

This post briefly introduces the basic data types of the Go programming language, and gives examples, which are not comprehensive due to the limited space. To completion of these contents is sufficient for learning common data structures and algorithms afterwards.

On the following section will discuss the various algorithms that are applicable to Lists and will follow by list of practice problems with similar approaches.

Reference