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)
}