Tuần 5. Golang Basic (Đang thiếu bổ sung sau)

Làm các bài tập sau:

https://laptrinhonline.club/problem/0001dayc

https://laptrinhonline.club/problem/0014dayc

https://laptrinhonline.club/problem/0021dayc

https://laptrinhonline.club/problem/basic4

Tài liệu Golang từ A đến Z

Mục lục

  1. Giới thiệu về Go

  2. Cài đặt Go

  3. Cấu trúc chương trình Go

  4. Các kiểu dữ liệu cơ bản

  5. Biến và hằng số

  6. Toán tử

  7. Cấu trúc điều khiển

  8. Hàm

  9. Con trỏ

  10. Cấu trúc (Struct)

  11. Interface

  12. Mảng và Slice

  13. Map

  14. Xử lý chuỗi

  15. Xử lý lỗi

  16. Goroutines và đồng thời

  17. Channels

  18. Context

  19. Làm việc với file

  20. Xử lý JSON

  21. Làm việc với database

  22. Testing

  23. Quản lý package với Go Modules

  24. Viết RESTful API

  25. Mẫu thiết kế trong Go

  26. Các công cụ và thư viện phổ biến

Giới thiệu về Go

Go (hay còn gọi là Golang) là một ngôn ngữ lập trình mã nguồn mở được phát triển bởi Google vào năm 2007 và ra mắt công chúng vào năm 2009. Go được thiết kế bởi Robert Griesemer, Rob Pike và Ken Thompson.

Đặc điểm của Go:

  • Đơn giản và dễ học: Cú pháp gọn gàng, dễ đọc và dễ hiểu.

  • Biên dịch nhanh: Thời gian biên dịch nhanh hơn so với các ngôn ngữ truyền thống như C/C++.

  • Hiệu suất cao: Gần với C/C++ nhưng có quản lý bộ nhớ tự động.

  • Tích hợp hỗ trợ đồng thời: Goroutines và channels hỗ trợ lập trình đồng thời dễ dàng.

  • Garbage Collection: Tự động quản lý bộ nhớ.

  • Tĩnh và mạnh mẽ: Là ngôn ngữ kiểu tĩnh (statically typed).

  • Thư viện chuẩn phong phú: Hỗ trợ nhiều tính năng mạnh mẽ sẵn có.

Các trường hợp sử dụng Go:

  • Lập trình hệ thống

  • Phát triển web backend

  • Ứng dụng mạng và phân tán

  • Công cụ dòng lệnh

  • Cloud computing và microservices

Cài đặt Go

Windows:

  1. Tải trình cài đặt từ golang.org

  2. Chạy trình cài đặt và làm theo hướng dẫn

  3. Kiểm tra cài đặt: go version

macOS:

  1. Sử dụng Homebrew: brew install go

  2. Hoặc tải trình cài đặt từ golang.org

  3. Kiểm tra cài đặt: go version

Linux:

  1. Tải và giải nén:

    wget https://golang.org/dl/go1.21.0.linux-amd64.tar.gzsudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz
    
  2. Thêm vào PATH:

    echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.profilesource ~/.profile
    
  3. Kiểm tra cài đặt: go version

Cấu hình GOPATH

GOPATH là thư mục làm việc cho các dự án Go. Mặc định là $HOME/go trên Unix/Linux và %USERPROFILE%\go trên Windows.

Cấu trúc chương trình Go

Hello World

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Giải thích:

  • package main: Khai báo package, main là package đặc biệt cho điểm bắt đầu chương trình

  • import "fmt": Import package fmt để sử dụng hàm Println

  • func main(): Hàm khởi đầu của chương trình

  • fmt.Println(): In chuỗi ra màn hình

Cách biên dịch và chạy:

# Biên dịch và chạy trong một bước
go run hello.go

# Biên dịch thành tệp thực thi
go build hello.go

# Chạy tệp thực thi
./hello   # trên Unix/Linux/macOS
hello.exe # trên Windows

Các kiểu dữ liệu cơ bản

Go có các kiểu dữ liệu cơ bản sau:

Kiểu số nguyên:

  • int, int8, int16, int32, int64

  • uint, uint8, uint16, uint32, uint64

  • byte (alias cho uint8)

  • rune (alias cho int32, biểu diễn một Unicode code point)

  • uintptr (số nguyên không dấu đủ lớn để lưu con trỏ)

Kiểu số thực:

  • float32, float64

  • complex64, complex128

Kiểu boolean:

  • bool (true hoặc false)

Kiểu chuỗi:

  • string

Ví dụ:

package main

import "fmt"

func main() {
    // Số nguyên
    var a int = 10
    var b int8 = 127
    
    // Số thực
    var c float64 = 3.14
    
    // Boolean
    var d bool = true
    
    // Chuỗi
    var e string = "Hello, Go!"
    
    // In các giá trị
    fmt.Println(a, b, c, d, e)
    
    // Kiểu dữ liệu phức tạp
    var f complex128 = 1 + 2i
    fmt.Println("Complex number:", f)
}

Biến và hằng số

Khai báo biến:

// Khai báo chuỗi
    s1 := "Hello, Go!"
    
    // Độ dài chuỗi (số byte)
    fmt.Println(len(s1))  // 10
    
    // Độ dài chuỗi Unicode (số ký tự)
    fmt.Println(utf8.RuneCountInString(s1))
    
    // Truy cập ký tự (byte)
    fmt.Println(s1[0])  // 72 (mã ASCII của 'H')
    
    // Duyệt qua các ký tự Unicode
    for i, r := range s1 {
        fmt.Printf("%d: %c\n", i, r)
    }
}

Package strings:

func main() {
    s := "Hello, Go!"
    
    // Tìm kiếm
    fmt.Println(strings.Contains(s, "Go"))      // true
    fmt.Println(strings.HasPrefix(s, "Hello"))  // true
    fmt.Println(strings.HasSuffix(s, "!"))      // true
    fmt.Println(strings.Index(s, "Go"))         // 7
    
    // Chuyển đổi
    fmt.Println(strings.ToUpper(s))          // HELLO, GO!
    fmt.Println(strings.ToLower(s))          // hello, go!
    fmt.Println(strings.Title(s))            // Hello, Go!
    
    // Cắt và nối
    parts := strings.Split(s, ", ")          // ["Hello", "Go!"]
    fmt.Println(strings.Join(parts, " - "))  // Hello - Go!
    fmt.Println(strings.Trim(" test ", " ")) // "test"
    
    // Thay thế
    fmt.Println(strings.Replace(s, "Go", "Golang", 1))  // Hello, Golang!
}

Rune:

func main() {
    // Xử lý các ký tự Unicode
    s := "Xin chào"
    runes := []rune(s)
    
    // Đếm số ký tự thực
    fmt.Println(len(runes))  // 8
    
    // Truy cập ký tự Unicode
    fmt.Printf("%c\n", runes[4])  // c
    
    // Chuyển đổi rune sang string
    fmt.Println(string(runes[4:]))  // chào
}

String Builder:

import "strings"

func main() {
    var builder strings.Builder
    
    // Thêm chuỗi
    builder.WriteString("Hello")
    builder.WriteString(", ")
    builder.WriteString("Go!")
    
    // Kết quả
    fmt.Println(builder.String())  // Hello, Go!
    
    // Độ dài hiện tại
    fmt.Println(builder.Len())     // 10
    
    // Reset builder
    builder.Reset()
}

Xử lý lỗi

Go sử dụng một kiểu trả về đặc biệt error để xử lý lỗi thay vì throw/catch exceptions.

Cơ bản về error:

package main

import (
    "errors"
    "fmt"
)

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("không thể chia cho 0")
    }
    return a / b, nil
}

Tạo error tùy chỉnh:

// Tạo error với fmt.Errorf
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("không thể chia %d cho %d", a, b)
    }
    return a / b, nil
}

// Tạo error custom
type MyError struct {
    Operation string
    Message   string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("%s error: %s", e.Operation, e.Message)
}

func process() error {
    return &MyError{
        Operation: "math",
        Message:   "lỗi xử lý",
    }
}

Kiểm tra kiểu của error:

func main() {
    err := process()
    
    // Kiểm tra kiểu của error
    if myErr, ok := err.(*MyError); ok {
        fmt.Println("Operation:", myErr.Operation)
        fmt.Println("Message:", myErr.Message)
    }
}

Wrapping errors:

import "fmt"

func readConfig() error {
    // Đọc file cấu hình
    err := readFile()
    if err != nil {
        return fmt.Errorf("không thể đọc file cấu hình: %w", err)
    }
    return nil
}

// Unwrap errors
import "errors"

func main() {
    err := readConfig()
    
    // Kiểm tra error gốc
    if errors.Is(err, fs.ErrNotExist) {
        fmt.Println("File không tồn tại")
    }
}

Panic và recover:

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()
    
    dangerousOperation()
    fmt.Println("Done")  // Dòng này không được thực thi
}

func dangerousOperation() {
    panic("something went wrong")
}

Goroutines và đồng thời

Goroutine là một hàm hoặc phương thức chạy đồng thời với các goroutine khác trong cùng một không gian địa chỉ.

Tạo goroutine:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Tạo goroutine
    go sayHello("World")
    
    // Goroutine khác
    sayHello("Go")
    
    // Chờ để goroutine đầu tiên hoàn thành
    time.Sleep(time.Second)
}

func sayHello(name string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("Hello, %s! (%d)\n", name, i)
    }
}

Đồng bộ với WaitGroup:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    
    // Thêm 2 goroutine cần đợi
    wg.Add(2)
    
    go func() {
        defer wg.Done()  // Báo hiệu goroutine hoàn thành
        sayHello("World")
    }()
    
    go func() {
        defer wg.Done()
        sayHello("Go")
    }()
    
    // Chờ tất cả goroutine hoàn thành
    wg.Wait()
    fmt.Println("All goroutines completed")
}

Mutex để tránh race condition:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var counter int
    var mutex sync.Mutex
    var wg sync.WaitGroup
    
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            
            // Khóa để truy cập an toàn vào counter
            mutex.Lock()
            counter++
            mutex.Unlock()
        }()
    }
    
    wg.Wait()
    fmt.Println("Counter:", counter)  // 1000
}

RWMutex:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var rwMutex sync.RWMutex
    var data = make(map[string]string)
    var wg sync.WaitGroup
    
    // Writers
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            
            // Khóa để ghi
            rwMutex.Lock()
            data[fmt.Sprintf("key%d", n)] = fmt.Sprintf("value%d", n)
            time.Sleep(10 * time.Millisecond)
            rwMutex.Unlock()
        }(i)
    }
    
    // Readers
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            
            for j := 0; j < 10; j++ {
                // Khóa để đọc (nhiều goroutine có thể đọc cùng lúc)
                rwMutex.RLock()
                fmt.Println("Length:", len(data))
                time.Sleep(1 * time.Millisecond)
                rwMutex.RUnlock()
            }
        }()
    }
    
    wg.Wait()
    fmt.Println("Final map:", data)
}

Once:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    var wg sync.WaitGroup
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            
            // Hàm này chỉ được gọi một lần duy nhất
            once.Do(func() {
                fmt.Println("Initialized!")
            })
            
            fmt.Println("Worker done")
        }()
    }
    
    wg.Wait()
}

Channels

Channel là một cơ chế để goroutine giao tiếp và đồng bộ hóa với nhau.

Cơ bản về channel:

package main

import "fmt"

func main() {
    // Tạo channel
    ch := make(chan int)
    
    // Gửi dữ liệu vào channel (trong goroutine khác)
    go func() {
        ch <- 42  // Gửi giá trị 42 vào channel
    }()
    
    // Nhận dữ liệu từ channel
    value := <-ch
    fmt.Println(value)  // 42
}

Buffered channel:

func main() {
    // Tạo buffered channel với capacity là 3
    ch := make(chan int, 3)
    
    // Có thể gửi 3 giá trị mà không cần goroutine nào nhận
    ch <- 1
    ch <- 2
    ch <- 3
    
    // Nhận giá trị
    fmt.Println(<-ch)  // 1
    fmt.Println(<-ch)  // 2
    fmt.Println(<-ch)  // 3
}

Đóng channel:

func main() {
    ch := make(chan int, 3)
    
    // Gửi dữ liệu
    ch <- 1
    ch <- 2
    ch <- 3
    
    // Đóng channel
    close(ch)
    
    // Kiểm tra channel đã đóng
    v, ok := <-ch
    fmt.Println(v, ok)  // 1, true
    
    v, ok = <-ch
    fmt.Println(v, ok)  // 2, true
    
    v, ok = <-ch
    fmt.Println(v, ok)  // 3, true
    
    v, ok = <-ch
    fmt.Println(v, ok)  // 0, false (channel đã đóng)
}

Lặp qua channel:

func main() {
    ch := make(chan int, 5)
    
    // Gửi dữ liệu
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
    
    // Lặp qua tất cả các giá trị trong channel
    for v := range ch {
        fmt.Println(v)
    }
}

Select:

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(time.Second)
        ch1 <- "one"
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "two"
    }()
    
    // Đợi 2 channel
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received from ch1:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received from ch2:", msg2)
        case <-time.After(3 * time.Second):
            fmt.Println("Timeout")
            return
        }
    }
}

Channel chỉ gửi hoặc chỉ nhận:

// Channel chỉ gửi
func send(ch chan<- int) {
    ch <- 42
}

// Channel chỉ nhận
func receive(ch <-chan int) {
    val := <-ch
    fmt.Println(val)
}

func main() {
    ch := make(chan int)
    
    go send(ch)
    receive(ch)
}

Context

Context giúp quản lý goroutine, truyền tín hiệu hủy bỏ và deadline.

Sử dụng context cơ bản:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Tạo context với cancel
    ctx, cancel := context.WithCancel(context.Background())
    
    // Đảm bảo gọi cancel để tránh rò rỉ tài nguyên
    defer cancel()
    
    // Khởi chạy goroutine với context
    go doSomething(ctx)
    
    // Chờ 2 giây
    time.Sleep(2 * time.Second)
    
    // Hủy bỏ context
    cancel()
    
    // Chờ cho goroutine kết thúc
    time.Sleep(100 * time.Millisecond)
    fmt.Println("Main: exiting")
}

func doSomething(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("doSomething: cancelled")
            return
        default:
            fmt.Println("doSomething: working...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

Context với deadline:

func main() {
    // Tạo context với timeout 3 giây
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    
    go doSomething(ctx)
    
    // Chờ cho đến khi context done
    <-ctx.Done()
    fmt.Println("Main: context done with error:", ctx.Err())
}

Context với value:

func main() {
    // Tạo context gốc
    ctx := context.Background()
    
    // Thêm giá trị vào context
    ctx = context.WithValue(ctx, "userID", 123)
    ctx = context.WithValue(ctx, "authToken", "abc123")
    
    // Khởi chạy goroutine với context
    go processRequest(ctx)
    
    time.Sleep(time.Second)
}

func processRequest(ctx context.Context) {
    // Lấy giá trị từ context
    userID := ctx.Value("userID")
    authToken := ctx.Value("authToken")
    
    fmt.Printf("Processing request for user %v with auth token %v\n", userID, authToken)
}

Context trong HTTP request:

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    // Lấy context từ request
    ctx := r.Context()
    
    // Tạo context với timeout
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()
    
    // Khởi chạy xử lý trong goroutine
    result := make(chan string, 1)
    go func() {
        // Mô phỏng xử lý tốn thời gian
        time.Sleep(3 * time.Second)
        result <- "Processing completed"
    }()
    
    // Đợi kết quả hoặc context timeout
    select {
    case <-ctx.Done():
        // Context đã hết hạn
        fmt.Fprint(w, "Request timed out")
        return
    case res := <-result:
        // Nhận được kết quả
        fmt.Fprint(w, res)
    }
}

Làm việc với file

Đọc file:

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    // Đọc toàn bộ file
    data, err := ioutil.ReadFile("file.txt")
    if err != nil {
        fmt.Println("Error reading file:", err)
        return
    }
    
    fmt.Println("File content:", string(data))
    
    // Đọc file với os.Open
    file, err := os.Open("file.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()  // Đảm bảo đóng file
    
    // Đọc một buffer nhỏ
    buffer := make([]byte, 100)
    n, err := file.Read(buffer)
    if err != nil {
        fmt.Println("Error reading:", err)
        return
    }
    
    fmt.Printf("Read %d bytes: %s\n", n, buffer[:n])
}

Ghi file:

func main() {
    // Ghi toàn bộ dữ liệu
    data := []byte("Hello, Go file I/O!")
    err := ioutil.WriteFile("output.txt", data, 0644)
    if err != nil {
        fmt.Println("Error writing file:", err)
        return
    }
    
    // Ghi với os.Create
    file, err := os.Create("output2.txt")
    if err != nil {
        fmt.Println("Error creating file:", err)
        return
    }
    defer file.Close()
    
    n, err := file.Write([]byte("Writing data to a file in Go.\n"))
    if err != nil {
        fmt.Println("Error writing:", err)
        return
    }
    
    fmt.Printf("Wrote %d bytes\n", n)
    
    // Ghi chuỗi
    n, err = file.WriteString("Writing string to a file.\n")
    if err != nil {
        fmt.Println("Error writing string:", err)
        return
    }
    
    fmt.Printf("Wrote %d more bytes\n", n)
}

Đọc và ghi theo dòng:

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // Đọc theo dòng
    file, err := os.Open("file.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    lineNum := 1
    
    for scanner.Scan() {
        fmt.Printf("Line %d: %s\n", lineNum, scanner.Text())
        lineNum++
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Println("Error scanning:", err)
    }
    
    // Ghi theo dòng
    outFile, err := os.Create("lines.txt")
    if err != nil {
        fmt.Println("Error creating file:", err)
        return
    }
    defer outFile.Close()
    
    writer := bufio.NewWriter(outFile)
    
    for i := 1; i <= 5; i++ {
        _, err := writer.WriteString(fmt.Sprintf("Line %d\n", i))
        if err != nil {
            fmt.Println("Error writing:", err)
            return
        }
    }
    
    // Flush đảm bảo dữ liệu được ghi xuống file
    writer.Flush()
}

Làm việc với thư mục:

func main() {
    // Tạo thư mục
    err := os.Mkdir("testdir", 0755)
    if err != nil && !os.IsExist(err) {
        fmt.Println("Error creating directory:", err)
        return
    }
    
    // Tạo thư mục nested
    err = os.MkdirAll("path/to/nested/dir", 0755)
    if err != nil {
        fmt.Println("Error creating nested directory:", err)
        return
    }
    
    // Đọc thư mục
    dir, err := os.Open(".")
    if err != nil {
        fmt.Println("Error opening directory:", err)
        return
    }
    defer dir.Close()
    
    files, err := dir.Readdir(-1)  // -1 để đọc tất cả các mục
    if err != nil {
        fmt.Println("Error reading directory:", err)
        return
    }
    
    for _, file := range files {
        fileType := "File"
        if file.IsDir() {
            fileType = "Directory"
        }
        fmt.Printf("%s: %s, Size: %d bytes\n", fileType, file.Name(), file.Size())
    }
}

Xử lý JSON

Marshal (Object to JSON):

package main

import (
    "encoding/json"
    "fmt"
)

// Struct với JSON tags
type Person struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Age       int    `json:"age"`
    Email     string `json:"email,omitempty"`  // Bỏ qua nếu trống
    Address   *Address `json:"address,omitempty"`
}

type Address struct {
    Street  string `json:"street"`
    City    string `json:"city"`
    Country string `json:"country"`
}

func main() {
    // Khởi tạo struct
    person := Person{
        FirstName: "John",
        LastName:  "Doe",
        Age:       30,
        Email:     "john.doe@example.com",
        Address: &Address{
            Street:  "123 Main St",
            City:    "New York",
            Country: "USA",
        },
    }
    
    // Chuyển đổi struct thành JSON
    jsonData, err := json.Marshal(person)
    if err != nil {
        fmt.Println("Error marshaling JSON:", err)
        return
    }
    
    fmt.Println(string(jsonData))
    
    // JSON đẹp hơn (pretty print)
    jsonPretty, err := json.MarshalIndent(person, "", "  ")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    
    fmt.Println(string(jsonPretty))
}

Unmarshal (JSON to Object):

func main() {
    // JSON string
    jsonStr := `{
        "first_name": "Jane",
        "last_name": "Smith",
        "age": 25,
        "email": "jane.smith@example.com",
        "address": {
            "street": "456 Elm St",
            "city": "Boston",
            "country": "USA"
        }
    }`
    
    // Khai báo biến để lưu kết quả
    var person Person
    
    // Chuyển đổi JSON thành struct
    err := json.Unmarshal([]byte(jsonStr), &person)
    if err != nil {
        fmt.Println("Error unmarshaling JSON:", err)
        return
    }
    
    // In kết quả
    fmt.Printf("Person: %+v\n", person)
    fmt.Printf("Address: %+v\n", *person.Address)
}

Encoder và Decoder:

import (
    "bytes"
    "encoding/json"
    "fmt"
    "strings"
)

func main() {
    // JSON Encoder
    var buf bytes.Buffer
    encoder := json.NewEncoder(&buf)
    
    person := Person{FirstName: "Alice", LastName: "Johnson", Age: 28}
    encoder.Encode(person)
    
    fmt.Println("Encoded JSON:")
    fmt.Println(buf.String())
    
    // JSON Decoder
    jsonReader := strings.NewReader(`{"first_name":"Bob","last_name":"Brown","age":35}`)
    decoder := json.NewDecoder(jsonReader)
    
    var newPerson Person
    decoder.Decode(&newPerson)
    
    fmt.Printf("Decoded Person: %+v\n", newPerson)
}

Xử lý JSON động:

func main() {
    // JSON với cấu trúc không xác định
    jsonStr := `{
        "name": "Product",
        "price": 19.99,
        "in_stock": true,
        "attributes": {
            "color": "red",
            "size": "medium"
        }
    }`
    
    // Sử dụng map[string]interface{} cho JSON objects
    var result map[string]interface{}
    
    err := json.Unmarshal([]byte(jsonStr), &result)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    
    // Truy cập các trường
    fmt.Println("Name:", result["name"])
    fmt.Println("Price:", result["price"])
    
    // Truy cập nested objects
    attrs := result["attributes"].(map[string]interface{})
    fmt.Println("Color:", attrs["color"])
    fmt.Println("Size:", attrs["size"])
    
    // Xử lý JSON array
    jsonArray := `[
        {"name": "Apple", "price": 1.99},
        {"name": "Orange", "price": 2.49},
        {"name": "Banana", "price": 0.99}
    ]`
    
    var fruits []map[string]interface{}
    
    json.Unmarshal([]byte(jsonArray), &fruits)
    
    for i, fruit := range fruits {
        fmt.Printf("Fruit %d: %s, Price: $%.2f\n", i+1, fruit["name"], fruit["price"])
    }
}

Làm việc với database

SQL Database (sử dụng package database/sql):

package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/go-sql-driver/mysql"  // Driver cho MySQL
)

func main() {
    // Kết nối đến database
    // Format: username:password@tcp(host:port)/dbname
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/testdb")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // Kiểm tra kết nối
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Println("Connected to database!")
    
    // Tạo bảng
    _, err = db.Exec(`
    CREATE TABLE IF NOT EXISTS users (
        id INT AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(50) NOT NULL,
        email VARCHAR(100) NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )`)
    
    if err != nil {
        log.Fatal(err)
    }
    
    // Insert dữ liệu
    result, err := db.Exec("INSERT INTO users (username, email) VALUES (?, ?)", "johndoe", "john@example.com")
    if err != nil {
        log.Fatal(err)
    }
    
    // Lấy ID của bản ghi vừa tạo
    userID, err := result.LastInsertId()
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("User created with ID: %d\n", userID)
    
    // Query một bản ghi
    var (
        id        int
        username  string
        email     string
        createdAt string
    )
    
    row := db.QueryRow("SELECT * FROM users WHERE id = ?", userID)
    err = row.Scan(&id, &username, &email, &createdAt)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("User: %d, %s, %s, %s\n", id, username, email, createdAt)
    
    // Query nhiều bản ghi
    rows, err := db.Query("SELECT id, username FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
    
    fmt.Println("Users:")
    for rows.Next() {
        var id int
        var username string
        if err := rows đầy đủ
var name string = "Go"

// Khai báo không chỉ định kiểu dữ liệu
var age = 30

// Khai báo ngắn gọn (chỉ sử dụng trong function)
shortName := "Go Language"

Khai báo nhiều biến:

var (
    name string = "Go"
    age  int    = 30
    isAwesome bool = true
)

// Hoặc
var a, b, c int = 1, 2, 3

Hằng số:

const Pi = 3.14159
const (
    StatusOK = 200
    StatusNotFound = 404
)

// iota là bộ đếm tự động
const (
    Sunday = iota   // 0
    Monday          // 1 
    Tuesday         // 2
    Wednesday       // 3
    Thursday        // 4
    Friday          // 5
    Saturday        // 6
)

Toán tử

Toán tử số học:

  • +: Cộng

  • -: Trừ

  • *: Nhân

  • /: Chia

  • %: Chia lấy phần dư

  • ++: Tăng 1

  • --: Giảm 1

Toán tử so sánh:

  • ==: Bằng

  • !=: Khác

  • <: Nhỏ hơn

  • >: Lớn hơn

  • <=: Nhỏ hơn hoặc bằng

  • >=: Lớn hơn hoặc bằng

Toán tử logic:

  • &&: Và

  • ||: Hoặc

  • !: Phủ định

Toán tử gán:

  • =, +=, -=, *=, /=, %=

Ví dụ:

package main

import "fmt"

func main() {
    a, b := 10, 3
    
    // Toán tử số học
    fmt.Println("a + b =", a+b)
    fmt.Println("a - b =", a-b)
    fmt.Println("a * b =", a*b)
    fmt.Println("a / b =", a/b)
    fmt.Println("a % b =", a%b)
    
    // Toán tử so sánh
    fmt.Println("a == b:", a == b)
    fmt.Println("a != b:", a != b)
    fmt.Println("a > b:", a > b)
    
    // Toán tử logic
    c, d := true, false
    fmt.Println("c && d:", c && d)
    fmt.Println("c || d:", c || d)
    fmt.Println("!c:", !c)
}

Cấu trúc điều khiển

If-else:

if condition {
    // code
} else if anotherCondition {
    // code
} else {
    // code
}

// If với statement ngắn
if x := calculateValue(); x > 10 {
    fmt.Println("x lớn hơn 10")
} else {
    fmt.Println("x không lớn hơn 10")
}

Switch:

switch value {
case 1:
    // code
case 2, 3:
    // code cho cả 2 và 3
default:
    // code
}

// Switch không cần biến so sánh
switch {
case age < 18:
    fmt.Println("Chưa đủ tuổi")
case age >= 18 && age < 60:
    fmt.Println("Người trưởng thành")
default:
    fmt.Println("Người cao tuổi")
}

For:

// Cú pháp cơ bản
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// Giống while trong các ngôn ngữ khác
for condition {
    // code
}

// Vòng lặp vô hạn
for {
    // code
    if shouldBreak {
        break
    }
}

// Vòng lặp với range
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
    fmt.Printf("Index: %d, Value: %d\n", index, value)
}

// Nếu không cần index
for _, value := range numbers {
    fmt.Println(value)
}

Break và Continue:

for i := 0; i < 10; i++ {
    if i == 5 {
        continue  // Bỏ qua phần còn lại của vòng lặp
    }
    if i == 8 {
        break     // Thoát khỏi vòng lặp
    }
    fmt.Println(i)
}

Goto (ít khi được sử dụng):

func main() {
    goto myLabel
    fmt.Println("Đoạn này bị bỏ qua")
    
myLabel:
    fmt.Println("Nhảy đến đây")
}

Hàm

Khai báo hàm cơ bản:

func functionName(param1 type1, param2 type2) returnType {
    // code
    return returnValue
}

// Ví dụ
func add(a, b int) int {
    return a + b
}

Trả về nhiều giá trị:

func getNameAndAge() (string, int) {
    return "Gopher", 10
}

// Sử dụng
name, age := getNameAndAge()

Trả về giá trị được đặt tên:

func divide(a, b float64) (quotient float64, remainder float64) {
    quotient = a / b
    remainder = math.Mod(a, b)
    return  // Trả về quotient và remainder
}

Variadic functions (hàm với số lượng tham số không cố định):

func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

// Sử dụng
result := sum(1, 2, 3, 4, 5)

Hàm như kiểu dữ liệu:

func mathOperation(a, b int, op func(int, int) int) int {
    return op(a, b)
}

// Định nghĩa hàm
func multiply(x, y int) int {
    return x * y
}

// Sử dụng
result := mathOperation(5, 3, multiply)  // 15

Hàm ẩn danh (anonymous functions):

func main() {
    // Hàm ẩn danh
    add := func(a, b int) int {
        return a + b
    }
    
    fmt.Println(add(3, 4))  // 7
    
    // Hàm ẩn danh được gọi ngay lập tức
    result := func(a, b int) int {
        return a * b
    }(4, 5)
    
    fmt.Println(result)  // 20
}

Closure:

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    counter := makeCounter()
    fmt.Println(counter())  // 1
    fmt.Println(counter())  // 2
    fmt.Println(counter())  // 3
}

Con trỏ

Con trỏ lưu địa chỉ của một biến.

Khai báo và sử dụng con trỏ:

func main() {
    var x int = 10
    var p *int = &x  // p lưu địa chỉ của x
    
    fmt.Println("Giá trị của x:", x)     // 10
    fmt.Println("Địa chỉ của x:", &x)    // Địa chỉ của x (0xc000000000 chẳng hạn)
    fmt.Println("Giá trị của p:", p)     // Cũng là địa chỉ của x
    fmt.Println("Giá trị tại địa chỉ p trỏ tới:", *p)  // 10
    
    // Thay đổi giá trị của x thông qua con trỏ
    *p = 20
    fmt.Println("Giá trị mới của x:", x)  // 20
}

Tạo biến con trỏ mới:

func main() {
    ptr := new(int)  // Tạo con trỏ đến một giá trị int mới
    *ptr = 42
    fmt.Println(*ptr)  // 42
}

Truyền con trỏ vào hàm:

func increment(n *int) {
    *n++  // Tăng giá trị mà con trỏ trỏ tới
}

func main() {
    x := 10
    increment(&x)
    fmt.Println(x)  // 11
}

Lưu ý về con trỏ:

  • Go không cho phép con trỏ trỏ đến con trỏ (pointer arithmetic) như trong C.

  • Con trỏ mặc định có giá trị nil khi chưa được khởi tạo.

  • Khi truyền con trỏ vào hàm, giá trị thực của biến có thể bị thay đổi.

Cấu trúc (Struct)

Struct là một kiểu dữ liệu tổng hợp, chứa nhiều trường với các kiểu dữ liệu khác nhau.

Khai báo và sử dụng struct:

// Khai báo struct
type Person struct {
    Name string
    Age  int
    City string
}

func main() {
    // Khởi tạo struct
    var p1 Person
    p1.Name = "Alice"
    p1.Age = 30
    p1.City = "New York"
    
    // Khởi tạo nhanh
    p2 := Person{
        Name: "Bob",
        Age:  25,
        City: "San Francisco",
    }
    
    // Khởi tạo theo thứ tự các trường (không nên dùng)
    p3 := Person{"Charlie", 35, "Boston"}
    
    fmt.Println(p1)
    fmt.Println(p2)
    fmt.Println(p3)
}

Struct lồng nhau:

type Address struct {
    Street  string
    City    string
    Country string
}

type Employee struct {
    Name    string
    Age     int
    Address Address  // Struct lồng nhau
}

func main() {
    emp := Employee{
        Name: "John",
        Age:  40,
        Address: Address{
            Street:  "123 Main St",
            City:    "Chicago",
            Country: "USA",
        },
    }
    
    fmt.Println(emp.Name)
    fmt.Println(emp.Address.City)
}

Con trỏ đến struct:

func main() {
    p := &Person{
        Name: "David",
        Age:  28,
        City: "Miami",
    }
    
    // Hai cách truy cập đều hợp lệ
    fmt.Println((*p).Name)  // David
    fmt.Println(p.Name)     // David (Go tự động dereference)
}

Phương thức cho struct:

// Phương thức cho struct Person
func (p Person) Greeting() string {
    return fmt.Sprintf("Hello, my name is %s and I'm %d years old", p.Name, p.Age)
}

// Phương thức với receiver là con trỏ (có thể thay đổi giá trị của struct)
func (p *Person) IncreaseAge() {
    p.Age++
}

func main() {
    person := Person{Name: "Eva", Age: 30}
    
    fmt.Println(person.Greeting())
    
    person.IncreaseAge()
    fmt.Println(person.Age)  // 31
}

Struct tag:

type User struct {
    Username string `json:"username"`
    Email    string `json:"email"`
    Password string `json:"-"`  // Trường password không được export khi chuyển sang JSON
}

Interface

Interface định nghĩa một tập hợp các phương thức mà một kiểu dữ liệu cần phải thực hiện.

Khai báo và sử dụng interface:

// Khai báo interface
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Struct Rectangle
type Rectangle struct {
    Width  float64
    Height float64
}

// Struct Circle
type Circle struct {
    Radius float64
}

// Rectangle thực hiện interface Shape
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Circle thực hiện interface Shape
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

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

// Hàm nhận các kiểu dữ liệu thực hiện interface Shape
func PrintShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

func main() {
    rect := Rectangle{Width: 5, Height: 3}
    circle := Circle{Radius: 2}
    
    PrintShapeInfo(rect)
    PrintShapeInfo(circle)
}

Interface rỗng:

// Interface rỗng có thể chứa bất kỳ giá trị nào
func PrintInfo(i interface{}) {
    fmt.Printf("Giá trị: %v, Kiểu: %T\n", i, i)
}

func main() {
    PrintInfo(42)           // int
    PrintInfo("hello")      // string 
    PrintInfo(true)         // bool
    PrintInfo([]int{1,2,3}) // slice
}

Type assertion:

func main() {
    var i interface{} = "hello"
    
    // Type assertion
    s, ok := i.(string)
    if ok {
        fmt.Println(s)  // hello
    }
    
    // Type assertion thất bại
    n, ok := i.(int)
    if !ok {
        fmt.Println("i không phải kiểu int")
    }
    
    // Type assertion không kiểm tra (có thể gây panic)
    // s2 := i.(string)  // OK
    // n2 := i.(int)     // Panic
}

Type switch:

func describeType(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    case bool:
        fmt.Printf("Boolean value is %v\n", v)
    default:
        fmt.Printf("Unknown type: %T\n", v)
    }
}

func main() {
    describeType(42)
    describeType("hello")
    describeType(true)
    describeType(3.14)
}

Mảng và Slice

Mảng:

Mảng trong Go có kích thước cố định và không thể thay đổi.

// Khai báo mảng
var arr1 [5]int                      // Mảng 5 phần tử kiểu int
arr2 := [3]string{"a", "b", "c"}     // Khai báo và khởi tạo
arr3 := [...]int{1, 2, 3, 4, 5}      // Compiler tự đếm số phần tử

// Truy cập phần tử
fmt.Println(arr2[1])  // b

// Gán giá trị
arr1[0] = 10
arr1[1] = 20

// Độ dài mảng
fmt.Println(len(arr1))  // 5

Slice:

Slice là một cấu trúc dữ liệu động, giống như mảng nhưng có thể thay đổi kích thước.

// Tạo slice từ mảng
arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[1:4]  // Slice có phần tử 2, 3, 4

// Tạo slice mới
slice2 := []int{1, 2, 3}     // Slice với 3 phần tử
slice3 := make([]int, 5)     // Slice với 5 phần tử, giá trị mặc định 0
slice4 := make([]int, 3, 5)  // Slice với 3 phần tử, capacity 5

// Thêm phần tử vào slice
slice2 = append(slice2, 4, 5)  // Slice: [1, 2, 3, 4, 5]

// Nối hai slice
slice5 := []int{6, 7}
slice2 = append(slice2, slice5...)  // Slice: [1, 2, 3, 4, 5, 6, 7]

// Chiều dài và dung lượng
fmt.Println(len(slice2))  // 7
fmt.Println(cap(slice2))  // Capacity (dung lượng)

Các thao tác với slice:

// Cắt slice
slice := []int{1, 2, 3, 4, 5}
slice1 := slice[1:3]  // [2, 3]
slice2 := slice[:2]   // [1, 2]
slice3 := slice[2:]   // [3, 4, 5]
slice4 := slice[:]    // [1, 2, 3, 4, 5]

// Sao chép slice
dest := make([]int, len(slice))
copy(dest, slice)

// Xóa phần tử (không có hàm xóa built-in)
// Ví dụ: Xóa phần tử tại vị trí 2
slice = append(slice[:2], slice[3:]...)  // [1, 2, 4, 5]

Lưu ý:

  • Slice là tham chiếu đến mảng, không chứa dữ liệu thực sự

  • Khi bạn truyền slice vào hàm, thay đổi nội dung slice sẽ ảnh hưởng đến mảng gốc

  • Slice có thể bị thay đổi kích thước khi sử dụng append

Map

Map là kiểu dữ liệu dùng để lưu trữ cặp key-value.

Khai báo và khởi tạo map:

// Khai báo map
var m1 map[string]int         // Nil map (chưa được khởi tạo)

// Khởi tạo map
m2 := make(map[string]int)    // Map rỗng
m3 := map[string]int{         // Map với giá trị khởi tạo
    "apple": 5,
    "banana": 8,
    "orange": 3,
}

Các thao tác với map:

// Thêm hoặc cập nhật phần tử
m2["apple"] = 5
m2["banana"] = 8

// Truy cập phần tử
fmt.Println(m2["apple"])  // 5

// Kiểm tra key tồn tại
value, exists := m3["grape"]
if exists {
    fmt.Println("grape:", value)
} else {
    fmt.Println("grape không tồn tại trong map")
}

// Xóa phần tử
delete(m3, "orange")

// Số lượng phần tử
fmt.Println(len(m3))  // 2

Lặp qua map:

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

// Chỉ lặp qua keys
for key := range m3 {
    fmt.Println(key)
}

Lưu ý:

  • Thứ tự lặp qua map không được đảm bảo

  • Map trong Go không phải là thread-safe, đồng thời truy cập và sửa đổi map cần được đồng bộ

  • Map là kiểu tham chiếu, khi bạn truyền map vào hàm, thay đổi trong hàm sẽ ảnh hưởng đến map gốc

Updated on