Chuyển đến nội dung chính

Lesson 2: Variables, Types & Control Flow

Primitive types, composite types (array, slice, map, struct). Pointers, constants, iota. If/else, switch, for loops, range. Type assertions and type switches.

💻 Programming — Lesson 2 Lesson 2: Variables, Types & Control Flow

Golang: From Basics to Advanced

Part 1: Go Fundamentals

xdev.asia

1. Declare variables in Go

Go has many ways to declare variables, each suitable for a different context:

1.1. Use var

package main

import "fmt"

func main() {
    // Khai báo với type
    var name string = "Gopher"
    var age int = 15
    
    // Type inference (Go tự suy luận type)
    var language = "Go"      // string
    var version = 1.23       // float64
    
    // Khai báo không gán giá trị (zero value)
    var count int            // 0
    var active bool          // false
    var message string       // ""
    var pointer *int         // nil
    
    // Khai báo nhiều biến
    var (
        host   string = "localhost"
        port   int    = 8080
        debug  bool   = true
    )
    
    fmt.Println(name, age, language, version)
    fmt.Println(count, active, message, pointer)
    fmt.Println(host, port, debug)
}

1.2. Short declaration :=

func main() {
    // Short declaration - chỉ dùng TRONG function
    name := "Gopher"           // var name string = "Gopher"
    age := 15                  // var age int = 15
    pi := 3.14159              // var pi float64 = 3.14159
    isActive := true           // var isActive bool = true
    
    // Khai báo nhiều biến
    x, y := 10, 20
    host, port := "localhost", 8080
    
    // Swap values
    x, y = y, x
    
    fmt.Println(name, age, pi, isActive, x, y, host, port)
}

Rules of use: Use := in function (short). Use var at package level or when zero value is needed.

1.3. Constants et iota

// Constants - giá trị không đổi
const Pi = 3.14159
const AppName = "MyApp"

// Group constants
const (
    MaxRetries = 3
    Timeout    = 30 // seconds
    Version    = "1.0.0"
)

// iota - auto-increment cho enums
type Status int

const (
    StatusPending  Status = iota // 0
    StatusActive                 // 1
    StatusInactive               // 2
    StatusDeleted                // 3
)

// iota nâng cao
type Permission int

const (
    Read    Permission = 1 << iota // 1  (001)
    Write                           // 2  (010)
    Execute                         // 4  (100)
)

// Kết hợp permissions bằng bitwise OR
const ReadWrite = Read | Write      // 3  (011)
const All = Read | Write | Execute  // 7  (111)

func main() {
    fmt.Println(StatusPending)     // 0
    fmt.Println(StatusActive)      // 1
    fmt.Println(Read | Write)      // 3
    fmt.Println(All)               // 7
}

2. Primitive Types

2.1. Numeric Types

TypeSizeRangeZero Value
int81 byte-128 → 1270
int162 bytes-32768 → 327670
int32 / runes4 bytes-2^31 → 2^31-10
int648 bytes-2^63 → 2^63-10
int4 or 8 bytesPlatform dependent0
uint8 / bytes1 byte0 → 2550
uint162 bytes0 → 655350
uint324 bytes0 → 2^32-10
uint648 bytes0 → 2^64-10
float324 bytesIEEE-7540.0
float648 bytesIEEE-7540.0
complex648 bytesfloat32 + float32i(0+0i)
complex12816 bytesfloat64 + float64i(0+0i)
func main() {
    // Thường dùng: int, float64
    var count int = 42
    var price float64 = 19.99
    
    // byte = uint8, thường dùng cho raw data
    var b byte = 'A'   // 65
    
    // rune = int32, đại diện Unicode code point
    var r rune = '🚀'  // 128640
    
    // Type conversion - phải explicit
    var i int = 42
    var f float64 = float64(i)    // int → float64
    var u uint = uint(f)          // float64 → uint
    
    fmt.Println(count, price, b, r, f, u)
}

2.2. String

import (
    "fmt"
    "strings"
)

func main() {
    // String là immutable sequence of bytes (UTF-8)
    greeting := "Xin chào Go! 🚀"
    
    // String operations
    fmt.Println(len(greeting))              // byte length ≠ rune count
    fmt.Println([]rune(greeting))           // convert to rune slice
    fmt.Println(len([]rune(greeting)))      // rune (character) count
    
    // String concatenation
    first := "Hello"
    last := "World"
    full := first + " " + last             // simple concatenation
    
    // fmt.Sprintf - formatted string
    msg := fmt.Sprintf("Name: %s, Age: %d", "Go", 15)
    
    // strings package
    fmt.Println(strings.ToUpper("hello"))          // "HELLO"
    fmt.Println(strings.Contains("hello", "ell"))  // true
    fmt.Println(strings.Split("a,b,c", ","))       // ["a", "b", "c"]
    fmt.Println(strings.Join([]string{"a", "b"}, "-")) // "a-b"
    fmt.Println(strings.TrimSpace("  hello  "))    // "hello"
    fmt.Println(strings.ReplaceAll("aabb", "a", "x")) // "xxbb"
    
    // Raw string literal (backticks)
    query := `SELECT * FROM users
              WHERE active = true
              ORDER BY created_at DESC`
    
    fmt.Println(full, msg, query)
}

2.3. Boolean

func main() {
    var isActive bool = true
    var isDeleted bool       // zero value: false
    
    // Boolean operators
    result := isActive && !isDeleted  // AND, NOT
    either := isActive || isDeleted   // OR
    
    fmt.Println(result, either)
}

3. Composite Types

3.1. Array (Fixed size)

func main() {
    // Array - fixed size, ít dùng trực tiếp
    var nums [5]int                       // [0, 0, 0, 0, 0]
    nums[0] = 10
    nums[4] = 50
    
    colors := [3]string{"red", "green", "blue"}
    
    // Compiler đếm size
    primes := [...]int{2, 3, 5, 7, 11}  // size = 5
    
    fmt.Println(nums, colors, primes)
    fmt.Println(len(primes))              // 5
}

3.2. Slice (Dynamic array) - Most important!

func main() {
    // Slice - dynamic, dùng nhiều nhất
    nums := []int{1, 2, 3, 4, 5}
    
    // make(type, length, capacity)
    scores := make([]int, 0, 10)  // len=0, cap=10
    
    // Append
    scores = append(scores, 95, 87, 92)
    fmt.Println(scores)     // [95, 87, 92]
    fmt.Println(len(scores)) // 3
    fmt.Println(cap(scores)) // 10
    
    // Slicing (sub-slice)
    first3 := nums[:3]    // [1, 2, 3]
    last3 := nums[2:]     // [3, 4, 5]
    middle := nums[1:4]   // [2, 3, 4]
    
    // ⚠️ Slices share underlying array!
    middle[0] = 99
    fmt.Println(nums)      // [1, 99, 3, 4, 5] - nums cũng bị thay đổi!
    
    // Copy để tránh shared reference
    copied := make([]int, len(nums))
    copy(copied, nums)
    
    // Delete element at index 2
    nums = append(nums[:2], nums[3:]...)
    
    // slices package (Go 1.21+)
    // import "slices"
    // slices.Sort(nums)
    // slices.Contains(nums, 3)
    // slices.Index(nums, 3)
    
    fmt.Println(first3, last3, copied)
}

3.3. Map(Key-Value)

func main() {
    // Map literal
    ages := map[string]int{
        "Alice": 30,
        "Bob":   25,
        "Carol": 28,
    }
    
    // make
    scores := make(map[string]float64)
    scores["math"] = 9.5
    scores["english"] = 8.0
    
    // Access
    age := ages["Alice"]         // 30
    
    // Check existence (comma-ok idiom)
    val, ok := ages["Dave"]
    if !ok {
        fmt.Println("Dave not found") // val = 0 (zero value)
    }
    
    // Delete
    delete(ages, "Bob")
    
    // Iterate
    for key, value := range ages {
        fmt.Printf("%s: %d\n", key, value)
    }
    
    // Map size
    fmt.Println(len(ages))
    
    fmt.Println(age, val)
}

3.4. Struct

// Struct definition
type User struct {
    ID        int
    Name      string
    Email     string
    Age       int
    IsActive  bool
    CreatedAt time.Time
}

// Method trên struct
func (u User) FullInfo() string {
    return fmt.Sprintf("%s (%s)", u.Name, u.Email)
}

// Pointer receiver - có thể modify struct
func (u *User) Deactivate() {
    u.IsActive = false
}

func main() {
    // Tạo struct
    user1 := User{
        ID:       1,
        Name:     "Alice",
        Email:    "[email protected]",
        Age:      30,
        IsActive: true,
    }
    
    // Partial initialization
    user2 := User{Name: "Bob", Email: "[email protected]"}
    // Các field khác = zero value
    
    // Access fields
    fmt.Println(user1.Name)        // "Alice"
    fmt.Println(user1.FullInfo())  // "Alice ([email protected])"
    
    // Pointer to struct
    user1.Deactivate()
    fmt.Println(user1.IsActive)    // false
    
    // Anonymous struct (dùng cho test, temporary)
    point := struct {
        X, Y int
    }{X: 10, Y: 20}
    
    fmt.Println(user2, point)
}

4. Pointers

func main() {
    // Pointer lưu địa chỉ bộ nhớ
    x := 42
    p := &x         // p là *int, trỏ đến x
    
    fmt.Println(x)   // 42
    fmt.Println(p)   // 0xc0000b4008 (địa chỉ)
    fmt.Println(*p)  // 42 (dereference - lấy giá trị)
    
    *p = 100         // thay đổi giá trị qua pointer
    fmt.Println(x)   // 100
    
    // new() tạo pointer đến zero value
    n := new(int)    // *int, giá trị = 0
    *n = 42
    
    // Nil pointer
    var ptr *string  // nil
    if ptr == nil {
        fmt.Println("ptr is nil")
    }
}

// Pass by value vs pass by pointer
func double(x int) {
    x *= 2  // Không ảnh hưởng bên ngoài
}

func doublePtr(x *int) {
    *x *= 2  // Thay đổi giá trị gốc
}

// Go KHÔNG CÓ pointer arithmetic (an toàn hơn C/C++)

5. Zero Values

Every variable in Go is initialized with zero value if no value is assigned:

TypeZero Value
int, float64, ...0
boolfalse
string.string"" (empty string)
pointer, slice, map, func, interface, channelnil
array. arrayArray of zero values
structStructure with zero-value fields
func main() {
    var i int       // 0
    var f float64   // 0
    var b bool      // false
    var s string    // ""
    var p *int      // nil
    var sl []int    // nil (nhưng len=0, append vẫn hoạt động)
    var m map[string]int // nil (⚠️ KHÔNG thể write, phải make trước)
    
    // ⚠️ Nil map panic khi write
    // m["key"] = 1  // panic: assignment to entry in nil map
    m = make(map[string]int)
    m["key"] = 1  // OK
    
    fmt.Println(i, f, b, s, p, sl, m)
}

6. Control Flow

6.1. If / Else

func main() {
    age := 20
    
    // Standard if-else
    if age >= 18 {
        fmt.Println("Adult")
    } else if age >= 13 {
        fmt.Println("Teenager")
    } else {
        fmt.Println("Child")
    }
    
    // If với initialization (Go idiom phổ biến)
    if err := doSomething(); err != nil {
        fmt.Println("Error:", err)
        return
    }
    // err không tồn tại ở ngoài if scope
    
    // Comma-ok pattern
    if val, ok := myMap["key"]; ok {
        fmt.Println("Found:", val)
    }
}

6.2. Switch

func main() {
    day := "Monday"
    
    // Switch - không cần break (auto break)
    switch day {
    case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
        fmt.Println("Weekday")
    case "Saturday", "Sunday":
        fmt.Println("Weekend")
    default:
        fmt.Println("Invalid day")
    }
    
    // Switch không có expression (thay if-else chain)
    score := 85
    switch {
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B")
    case score >= 70:
        fmt.Println("C")
    default:
        fmt.Println("F")
    }
    
    // Type switch
    var i interface{} = "hello"
    switch v := i.(type) {
    case int:
        fmt.Printf("Integer: %d\n", v)
    case string:
        fmt.Printf("String: %s\n", v)
    case bool:
        fmt.Printf("Boolean: %t\n", v)
    default:
        fmt.Printf("Unknown type: %T\n", v)
    }
    
    // fallthrough (hiếm dùng)
    switch 3 {
    case 3:
        fmt.Println("3")
        fallthrough  // tiếp tục case tiếp theo
    case 4:
        fmt.Println("4")
    }
    // Output: 3, 4
}

6.3. For Loops

func main() {
    // Go CHỈ CÓ for loop (không có while, do-while)
    
    // Classic for
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
    
    // While-style  
    count := 0
    for count < 10 {
        count++
    }
    
    // Infinite loop
    // for {
    //     // break khi cần
    // }
    
    // Range over slice
    fruits := []string{"apple", "banana", "cherry"}
    for index, fruit := range fruits {
        fmt.Printf("%d: %s\n", index, fruit)
    }
    
    // Chỉ lấy value
    for _, fruit := range fruits {
        fmt.Println(fruit)
    }
    
    // Chỉ lấy index
    for i := range fruits {
        fmt.Println(i)
    }
    
    // Range over map
    ages := map[string]int{"Alice": 30, "Bob": 25}
    for name, age := range ages {
        fmt.Printf("%s is %d\n", name, age)
    }
    
    // Range over string (iterates runes)
    for i, ch := range "Hello 🌍" {
        fmt.Printf("%d: %c (%d)\n", i, ch, ch)
    }
    
    // Range over integer (Go 1.22+)
    for i := range 5 {
        fmt.Println(i) // 0, 1, 2, 3, 4
    }
    
    // break, continue, labels
    for i := 0; i < 10; i++ {
        if i == 3 { continue }  // skip 3
        if i == 7 { break }     // stop at 7
        fmt.Println(i)
    }
}

7. Type Assertions & Conversions

func main() {
    // Type assertion (cho interface)
    var i interface{} = "hello"
    
    // Dangerous (panics nếu sai type)
    s := i.(string)
    fmt.Println(s) // "hello"
    
    // Safe (comma-ok pattern)
    s, ok := i.(string)
    if ok {
        fmt.Println("String:", s)
    }
    
    n, ok := i.(int)
    if !ok {
        fmt.Println("Not an int") // n = 0
    }
    
    // Type conversion (explicit)
    var x int = 42
    var f float64 = float64(x)
    var u uint = uint(f)
    
    // String conversions
    import "strconv"
    
    numStr := strconv.Itoa(42)           // int → string: "42"
    num, err := strconv.Atoi("42")       // string → int: 42
    flt, err := strconv.ParseFloat("3.14", 64) // string → float64
    boolVal, err := strconv.ParseBool("true")  // string → bool
    
    fmt.Println(f, u, numStr, num, flt, boolVal, err)
}

8. Summary

Go has a simple but complete type system. Important points to remember:

  • Use := for local variables, var for package level or zero value
  • Slice Used more than Array, always used append. append to add elements
  • Map right make before write (nil map → panic)
  • Pointer Use when you need to modify the value or avoid large copies
  • Zero values This is an important feature that helps make code safer
  • For is the only loop, used range. range to iterate
  • Type conversion always explicit, no implicit conversion

Next article: Functions, Interfaces & Error Handling — three pillars of Go programming.