Chuyển tới nội dung chính

🗄️ 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à ArraysSlices - 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 string
  • keGiaVi[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ước
  • append() = 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ết
  • menu[: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ằng make() 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

  1. Ư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ế

  2. 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

  3. for range an toàn hơn: for i, item := range slice an toàn hơn for i := 0; i < len(slice); i++

  4. append() hoạt động với nil slice: var s []string; s = append(s, "OK") vẫn chạy bình thường!

  5. Dùng copy() khi cần bản sao: Tránh thay đổi slice gốc không mong muốn

  6. Slicing không copy dữ liệu: menu[0:3] vẫn dùng chung bộ nhớ với menu, 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:

👉 Đơn Gọi Món - Structs


💡 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!