Tổng quan về Chuỗi, Hằng số, Con trỏ và Mảng trong Go

Xử lý Chuỗi (Strings)

Trong Go, chuỗi là một dãy byte bất biến. Khi làm việc với chuỗi, cần lưu ý sự khác biệt giữa độ dài byte và độ dài ký tự, đặc biệt là với các ký tự Unicode (như tiếng Việt có dấu).

Để lấy độ dài theo byte, ta dùng hàm len(). Tuy nhiên, để xử lý chính xác từng ký tự (rune), nên chuyển đổi chuỗi sang dạng []rune. Các ký tự ASCII chiếm 1 byte, trong khi các ký tự Unicode phức tạp có thể chiếm nhiều byte hơn.

package main

import "fmt"

func main() {
    text := "Lập trình"
    
    // Lấy độ dài byte
    fmt.Println("Độ dài byte:", len(text)) // Kết quả phụ thuộc vào encoding UTF-8
    
    // Lấy ký tự tại vị trí byte thứ 2
    // Lưu ý: truy cập trực tiếp theo byte có thể gây lỗi hiển thị nếu gặp ký tự multi-byte
    byteVal := text[2] 
    fmt.Printf("Ký tự byte: %c (type: %T)\n", byteVal, byteVal)
    
    // Cắt chuỗi (Slicing): lấy từ byte thứ 0 đến 4
    fmt.Println("Cắt chuỗi:", text[0:4])
    
    // Dùng range để duyệt từng rune (ký tự Unicode)
    for index, char := range text {
        fmt.Printf("Vị trí: %d, Ký tự: %c\n", index, char)
    }
}

Go cung cấp package strings với nhiều tiện ích hữu ích cho thao tác chuỗi:

package main

import (
    "fmt"
    "strings"
)

func main() {
    data := "Golang Programming"
    
    // Tìm vị trí xuất hiện đầu tiên
    fmt.Println(strings.Index(data, "go"))
    
    // Kiểm tra tiền tố và hậu tố
    fmt.Println(strings.HasPrefix(data, "Go"))
    fmt.Println(strings.HasSuffix(data, "ing"))
    
    // Thay thế chuỗi con (tham số cuối cùng là số lần thay thế, -1 là thay tất cả)
    fmt.Println(strings.Replace(data, "Go", "Java", -1))
    
    // Chuyển đổi chữ hoa/thường
    fmt.Println(strings.ToLower(data))
    fmt.Println(strings.ToUpper(data))
}

Hằng số (Constants)

Hằng số là các giá trị bất biến được xác định tại thời điểm biên dịch. Khác với biến, hằng số không thể thay đổi giá trị sau khi khai báo và bắt buộc phải được gán giá trị ngay lập tức. Ta sử dụng từ khóa const để định nghĩa.

func main() {
    // Khai báo hằng số có kiểu cụ thể
    const appName string = "MyApp"
    
    // Khai báo hằng số không có kiểu (type-less)
    const maxLimit = 100
    const pi = 3.14
    
    // Hằng số có thể là biểu thức hằng
    const area = pi * 10 * 10
    
    fmt.Printf("%T, %v\n", maxLimit, maxLimit)
    fmt.Println(area)
}

Khai báo nhóm hằng số giúp mã nguồn gọn gàng hơn. Nếu một hằng số trong nhóm không được gán giá trị, nó sẽ thừa kế giá trị của hằng số liền trước nó.

const (
    StatusActive   = 1
    StatusInactive // Sẽ nhận giá trị 1
    StatusPending  // Sẽ nhận giá trị 1
)

fmt.Println(StatusActive, StatusInactive, StatusPending) // 1 1 1

Generater Iota: iota là một bộ đếm đặc biệt trong Go, bắt đầu từ 0 và tự động tăng 1 cho mỗi dòng hằng số tiếp theo trong cùng một khối.

const (
    Sunday = iota // 0
    Monday        // 1
    Tuesday       // 2
)

// Iota trong biểu thức phức tạp
const (
    Read  = 1 << iota // 1 (binary 001)
    Write             // 2 (binary 010)
    Execute           // 4 (binary 100)
)

fmt.Println(Read, Write, Execute)

Con trỏ (Pointers)

Một con trỏ lưu trữ địa chỉ bộ nhớ của một biến. Toán tử & lấy địa chỉ của biến, còn toán tử * truy xuất giá trị tại địa chỉ mà con trỏ đang trỏ tới (dereference).

func main() {
    val := 50
    // ptr là con trỏ kiểu int, chứa địa chỉ của val
    ptr := &val
    
    fmt.Println("Giá trị val:", val)
    fmt.Println("Địa chỉ val:", &val)
    fmt.Println("Con trỏ ptr trỏ tới:", ptr)
    fmt.Println("Giá trị tại địa chỉ ptr:", *ptr)
    
    // Thay đổi giá trị thông qua con trỏ
    *ptr = 100
    fmt.Println("Giá trị mới của val:", val) // 100
}

Khai báo một biến con trỏ mà không gán địa chỉ sẽ mặc định là nil. Việc truy xuất giá trị của con trỏ nil sẽ gây lỗi runtime (panic).

Hàm new

Thay vì phải khai báo một biến bình thường rồi lấy địa chỉ của nó để gán cho con trỏ, Go cung cấp hàm new(). Hàm này cấp phát một vùng nhớ cho kiểu dữ liệu chỉ định, khởi tạo giá trị zero (0, "", false...) và trả về con trỏ trỏ tới vùng nhớ đó.

func main() {
    // Cấp phát vùng nhớ cho một int
    numPtr := new(int)
    
    fmt.Println("Giá trị ban đầu:", *numPtr) // 0
    
    *numPtr = 999
    fmt.Println("Giá trị sau khi gán:", *numPtr) // 999
    
    // So sánh với con trỏ nil
    var emptyPtr *string
    if emptyPtr == nil {
        fmt.Println("Con trỏ rỗng")
    }
}

Số ngẫu nhiên

Package math/rand được sử dụng để tạo số ngẫu nhiên. Tuy nhiên, để đảm bảo mỗi lần chạy chương trình đều tạo ra dãy số khác nhau, ta cần thiết lập "seed" (hạt giống) ngẫu nhiên, thường là thời gian hiện tại.

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // Thiết lập seed dựa trên thời gian nano giây hiện tại
    rand.Seed(time.Now().UnixNano())
    
    // Tạo số ngẫu nhiên trong khoảng [0, n)
    fmt.Println("Số ngẫu nhiên:", rand.Intn(100))
    fmt.Println("Số thực ngẫu nhiên:", rand.Float64())
}

Mảng (Arrays)

Mảng trong Go là một tập hợp các phần tử có cùng kiểu dữ liệu với độ dài cố định. Cú pháp khai báo là [N]KieuDuLieu. Khi gán một mảng cho một mảng khác, Go sẽ sao chép toàn bộ giá trị (copy by value), thay vì tham chiếu.

func main() {
    // Khai báo mảng số nguyên có 5 phần tử
    var scores [5]int
    fmt.Println(scores) // [0 0 0 0 0]
    
    // Khởi tạo giá trị cho mảng
    primes := [4]int{2, 3, 5, 7}
    fmt.Println(primes)
    
    // Để trình biên dịch tự động đếm độ dài, dùng ...
    languages := [...]string{"Go", "Python", "C++"}
    fmt.Println(len(languages)) // 3
    
    // Mảng là value type
    listA := [3]int{1, 2, 3}
    listB := listA
    listB[0] = 99
    fmt.Println(listA) // [1 2 3] (không bị ảnh hưởng)
    fmt.Println(listB) // [99 2 3]
}

Vòng lặp (Loops)

Go chỉ có một từ khóa vòng lặp duy nhất là for, nhưng nó rất linh hoạt và có thể thay thế cho các loại vòng lặp truyền thống khác.

func main() {
    items := [3]string{"A", "B", "C"}
    
    // Kiểu vòng lặp điều kiện (giống while trong các ngôn ngữ khác)
    i := 0
    for i < len(items) {
        fmt.Println(items[i])
        i++
    }
    
    // Vòng lặp với range (thường dùng để duyệt mảng/slice)
    for index, value := range items {
        fmt.Printf("Index: %d, Value: %s\n", index, value)
    }
    
    // Ví dụ sắp xếp nổi bọt (Bubble Sort) đơn giản
    nums := [5]int{64, 34, 25, 12, 22}
    n := len(nums)
    for i := 0; i < n-1; i++ {
        for j := 0; j < n-i-1; j++ {
            if nums[j] > nums[j+1] {
                // Hoán đổi giá trị
                nums[j], nums[j+1] = nums[j+1], nums[j]
            }
        }
    }
    fmt.Println("Mảng sau khi sắp xếp:", nums)
}

Thẻ: golang strings pointers arrays constants

Đăng vào ngày 26 tháng 5 lúc 00:15