Kiểu dữ liệu nâng cao
Chương này gồm 4 mục — đọc mục nào cần, không phải đọc hết 1 lượt.
A1. Slice — Sâu về cấu trúc & các bẫy thường gặp
🧠 Bản chất
Slice không phải array. Nó là một struct 3 trường bên trong runtime:
type slice struct {
ptr *element // Con trỏ đến array gốc
len int // Số phần tử đang có
cap int // Sức chứa tối đa trước khi phải cấp phát lại
}
💻 Ví dụ minh hoạ
s := make([]int, 3, 5) // len=3, cap=5
fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 5
s = append(s, 1, 2) // len=5, cap=5 (chưa vượt cap)
s = append(s, 99) // len=6, cap tăng gấp đôi → cap=10 (cấp phát mới!)
⚠️ Bẫy #1 — Slice chia sẻ array gốc
a := []int{1, 2, 3, 4, 5}
b := a[1:3] // b = [2 3], nhưng vẫn dùng chung array với a
b[0] = 999
fmt.Println(a) // [1 999 3 4 5] — a bị đổi theo!
👉 Cách xử lý an toàn: dùng copy để tách hẳn ra:
b := make([]int, 2)
copy(b, a[1:3])
⚠️ Bẫy #2 — append vào slice truyền vào hàm
func addOne(s []int) { s = append(s, 1) } // Không sửa được slice gốc nếu cap không đổi
👉 Cách đúng: trả về slice mới hoặc dùng con trỏ.
⚠️ Bẫy #3 — Vòng lặp với con trỏ (rất hay gặp)
users := []User{{"A"}, {"B"}, {"C"}}
var ptrs []*User
for _, u := range users {
ptrs = append(ptrs, &u) // ❌ SAI: u là biến tái sử dụng, tất cả pointer cùng trỏ vào 1 chỗ
}
👉 Từ Go 1.22 đã sửa mặc định, nhưng vẫn nên viết:
for i := range users {
ptrs = append(ptrs, &users[i]) // ✅ Đúng
}
A2. Map — Dictionary của Go
💻 Cú pháp cơ bản
m := map[string]int{
"apple": 10,
"banana": 20,
}
m["cherry"] = 30 // Thêm/sửa
delete(m, "apple") // Xoá
value, ok := m["banana"] // ok = true nếu tồn tại
⚠️ Bẫy #1 — Truy cập key không tồn tại
n := m["notexist"] // Trả về zero value (0 cho int, "" cho string), KHÔNG panic
👉 Luôn dùng dạng comma-ok để phân biệt "0 thật" và "không có key":
if v, ok := m["key"]; ok { ... }
⚠️ Bẫy #2 — Không an toàn khi concurrency
Đọc/ghi map từ nhiều goroutine → panic. Giải pháp: dùng sync.Mutex bọc quanh, hoặc
sync.Map (xem chương "Concurrency nâng cao").
⚠️ Bẫy #3 — Thứ tự lặp không ổn định
for k, v := range m { ... } // Mỗi lần chạy có thể khác thứ tự
👉 Nếu cần thứ tự: lấy keys ra slice, sort.Strings(keys), rồi lặp qua slice.
⚠️ Bẫy #4 — Không thể &m["key"]
p := &m["apple"] // ❌ Compile error
Vì map có thể cấp phát lại khi lớn lên → pointer sẽ trỏ vào rác.
A3. String, byte, rune
🧠 Bản chất
stringbất biến (immutable) — không sửa được từng ký tự.byte=uint8— 1 byte (ASCII).rune=int32— 1 ký tự Unicode (có thể nhiều byte).
💻 Ví dụ
s := "Xin chào"
fmt.Println(len(s)) // 10 (số BYTE, không phải ký tự)
fmt.Println(len([]rune(s))) // 8 (số ký tự thật)
for i, r := range s { // range trên string tự động decode rune
fmt.Printf("%d: %c\n", i, r)
}
⚠️ Bẫy
s := "chào"
fmt.Println(s[0]) // 99 (byte 'c'), OK
fmt.Println(s[1]) // 104 (byte 'h'), OK
// Nhưng nếu là "🚀" thì s[0] không phải cả emoji!
🔧 Nối chuỗi hiệu quả
// ❌ Chậm: mỗi + tạo string mới
result := ""
for _, s := range list {
result += s
}
// ✅ Nhanh: dùng strings.Builder
var b strings.Builder
for _, s := range list {
b.WriteString(s)
}
result := b.String()
A4. Constants & iota
const Pi = 3.14
// iota = bộ đếm tự tăng từ 0 trong khối const
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
)
// Ứng dụng: enum flag với bit shift
const (
Read = 1 << iota // 1 (0001)
Write // 2 (0010)
Execute // 4 (0100)
)
perm := Read | Write // 3 (0011)