Go Programming Languages - Fundamentals

Go Programming Languages - Fundamentals

Table of Contents

Overview

The go programming language has the features in C, Java and Python. Go programming language is a grammar-sensitive language, meaning that the language attached great importance on grammar. Go supports gabbage collection. Go does not support inheritance and overloading, but supports interface. Go supports concurrency. Go does not support casting. Go does not support assert (third-party packages available), Go does not support static variable.

Environment Variable

  1. GOROOT: the place where you install the Go
  2. GOPATH: the place where you store the source code
    1. src: source code
    2. pkg: dependent package
  3. 国内用户建议设置goproxy: export GOPROXY=https://goproxy.cn

Basic Command

command content
bug start a bug report
build compile packages and dependencies
clean remove object files and cached files
doc show documentation for package or symbol
env print Go environment information
fix update packages to use new APIs
fmt gofmt (reformat) package sources
generate generate Go files by processing source
get add dependencies to current module and install them
install compile and install packages and dependencies
list list packages or modules
mod module maintenance
run compile and run Go program
test test packages (go test will scan for all files ending with *_test.go, so put test and src files together)
tool run specified go tool
version print Go version
vet report likely mistakes in packages

Go Fundamentals

Hello World

The first and foremost thing for a programming language is to understand the basic hello_world.go

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("hello world")
}
Three things to notice:

  • package mainindicates where the package is in, this is the entry point for go programs
    • There is only one main function in one package
  • import "fmt"indicates importing a package
  • func main like many other languages, the main function
    • However, the main does not need input arguments - like Python
    • uses os.Argsto obtain arguments

Input from stdin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//take input like scanf in C
_, err = fmt.Scanf("%d %s", &x, &y)
fmt.Println("You Entered x:", x, " and y: ", y, " Error: ", err)

//take input like cin >> in c++
var x int = 1337
var y string = "string value"

_, err := fmt.Scan(&x, &y)
fmt.Println("You Entered x:", x, " and y: ", y, " Error: ", err)

//take input with white spaces
var z string = "string"

scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
z = scanner.Text()
fmt.Println("You Entered z:", z)

Variable

Initialization

There are two ways to define a variable

  • Implicitly initialize

    1
    2
    var a int
    // equivalent to var a int = 0

  • Define initial value but without stating the variable type

    1
    var a = 1

  • Define initial value plus variable type

    1
    var a int = 1

  • (new) convenient way without var

    1
    2
    a := 1
    // equivalent to var a := 1

  • 使用var()集中定义变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var (
    a int
    b string
    c []float32
    d func() bool
    e struct {
    x int
    }
    )

  • 编译器智能决定变量

    1
    2
    // only can be used in a function
    var a, b, i, s1, s2 = true, false, 3, "Hello", "World"

Explicit conversion (强制类型转换)

  • Go中,类型转换是强制的(没有隐式转换)
    1
    2
    3
    4
    5
    6
    var a, b int = 3, 4
    // wrong, math.Sqrt returns float64
    // also, a * a + b * b is int, not float64
    var c int = math.Sqrt(a * a + b * b)
    // correct, must explicitly convert
    var c int = int(math.Sqrt(float64(a*a + b*b)))

Type

Basic Types

  1. bool
  2. string
  3. int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64
  4. byte // alias for uint8
  5. rune // char in java, alias for int32
    • official:rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.
  6. float32, float64
  7. complex64, complex128

Constant

Initialization

  1. Full version

    1
    const a int = 1

  2. Without stating the type

    1
    const a = 1
    In Go programming language, the initialization cannot be like the following:
    1
    2
    const a int // not legal
    a = 1 // not legal

Enum (No enum explicitly in Go)

1
2
3
4
5
6
const (
cpp = 1
java = 2
python = 3
golang = 4
)

For simplicity, we can use iota keyword for auto increment

1
2
3
4
5
6
const (
cpp = iota
java
python
golang
)

For more advanced usage:

1
2
3
4
5
6
7
8
9
// e.g., we want to create b, kb, mb, gb, tb, pb...
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)

Condition Statement

For loop

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {

for j:=1 ; j<10 ; j++ {
fmt.Println("j")
}

}

here j:=1is the alias for var int j = 1 In java, the type can be written in the for loop condition

1
2
3
4
for (int i=0; i<10; i++) {
// statement here
}

For go, it does not allow type to be written in the condition, but you can use more stupid way of declaring the variable
1
2
3
4
var j = 1
for j=1; j<10; j++ {
// statement here
}
Also, for go, there is no **while** statement, meaning that if you want to do while statement, the way to do that is to limit the condition to just one:
1
2
3
4
5
var j = 1
for j < 10 {
//statement here
j = j + 1
}
Others:
1
2
3
4
5
6
7
8
9
10
var j = 1
// infinite loop
for {
//statement here
j = j + 1
if j >= 10 {
break
}
}

For also allowed for three ways that substitute the while

  1. ignore initial state

    1
    2
    3
    4
    5
    6
    7
    func convertToBin(v int) string {
    result := ""
    for ; v > 0 ; v /= 2 {
    result = strconv.Itoa(v % 2) + result
    }
    return result
    }

  2. ignore initial and final state

    1
    2
    3
    for scanner.Scan() {
    fmt.Println(scanner.Text())
    }

  3. ignore initial and final state, and update condition

    1
    2
    3
    4
    // equals to while (true)
    for {
    fmt.Println("abc")
    }

If/else

Similar to most of the language, with a few exceptions:

  1. Can exclude (), but cannot exclude {}, the left bracket { must be on the same line with if statement
  2. Go doesn't have Ternary Operator ( x < 0 ? A : B )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"io/iotuil"
)

func main() {
const filename = "abc.txt"
// normal way
contents, err := ioutil.ReadFile(filename)
if (err != nil) {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
}

Another way to write this would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"io/ioutil"
)

func main() {
const filename = "abc.txt"
// equivalent to contents, err := ioutil.ReadFile(filename)
// if (err != nil) {
// ...
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
}

Switch

Switch statements in go propgramming language doesn't require break; they will break by default, fallthrough keyword used to go to NEXT statement even if condition doesn't match, fallthrough is like a continue so statement that after it will also executed. however a workaround is to use labels and goto

1
2
3
4
5
6
7
8
9
switch var1 {
case var1:
case var2:
fallthrough
case var3:
f()
default:
...
}
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func eval(a, b int, op string) {
var result int
switch op {
case "+":
result = a + b
case "-":
result = a - b
case "*":
result = a * b
case "/":
result = a / b
default:
panic("unsupported operator:" + op)
}
return result
}

Note:

  1. Switch will automatically "break" (we do not need to write break)

  2. fallthrough = continue running the following code without breaking

Array

Initialzation

ways of declaring initialization

1
2
3
4
5
6
7
8
// first one
var a [3] int
var balance [10] float32
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8, 10}

// two-dimension
var x [3][2] int

iterate over array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// first method
for i := 0; i<len(arr3); i++ {
fmt.Println(arr3[i])
}

// second method: using range
for i := range arr3 {
fmt.Println(arr3[i])
}

// if we want to directly access the value
for i, v := range arr3 {
fmt.Println(i, v)
}

Slices

Slices are of dynamic size

1
2
3
4
5
6
7
8
9
letters := []string{"a", "b", "c", "d"}

/* using make -> make([]T, len, cap) */
var s []byte
s = make([]byte, 5, 5)
//OR
s := make([]byte, 5)

// both equiavlent to: s == []byte{0, 0, 0, 0, 0}

  • 左开右闭
  • A slice does not store any data, it just describes a section of an underlying array. Changing the elements of a slice modifies the corresponding elements of its underlying array. Other slices that share the same underlying array will see those changes.
  • Slicing a slice changes pointers of the underlying array, so it is as efficient as manipulating array indices, size and capacity of the new slice are changed too.
  • Example usage:
    1
    2
    3
    4
    5
    6
    7
    8
    names := [4]string{"John","Paul","George","Ringo",}
    fmt.Println(names) //[John Paul George Ringo]
    a := names[0:2]
    b := names[1:3]
    fmt.Println(a, b) //[John Paul] [Paul George]
    b[0] = "XXX"
    fmt.Println(a, b) //[John XXX] [XXX George]
    fmt.Println(names) //[John XXX George Ringo]

Difference between slice and array

1
2
3
4
5
//This is an array literal:
[3]bool{true, true, false}

//And this creates the same array as above, then builds a slice that references it:
[]bool{true, true, false}

Iterating over slice

1
2
3
for i, v := range arr {	//do stuff }
for _, v := range arr { //do stuff }
for i, _ := range arr { //do stuff }

Map

Map format: map[k]v

1
2
3
4
5
6
m := map[string]string {
"name": "ccmouse",
"course": "golang",
"site", "imooc",
"quality": "notbad"
}

Create a map

1
2
3
4
// m2 == empty map
m2 := make(map[string]int)
// initialize the space
m2 := make(map[string]int, 5)

Traversing the map (using range)

1
2
3
for k, v := range m {
fmt.Println(k, v)
}

Get Element

Unlike Python when accessing non-exist key, it will pop exceptions, Go will not, and it will return two values on accessing the value through keys, 1) the value itself, and 2) a bool of whether the element exists

1
2
3
4
5
6
7
8
// ok will be whether the key exists
courseName, ok := m1["course"]

if courseName, ok := m1["course"]; ok {
fmt.Println("causeName")
} else {
fmt.Println("key does not exist")
}

Delete Element

The delete() function is a direct and effective way to eliminate a key−value pair from a map. By specifying the key to delete, you can promptly remove the corresponding data from the map.

1
2
3
//  delete(mapName, key)
name, ok := m["name"]
delete(m, "name")

Map's Key

  • Map uses hashmap as its data structure, so other than slice, map, function, other type can be used as keys.
  • Sturct data without slice, map and function can also be the key

Functions in Go

Typical Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// return void
func add(x int, y int) {
fmt.Println("Hello, World!")
}

//-------arguments-----return------
func add(x int, y int) int {
return x + y
}

//-----same type arguments-----------
func add(x, y int) int {
return x + y
}

Multiple Returns

1
2
3
4
5
6
7
8
9
func swap(x, y string) (string, string) {
return y, x
}

//in main
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b) //prints "world hello"
}

Named Returns

You can declare return variables and name them at the beginning, they are returned in the end.

1
2
3
4
5
6
7
8
//Returns x,y at the end.
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
// by default, it returns x and y
return
//return a,b <- u can override default return of x,y.
}

Variadic Functions

The rightmost argument can be a list of variable size (slice) of data. ... three dots means you are passing a variable size of data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func average(x int, values ...int) float64 {
//print values
fmt.Println("Single argument value: ", x)
fmt.Println("Variable argument values: ", values)

//calculate average
total := 0
for _, value := range values {
total += value
}

return float64(total) / float64(len(values))
}

func main() {
avg := average(10,20,30,40,50)
println("Average:", avg)
}

Defer Keyword

Defer used before a functions executes the function at the end of the scope of it.

  • usually used to close opened files/buffers so you open the file and closes it using defer in the next line to keep things clean.
  • they're executed as a stack.
    1
    2
    3
    4
    5
    6
    fmt.Println("One")
    defer fmt.Println("Four")
    defer fmt.Println("Three")
    fmt.Println("Two")

    //Prints One Two Three Four

Type Function and Returning Functions

  1. Functions can be assigned to variables func0 := func() int {x++; return x}
  2. Functions that are returned from another functions has its own scope per returned function
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package main

    func incrementGlobalX() int {
    x++
    return x
    }

    func wrapper() func() int {
    x := 0
    return func() int {
    x++
    return x
    }
    }

    func main() {
    fn := wrapper()
    fmt.Print(wrapper())
    fmt.Print(wrapper())
    // print 12
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package main

    var x = 0

    func main() {
    //local x
    x := 0

    func0 := func() int {x++; return x}
    func1 := incrementGlobalX //without ()
    func2 := wrapper()
    func3 := wrapper()

    println(func0(), " : func0 (local x)")
    println(func1(), " : func1 (global x)")
    println(func2(), " : func2 (per func scope x1)")
    println(func3(), " : func3 (per func scope x2)")
    println("Second Increment")
    println(func0(), " : func0 (local x)")
    println(func1(), " : func1 (global x)")
    println(func2(), " : func2 (per func scope x1)")
    println(func3(), " : func3 (per func scope x2)")
    }

    func incrementGlobalX() int {
    x++
    return x
    }

    func wrapper() func() int {
    x := 0
    return func() int {
    x++
    return x
    }
    }

Receiver

Receiver is the way you create a method for a specific type/struct

1
2
3
4
5
6
7
8
9
10
11
12
type rect struct {
width, height int
}

// (r *rect) is called rceiver
func (r *rect) area() int {
return r.width * r.height
}

r := rect{2,3}
areaX := r.area()
fmt.Println(areaX)

Pointers

Similar to C/C++

1
2
3
4
5
6
7
8
9
var value int = 1000
var pointer *int = &value
println(value) //1000
println(pointer) //0xfffffffff
println(*pointer) //1000
(*pointer)++ //1001
*pointer = *pointer + 10 //1011
println(*pointer) //1011
println(*pointer + *pointer) //1011 + 1011 = 2022

Difference?

  1. Pointer cannot do arithmetic calculation: you can change what pointer points to, but you cannot change the pointer itself

Pass by value & Pass by reference

  1. Every thing is passed by value except arrays, slices, maps and channels which some calls reference types, these types are passed by reference ( they internally have pointers, so no copying of the actual data happens when passing them) .
  2. Unlike C, where the address of a local variable will be recollected after the function returns, it's perfectly OK to return the address of a local variable (like Java); the storage associated with the variable survives after the function returns.

Reference

  1. https://github.com/sherifabdlnaby/Golang-study-notes
  2. Coursera - Functions, Methods, and Interfaces in Go, University of California, Irvine.
  3. 慕课网 Google资深工程师深度讲解Go语言 由浅入深掌握Go语言
Author

Tragic Master

Posted on

2024-04-28

Updated on

2024-04-28

Licensed under