🗄️ Kệ Đựng Gia Vị - Arrays & Slices
Chào mừng đến với bài học về Arrays & Slices - cách sắp xếp và quản lý nguyên liệu trong Bếp Á!
🎯 Món Ăn Hôm Nay
Tưởng tượng bạn là đầu bếp cần sắp xếp gia vị:
- Kệ cố định (Array): 5 lọ gia vị cố định trên tường - muối, tiêu, đường, bột ngọt, nước mắm
- Túi linh hoạt (Slice): Rổ gia vị có thể thêm bớt tùy ý - hôm nay 3 loại, ngày mai 10 loại
Đó chính là Arrays và Slices - hai cách lưu trữ nhiều nguyên liệu cùng lúc trong Go!
🥘 Arrays & Slices Là Gì?
Array = Kệ gia vị cố định (số lượng ô cố định, không thay đổi được) Slice = Túi đựng linh hoạt (có thể thêm bớt nguyên liệu)
// Array - Kệ cố định 5 gia vị
var keGiaVi [5]string = [5]string{"Muối", "Tiêu", "Đường", "Bột ngọt", "Nước mắm"}
// Slice - Túi linh hoạt
tuiGiaVi := []string{"Muối", "Tiêu", "Đường"}
tuiGiaVi = append(tuiGiaVi, "Tỏi", "Ớt") // Thêm được!
🍜 Ẩn Dụ Nhà Hàng:
- Array = Kệ gia vị cố định trên tường (5 ô luôn luôn là 5 ô)
- Slice = Rổ gia vị di động (hôm nay 3 loại, mai 10 loại đều được)
- Index = Vị trí trên kệ (ô số 0, ô số 1, ô số 2...)
- append() = Thêm gia vị vào rổ
👨🍳 Câu Chuyện Trong Bếp
Sáng nay, đầu bếp Lan mở nhà hàng phở. Cô cần sắp xếp gia vị:
Tình huống 1: Kệ gia vị cố định (Array)
Trên tường có một kệ gỗ với 5 ô cố định. Mỗi sáng, Lan luôn đặt đúng 5 loại gia vị chính vào:
keGiaViChinh := [5]string{"Muối", "Tiêu", "Đường", "Bột ngọt", "Nước mắm"}
fmt.Println("🧂 Gia vị thứ 1:", keGiaViChinh[0]) // Muối
fmt.Println("📏 Tổng số gia vị:", len(keGiaViChinh)) // 5
Kệ này luôn có đúng 5 ô. Không thể thêm ô thứ 6, cũng không thể bỏ bớt. Cố định!
Tình huống 2: Rổ nguyên liệu linh hoạt (Slice)
Bên cạnh đó, Lan có một rổ đựng nguyên liệu cho món đặc biệt hôm nay:
// Ban đầu chỉ có 2 món
ropNguyenLieu := []string{"Thịt bò", "Bánh phở"}
// Khách đặt thêm phở đặc biệt, cần thêm nguyên liệu
ropNguyenLieu = append(ropNguyenLieu, "Gân", "Nạm", "Vè dòn")
fmt.Println("🥘 Tổng nguyên liệu:", len(ropNguyenLieu)) // 5
Rổ này linh hoạt! Có thể thêm bớt nguyên liệu tùy ý theo nhu cầu món ăn.
🎉 Kết luận: Dùng Array khi biết trước số lượng cố định, dùng Slice khi cần linh hoạt!
📝 Công Thức Nấu (Code Examples)
Ví Dụ 1: Array - Kệ Gia Vị Cố Định
package main
import "fmt"
func main() {
// Cách 1: Khai báo kệ rỗng với 5 ô
var keGiaVi [5]string
// Đặt gia vị vào từng ô (index bắt đầu từ 0)
keGiaVi[0] = "Muối"
keGiaVi[1] = "Tiêu"
keGiaVi[2] = "Đường"
keGiaVi[3] = "Bột ngọt"
keGiaVi[4] = "Nước mắm"
// In ra kệ gia vị
fmt.Println("🗄️ KỆ GIA VỊ CỐ ĐỊNH:")
for i, ten := range keGiaVi {
fmt.Printf("Ô %d: %s\n", i, ten)
}
// Cách 2: Tạo kệ với gia vị sẵn
keGiaViDacBiet := [3]string{"Tỏi", "Ớt", "Gừng"}
fmt.Println("\n🌶️ Gia vị đặc biệt:", keGiaViDacBiet)
// Lấy gia vị từ vị trí cụ thể
fmt.Println("🥄 Gia vị ở ô 1:", keGiaViDacBiet[1]) // Ớt
}
🔍 Giải Thích:
[5]string= Kệ có 5 ô, mỗi ô chứa một stringkeGiaVi[0]= Truy cập ô số 0 (ô đầu tiên)- Array có kích thước cố định, không thể thay đổi!
Ví Dụ 2: Slice - Rổ Nguyên Liệu Linh Hoạt
package main
import "fmt"
func main() {
// Tạo rổ rỗng
ropNguyenLieu := []string{}
// Thêm nguyên liệu vào rổ
ropNguyenLieu = append(ropNguyenLieu, "Thịt bò")
ropNguyenLieu = append(ropNguyenLieu, "Bánh phở")
ropNguyenLieu = append(ropNguyenLieu, "Hành tây")
fmt.Println("👜 RỔ NGUYÊN LIỆU:", ropNguyenLieu)
fmt.Println("📊 Số lượng:", len(ropNguyenLieu))
// Thêm nhiều nguyên liệu cùng lúc
ropNguyenLieu = append(ropNguyenLieu, "Ngò", "Chanh", "Ớt")
fmt.Println("\n👜 SAU KHI THÊM:", ropNguyenLieu)
fmt.Println("📊 Số lượng mới:", len(ropNguyenLieu))
}
🔍 Giải Thích:
[]string{}= Rổ rỗng, không giới hạn kích thướcappend()= Thêm nguyên liệu vào rổ- Slice tự động mở rộng khi cần!
Ví Dụ 3: Tạo Menu Món Ăn Với Slice
package main
import "fmt"
func main() {
// Menu hôm nay
menu := []string{"Phở Bò", "Bún Chả", "Gỏi Cuốn"}
fmt.Println("📋 MENU HÔM NAY:")
for thuTu, tenMon := range menu {
fmt.Printf("%d. %s\n", thuTu+1, tenMon)
}
// Có khách đặt thêm món mới
fmt.Println("\n✨ Khách đặt thêm món đặc biệt!")
menu = append(menu, "Nem Rán", "Chả Giò")
fmt.Println("\n📋 MENU CẬP NHẬT:")
for thuTu, tenMon := range menu {
fmt.Printf("%d. %s\n", thuTu+1, tenMon)
}
// Kiểm tra số món
fmt.Printf("\n📊 Tổng số món: %d\n", len(menu))
}
Ví Dụ 4: Slice với make() - Chuẩn Bị Sẵn Sức Chứa
package main
import "fmt"
func main() {
// Chuẩn bị rổ cho 10 món (dự trữ sẵn chỗ)
// make(kiểu, độ dài hiện tại, sức chứa tối đa)
danhSachMon := make([]string, 0, 10)
fmt.Printf("📦 Sức chứa: %d, Đang dùng: %d\n",
cap(danhSachMon), len(danhSachMon))
// Thêm món vào
danhSachMon = append(danhSachMon, "Phở")
danhSachMon = append(danhSachMon, "Bún")
danhSachMon = append(danhSachMon, "Gỏi")
fmt.Printf("📦 Sức chứa: %d, Đang dùng: %d\n",
cap(danhSachMon), len(danhSachMon))
fmt.Println("📋 Danh sách:", danhSachMon)
}
🔍 Giải Thích:
len()= Số lượng phần tử hiện có (đang dùng)cap()= Sức chứa tối đa (đã chuẩn bị)- Dùng
make()khi biết trước số lượng để tối ưu bộ nhớ!
Ví Dụ 5: Cắt Slice (Slicing) - Lấy Một Phần Menu
package main
import "fmt"
func main() {
menu := []string{"Phở Bò", "Bún Chả", "Gỏi Cuốn", "Nem Rán", "Chả Giò"}
fmt.Println("📋 MENU ĐẦY ĐỦ:", menu)
// Lấy 3 món đầu (từ vị trí 0 đến 3, không bao gồm 3)
monPhoThong := menu[0:3]
fmt.Println("\n🍜 Món phổ thông (3 món đầu):", monPhoThong)
// Lấy 2 món cuối (từ vị trí 3 đến hết)
monDacBiet := menu[3:]
fmt.Println("⭐ Món đặc biệt (2 món cuối):", monDacBiet)
// Lấy món từ vị trí 1 đến 4
monGiua := menu[1:4]
fmt.Println("📍 Món giữa (vị trí 1-4):", monGiua)
// Lấy tất cả từ đầu đến vị trí 4
monDauTien := menu[:4]
fmt.Println("🔝 4 món đầu:", monDauTien)
}
🔍 Cú Pháp Slicing:
menu[0:3]= Từ vị trí 0 đến 3 (không bao gồm 3)menu[3:]= Từ vị trí 3 đến hếtmenu[:4]= Từ đầu đến vị trí 4 (không bao gồm 4)menu[:]= Lấy tất cả
🔥 Thực Hành Trong Bếp
Bài Tập 1: Quản Lý Nguyên Liệu Phở
package main
import "fmt"
func main() {
// Danh sách nguyên liệu ban đầu
nguyenLieu := []string{"Thịt bò", "Bánh phở", "Hành", "Ngò"}
fmt.Println("🥘 NGUYÊN LIỆU BAN ĐẦU:")
for i, ten := range nguyenLieu {
fmt.Printf("%d. %s\n", i+1, ten)
}
// Đi chợ thêm nguyên liệu
nguyenLieu = append(nguyenLieu, "Chanh", "Ớt", "Tương")
fmt.Println("\n🛒 SAU KHI ĐI CHỢ:")
for i, ten := range nguyenLieu {
fmt.Printf("%d. %s\n", i+1, ten)
}
fmt.Printf("\n📊 Tổng số nguyên liệu: %d\n", len(nguyenLieu))
}
Bài Tập 2: Tính Tổng Giá Menu
package main
import "fmt"
func main() {
// Menu và giá
tenMon := []string{"Phở Bò", "Bún Chả", "Gỏi Cuốn", "Nem Rán"}
giaMon := []int{50000, 45000, 30000, 25000}
fmt.Println("📋 MENU VÀ GIÁ:")
tongGia := 0
for i := 0; i < len(tenMon); i++ {
fmt.Printf("%s: %d VND\n", tenMon[i], giaMon[i])
tongGia += giaMon[i]
}
giaTraBinh := tongGia / len(tenMon)
fmt.Printf("\n💰 Tổng giá: %d VND\n", tongGia)
fmt.Printf("📊 Giá trung bình: %d VND\n", giaTraBinh)
}
Bài Tập 3: Lọc Món Theo Giá
package main
import "fmt"
func main() {
tenMon := []string{"Phở Bò", "Bún Chả", "Gỏi Cuốn", "Nem Rán", "Chả Giò"}
giaMon := []int{50000, 45000, 30000, 25000, 35000}
ngangGia := 40000
fmt.Printf("🔍 TÌM MÓN DƯỚI %d VND:\n\n", ngangGia)
// Tạo danh sách món rẻ
monReVoi := []string{}
giaReVoi := []int{}
for i := 0; i < len(tenMon); i++ {
if giaMon[i] < ngangGia {
monReVoi = append(monReVoi, tenMon[i])
giaReVoi = append(giaReVoi, giaMon[i])
}
}
// In kết quả
fmt.Println("📋 KẾT QUẢ:")
for i := 0; i < len(monReVoi); i++ {
fmt.Printf("✅ %s: %d VND\n", monReVoi[i], giaReVoi[i])
}
fmt.Printf("\n📊 Tìm được %d món\n", len(monReVoi))
}
⚠️ Những Lỗi Đầu Bếp Thường Gặp
Lỗi 1: Truy Cập Ngoài Phạm Vi Kệ
// ❌ SAI: Vượt quá số ô trong kệ
func main() {
menu := []string{"Phở", "Bún", "Gỏi"}
fmt.Println(menu[5]) // PANIC! Chỉ có 3 phần tử (index 0,1,2)
}
// ✅ ĐÚNG: Kiểm tra trước khi truy cập
func main() {
menu := []string{"Phở", "Bún", "Gỏi"}
viTri := 5
if viTri < len(menu) {
fmt.Println(menu[viTri])
} else {
fmt.Println("❌ Không có món ở vị trí này!")
}
}
🔧 Cách sửa: Luôn kiểm tra
index < len(slice)trước khi truy cập!
Lỗi 2: Quên Khởi Tạo Slice
// ❌ SAI: Slice nil không thể gán trực tiếp bằng index
func main() {
var menu []string // nil slice
menu[0] = "Phở" // PANIC! Slice chưa được khởi tạo
}
// ✅ ĐÚNG: Khởi tạo trước hoặc dùng append
func main() {
// Cách 1: Khởi tạo với make
menu := make([]string, 1)
menu[0] = "Phở"
// Cách 2: Dùng append (an toàn nhất)
var menu2 []string
menu2 = append(menu2, "Phở")
}
🔧 Cách sửa: Với nil slice, luôn dùng
append()hoặc khởi tạo bằngmake()trước!
Lỗi 3: Hiểu Lầm Array Và Slice
// ❌ SAI: Cố thêm phần tử vào array
func main() {
menu := [3]string{"Phở", "Bún", "Gỏi"} // Array cố định
// menu = append(menu, "Nem") // LỖI! Không thể append vào array
}
// ✅ ĐÚNG: Dùng slice khi cần thêm bớt
func main() {
menu := []string{"Phở", "Bún", "Gỏi"} // Slice (không có số trong [])
menu = append(menu, "Nem") // OK!
fmt.Println(menu)
}
🔧 Cách nhớ: Array có
[số], Slice có[]rỗng!
Lỗi 4: Copy Slice Sai Cách
// ❌ SAI: Gán trực tiếp chỉ copy tham chiếu
func main() {
menu1 := []string{"Phở", "Bún"}
menu2 := menu1 // Cùng trỏ đến một chỗ!
menu2[0] = "Nem"
fmt.Println(menu1[0]) // "Nem" - Menu1 cũng bị đổi!
}
// ✅ ĐÚNG: Dùng copy để tạo bản sao độc lập
func main() {
menu1 := []string{"Phở", "Bún"}
menu2 := make([]string, len(menu1))
copy(menu2, menu1) // Copy nội dung
menu2[0] = "Nem"
fmt.Println(menu1[0]) // "Phở" - Không đổi!
fmt.Println(menu2[0]) // "Nem"
}
🔧 Cách sửa: Dùng
copy(dst, src)để tạo bản sao thật sự!
💡 Bí Quyết Của Đầu Bếp
-
Ưu tiên dùng Slice thay vì Array: Slice linh hoạt hơn cho hầu hết trường hợp trong thực tế
-
Dùng make() với capacity khi biết trước: Nếu biết sẽ có 100 món, dùng
make([]string, 0, 100)để tránh phải cấp phát bộ nhớ nhiều lần -
for range an toàn hơn:
for i, item := range slicean toàn hơnfor i := 0; i < len(slice); i++ -
append() hoạt động với nil slice:
var s []string; s = append(s, "OK")vẫn chạy bình thường! -
Dùng copy() khi cần bản sao: Tránh thay đổi slice gốc không mong muốn
-
Slicing không copy dữ liệu:
menu[0:3]vẫn dùng chung bộ nhớ vớimenu, chỉ thay đổi con trỏ
🎓 Bạn Đã Học Được
- ✅ Array = Kệ cố định (kích thước không đổi, khai báo với
[n]type) - ✅ Slice = Rổ linh hoạt (kích thước động, khai báo với
[]type) - ✅ append() = Thêm phần tử vào slice
- ✅ Slicing [a:b] = Cắt slice thành phần nhỏ hơn
- ✅ make() = Tạo slice với capacity chuẩn bị sẵn
- ✅ copy() = Tạo bản sao độc lập của slice
- ✅ len() = Độ dài hiện tại, cap() = Sức chứa tối đa
- ✅ Luôn kiểm tra index trước khi truy cập để tránh panic
🍜 Món Tiếp Theo
Đã biết lưu trữ nhiều nguyên liệu trong rổ! Giờ hãy học cách tổ chức chúng thành các đơn gọi món có cấu trúc:
💡 Lời Khuyên Cuối: Slice giống như rổ nguyên liệu của đầu bếp - càng biết sắp xếp khéo, càng nấu nhanh! Hãy thực hành nhiều với slice để thành thạo quản lý dữ liệu trong Go!