Go Programming Fundamentals: Constants, Pointers, and Control Flow

Constants

In Go, constants are defined using the const keyword and store values that remain unchanged during program execution. These constants are evaluated at compile time, even when declared within functions, and can only be of boolean, numeric (integer, floating-point, complex), or string types.

Due to compile-time constraints, constant expressions must be evaluable by the compiler.

Declaration syntax:

const name [type] = value
const a1 int8 = 9

The type can be omitted, similar to variable declarations, allowing multiple constants to be declared together.

All constant operations are performed at compile time. Arithmetic, logical, comparison operations between constants, type conversions, and calls to functions like len, cap, real, imag, complex, and unsafe.Sizeof all return constant values.

When declaring multiple constants, initialization expressions can be omitted for all but the first one. If omitted, it defaults to the previous constant's expression and type.

const a1 int8 = 9
const (
    a = 1
    b
    c = 13.0
    d
)

func main() {
    fmt.Println(a)
    fmt.Printf("%d %d %.2f %.2f\n", a, b, c, d)
}

iota Constant Generator

Constants can utilize the iota generator for sequential initialization. It starts at zero in the first line of a const block and increments by one for each subsequent line.

const (
    Sunday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

func main() {
    fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
}

Pointers

Go has two types of pointers:

  • Pointer types allow modification of data they reference, enabling efficient access without copying.
  • Slices consist of a pointer to an underlying array, element count, and capacity.

Slices offer enhanced safety compared to raw pointers, providing bounds checking that prevents crashes from out-of-bounds access.

Understanding Pointers

Memory space is allocated where a variable's value resides. Each memory location has a unique address. A pointer variable holds this address.

A pointer can reference any memory address, occupying 4 bytes on 32-bit systems and 8 bytes on 64-bit systems, independent of the referenced value size.

Uninitialized pointers default to nil. Every variable has a memory address accessible via the & operator.

func main() {
    a := 1
    str := "Beta"
    var aptr *int = &a
    var sptr *string = &str
    fmt.Printf("%p %p \n", aptr, sptr)
}

The relationship among variables, addresses, and pointers is:

  • Variables have memory addresses
  • Pointers hold these addresses
  • Dereferencing (*) retrieves the value at the address

Modifying Values Through Pointers

Values pointed to can be modified using pointers:

func main() {
    var a = 10
    fmt.Printf("main method value=%d\n", a)
    update1(a)
    fmt.Printf("after update1, main method value=%d\n", a)
    update2(&a)
    fmt.Printf("after update2, main method value=%d\n", a)
}

func update1(a int) {
    a = 66
    fmt.Printf("update1 internal=%d\n", a)
}

func update2(a *int) {
    *a = 99
    fmt.Printf("update2 internal value=%d\n", *a)
}

Creating Pointers

Use the new keyword to create a pointer:

func main() {
    ptr := new(string)
    *ptr = "BetaBeta"
    fmt.Println(*ptr)
}

Command Line Input

To capture command-line arguments:

var mode = flag.String("name", "", "Enter your name")

func main() {
    flag.Parse()
    fmt.Printf("Your name is: %s", *mode)
}

Variable Lifetimes

Variable lifetime refers to the duration a variable exists during program execution.

Types of lifetimes:

  1. Global variables: Live throughout the entire program execution.
  2. Local variables: Exist from declaration until no longer referenced.
  3. Parameters and return values: Local variables created upon function call and destroyed afterward.

Go uses heap and stack for storing variables:

  • Heap: For dynamically allocated memory segments. Size can expand or shrink.
  • Stack: Stores temporary local variables with in function blocks.

Stack follows LIFO principle. Memory allocation and deallocation are fast.

Compiler decides whether to allocate on stack or heap. Both var and new do not influence this decision.

var global *int

func f() {
    var x int
    x = 1
    global = &x // Escapes to heap
}

func g() {
    y := new(int)
    *y = 1
    // y does not escape
}

Type Aliases

type IntNew int
type IntAlias = int

func main() {
    var a IntNew
    var b IntAlias
    fmt.Printf("aType=%T\n", a)
    fmt.Printf("bType=%T\n", b)
}

Comments

Comments come in single-line (//) and multi-line (/* */) forms. Multi-line comments cannot be nested. Package documentation should precede the package statement.

Keywords and Identifiers

There are 25 keywords in Go:

break   default     func    interface   select
case    defer       go      map         struct
chan    else        goto    package     switch
const   fallthrough if      range       type
continue for         import  return      var

Identifiers are character sequences used for naming variables, functions, etc., consisting of letters, underscores, and digits, starting with a letter.

Predefined identifiers (36 total) such as int, string, make, len cannot be used as identifiers.

Operator Precedence

Precedence Category Operators Associativity
1 Comma , Left-to-right
2 Assignment =, +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, =
3 Logical OR
4 Logical AND && Left-to-right
5 Bitwise OR
6 Bitwise XOR ^ Left-to-right
7 Bitwise AND & Left-to-right
8 Equality/Inequality ==, != Left-to-right
9 Relational <, <=, >, >= Left-to-right
10 Shift <<, >> Left-to-right
11 Addition/Subtraction +, - Left-to-right
12 Multiplication/Division *, /, % Left-to-right
13 Unary !, *, &, ++, --, +, - Right-to-left
14 Postfix ( ), [ ], -> Left-to-right

String Conversion

Integer to String

func main() {
    str := "666"
    iVal, _ := strconv.Atoi(str)
    fmt.Printf("%d %T \n", iVal, iVal)
    
    sVal := strconv.Itoa(iVal)
    fmt.Printf("%s %T \n", sVal, sVal)
}

Float to String

func main() {
    str := "3.1415"
    fValue, _ := strconv.ParseFloat(str, 64)
    fmt.Printf("%f %T \n", fValue, fValue)
    
    sValue := strconv.FormatFloat(fValue, 'f', -1, 64)
    fmt.Printf("%s %T \n", sValue, sValue)
}

Arrays

Arrays are fixed-length sequences of elements of the same type.

var arrayName [elementCount]Type
var arr [3]int

func main() {
    fmt.Printf("%d %d %d \n", arr[0], arr[1], arr[2])
    
    for idx, val := range arr {
        fmt.Printf("idx = %d  value = %d |", idx, val)
    }
    fmt.Println()
    
    for _, val := range arr {
        fmt.Printf("value=%d |", val)
    }
    
    fmt.Println()
    for idx := range arr {
        fmt.Printf("index=%d |", idx)
    }
}

Initialization

var arr [3]int = [3]int{1, 2, 3}
var arr1 [3]int = [3]int{1, 2}
var arr2 [3]int = [...]int{1, 2, 3}

func main() {
    arr := [3]int{1, 2, 3}
    fmt.Printf("%d", arr)
    arr[0] = 4
    arr[1] = 5
    arr[2] = 6
}

Type Definition

type arr3 [3]int
var arr arr3

func main() {
    arr[0] = 1
    arr[1] = 2
    fmt.Printf("%d %T", arr, arr)
}

Index Assignment

var arr [3]int

func main() {
    arr = [3]int{1: 5}
    fmt.Printf("%d", arr)
}

Comparison

Arrays of identical type and length can be compared using == and !=.

func main() {
    a := [2]int{1, 2}
    b := [...]int{1, 2}
    c := [2]int{1, 3}
    fmt.Printf("%t %t %t \n", a == b, b != c, a != c)
    
    d := [3]int{1, 2}
    // Compilation error
    fmt.Printf("%t", a == d)
}

Multi-dimensional Arrays

Go supports multi-dimensional arrays where each dimension initializes to zero values.

var arr [4][2]int
var arr1 = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
var arr2 = [4][2]int{1: {20, 21}, 3: {40, 41}}
var arr3 = [4][2]int{1: {0: 20}, 3: {1: 41}}

func main() {
    fmt.Printf("%d\n", arr2[1])
    for idx, val := range arr3 {
        fmt.Printf("Index:%d Value:%d \n", idx, val)
    }
}

Slices

Slices are references to contiguous sections of arrays.

slice [start:end]

func main() {
    var val = [3]int{1, 2, 3}
    slice := val[1:2]
    fmt.Printf("%d %d %T", val, slice, slice)
}

Slice Operations

func main() {
    var val [30]int
    for i := 0; i < 30; i++ {
        val[i] = i + 1
    }
    
    fmt.Printf("%d\n", val[10:15])
    fmt.Printf("%d\n", val[20:])
    fmt.Printf("%d\n", val[:2])
}

Declaration

var name []Type

func main() {
    var strList []string
    var val1 []int
    var val2 []int = []int{}
    
    fmt.Println(strList, val1, val2)
    fmt.Println(len(strList), len(val1), len(val2))
    fmt.Println(strList == nil, val1 == nil, val2 == nil)
    
    strList = append(strList, "BetaBeta")
    fmt.Println(strList)
}

Using make

make([]Type, size, cap)

func main() {
    var a = make([]int, 2)
    b := make([]int, 2, 3)
    fmt.Println(a, b)
    fmt.Println(len(a), len(b))
    fmt.Println(cap(a), cap(b))
}

Copying

copy(destSlice, srcSlice []T) int

func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := []int{7, 6, 5}
    copy(slice1, slice2)
    fmt.Println(slice1)
    
    slice2 = []int{1, 1, 1}
    copy(slice2, slice1)
    fmt.Println(slice2)
}

Maps

Maps are unordered collections of key-value pairs.

var mapname map[keytype]valuetype

func main() {
    var map1 = map[string]int{"k1": 1, "k2": 2}
    var map2 map[string]int
    map2 = map1
    map2["k1"] = 99
    fmt.Println(map1["k1"], map2["k1"], map1["k99"])
}

Creating with Capacity

make(map[keytype]valuetype, cap)

mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)

Iteration

func main() {
    var map1 map[string]string
    map1 = make(map[string]string, 3)
    map1["k1"] = "key1"
    map1["k2"] = "key2"
    map1["k3"] = "key3"

    for key, value := range map1 {
        fmt.Printf("key:%s value:%s \n", key, value)
    }
}

Deletion

func main() {
    map1 := make(map[string]int, 3)
    map1["a"] = 1
    map1["b"] = 2
    map1["c"] = 3
    delete(map1, "b")
    fmt.Println(map1)
    map1 = make(map[string]int)
    fmt.Println(map1)
}

Concurrent Access

Map access is not thread-safe. Use sync.Map for concurrent scenarios:

func main() {
    var syncMap sync.Map
    syncMap.Store("a", 1)
    syncMap.Store("b", 2)
    syncMap.Store("c", 3)
    fmt.Println(syncMap.Load("a"))
    syncMap.Delete("b")
    syncMap.Range(func(k, v interface{}) bool {
        fmt.Printf("%s %d \n", k, v)
        return true
    })
}

Nil Values

Nil represents zero values for pointers, slices, maps, channels, functions, and interfaces.

func main() {
    var a []int
    var b *int
    fmt.Printf("%p %p \n", a, b)
    
    var m map[string]string
    var sl []int
    var ptr *int
    var c chan int
    var f func()
    var i interface{}
    fmt.Printf("%#v\n%#v\n%#v\n%#v\n%#v\n%#v\n", m, sl, ptr, c, f, i)
}

new vs make

  • make allocates and initializes slices, maps, and channels.
  • new allocates memory and returns a pointer to it, initializing to zero values.

Control Flow

if-else

if condition1 {
    // Execute if condition1 is true
} else if condition2 {
    // Execute if condition1 is false and condition2 is true
} else {
    // Execute if both conditions are false
}

if a := 5; a > 0 {
    fmt.Println(a)
}

for loops

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
    
    var a int
    for {
        a++
        if a == 2 {
            break
        }
    }
    
    i := 0
    for ; i < 3; i++ {
        fmt.Println(i)
    }
    
    b := 0
    for b < 2 {
        fmt.Println(b)
        b++
    }
}

Loop Control

Break, return, panic, and goto are available for loop control:

for i := 0; i < 5; i++ {
    fmt.Println(i)
    if i == 2 {
        break
    }
}
fmt.Println("end")

for i := 0; i < 5; i++ {
    fmt.Println(i)
    if i == 2 {
        return
    }
}
fmt.Println("end")

for i := 0; i < 5; i++ {
    fmt.Println(i)
    if i == 2 {
        panic("exception")
    }
}
fmt.Println("end")

func main() {
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            if j == 2 {
                goto breakHere
            }
        }
    }
    return
breakHere:
    fmt.Println("end")
}

for-range

map1 := make(map[string]string, 3)
map1["a"] = "1"
map1["b"] = "2"
map1["c"] = "3"

for k, v := range map1 {
    fmt.Printf("key:%s value:%s \n", k, v)
    if k == "a" {
        v = "AAA"
        fmt.Printf("updated: %s\n", map1[k])
    }
}

str := "你好Beta"
for idx, s := range str {
    fmt.Printf("%d %c \n", idx, s)
}

switch

func main() {
    var exp = 100
    var level int
    switch exp {
    case 50, 60, 70:
        level = 7
    case 80:
        level = 8
    case 100:
        level = 10
    default:
        level = 5
    }
    
    switch {
    case level == 7:
        fmt.Println("Average Level")
    case level > 7:
        fmt.Println("High Level")
    case level < 7:
        fmt.Println("Beginner Level")
    }
}

fallthrough

str := "GOGO"
switch str {
    case "GOGO":
        fmt.Print("GOGO")
        fallthrough
    case "Lets GO":
        fmt.Print(" Lets GO")
}

goto

func main() {
    for x := 0; x < 10; x++ {
        for y := 0; y < 10; y++ {
            if y == 2 {
                goto breakPoint
            }
        }
    }
    fmt.Println("done")
breakPoint:
    fmt.Println("goto break")
}

break

func main() {
breakPoint:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                break breakPoint
            }
            fmt.Println("j:", j)
        }
    }
}

continue

func main() {
continuePoint:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                continue continuePoint
            }
            fmt.Println("j:", j)
        }
    }
}

Tags: Go programming constants pointers Arrays

Posted on Sun, 10 May 2026 21:06:35 +0000 by hi2you