append, clone, map + lock, embedding
var a [3]int // array: ตู้ปูน 3 ช่อง เปลี่ยนจำนวนไม่ได้ [3]int กับ [5]int คนละชนิด
a[0] = 10
s := []int{1, 2, 3} // slice: กระเป๋าผ้ายืดได้ (ใช้ตัวนี้ 95%)กฎ: เขียนโค้ดทั่วไปใช้ slice อย่าใช้ array ยกเว้นอยากได้ก้อนนิ่งๆ เช่น [32]byte เก็บ hash
นึกว่า slice = โน้ตแปะว่า "ของอยู่บ้านไหน ยาวเท่าไหร่ จุได้เท่าไหร่" (ptr/len/cap):
กับดัก: ก็อปโน้ต = ชี้บ้านเดิม!
ส่งเข้า func ก็เหมือนกัน แก้ข้างใน caller เห็นด้วย (เพราะชี้บ้านเดียวกัน) แต่ถ้า append จนย้ายบ้าน caller จะไม่เห็นถ้าไม่ return กลับ
s := []int{1, 2, 3}
s = append(s, 4, 5) // ต้องมี s = ข้างหน้า! เพราะอาจย้ายบ้าน
y := []int{6, 7}
s = append(s, y...) // ... = แกะกล่องโปรยเข้าไป
// อยากได้บ้านใหม่จริงๆ
d := make([]int, len(s))
copy(d, s)
// หรือสั้นๆ
c := append([]int{}, s...)
// หั่น
buf := []int{1, 2, 3, 4, 5}
fmt.Println(buf[0:2]) // [1 2]
fmt.Println(buf[2:]) // [3 4 5]
fmt.Println(buf[:3]) // [1 2 3]ระวังหั่นแล้วยังชี้บ้านเดิม แก้ฝั่งนึงอีกฝั่งโดนด้วย ถ้าจะให้ขาดให้ copy หรือใช้ slices.Clone (Go 1.21+):
go fix ตัวใหม่ slicesbackward จะช่วยแก้ loop วนกลับหลังให้ใช้ slices.Backward ที่ถูกและเร็วขึ้น (Go 1.26+)
Go มีแค่ 1 มิติ ต้องซ้อน:
// วิธี 1: แยกบ้านทีละแถว (แถวโตได้อิสระ)
pic := make([][]uint8, 3)
for i := range pic { pic[i] = make([]uint8, 4) }
// วิธี 2: บ้านเดียวแล้วหั่น (ประหยัด ถ้าขนาดคงที่)
pic2 := make([][]uint8, 3)
pix := make([]uint8, 3*4)
for i := range pic2 { pic2[i], pix = pix[:4], pix[4:] }ข่าวดี 1.26: compiler เอาที่เก็บ slice ไปไว้บน stack ได้บ่อยขึ้น = เร็วขึ้น กินแรมน้อยลง โดยไม่ต้องแก้โค้ด ดูได้ด้วย go build -gcflags='-m'
m := map[string]int{"UTC": 0, "EST": -18000}
m["CST"] = -21600
delete(m, "EST") // ลบตัวไม่มีก็ไม่พัง
v := m["xxx"] // ไม่มีได้ 0 (zero value) ต้องแยกด้วย comma-ok
v, ok := m["xxx"]
if !ok { fmt.Println("ไม่มี key นี้") }key ได้ทุก type ที่ == ได้ (string int struct) แต่ slice ใช้เป็น key ไม่ได้ เพราะเทียบไม่ได้
สมุดเล่มเดียว 2 คนเขียนพร้อมกันขาด (race):
var mu sync.Mutex
mu.Lock()
m[k] = v
mu.Unlock()
// อ่านเยอะเขียนน้อยใช้ RWMutex
mu.RLock()
v := m[k]
mu.RUnlock()
// หรือใช้ sync.Map
var sm sync.Map
sm.Store(k, v)
v, ok := sm.Load(k)ตรวจเสมอ go test -race ./...
struct = แฟ้มรวมกล่อง:
Embedding = ยัดกล่องในกล่อง เรียกของข้างในได้เลย:
type Logger struct{}
func (Logger) Log(s string) { fmt.Println(s) }
type Job struct {
Command string
Logger
}
j := Job{Command: "run"}
j.Log("start")ของใหม่ 1.27: สร้างแบบฝังยัดไส้ชั้นในได้เลย:
type Habitat struct{ Burrow string }
type Gopher struct {
Name string
Habitat
}
g := Gopher{Name: "Gopher", Burrow: "Burrow #42"} // ไม่ต้องห่อ Habitat{...} แล้วgo fix embedlit ช่วยเปลี่ยนแบบเก่าเป็นแบบใหม่ให้ + new(T) แบบใหม่ 1.26 ใส่ค่าได้เลย p := new(int64(300))
a := []int{1, 2, 3}
b := append(a[:2], 9) // b แชร์ array เดียวกับ a!
fmt.Println(a) // [1 2 9] <- a โดนเปลี่ยนด้วย ไม่รู้ตัว
c := append([]int(nil), a[:2]...) // แบบนี้ปลอดภัย (clone ก่อน)
_ = cbuf := make([]byte, 0, 1024)
chunk := buf[:0:0] // cap=0: append ทีหลังไม่กระทบ buf แน่นอน
_ = chunktype SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Get(k string) (int, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.m[k]
return v, ok
}append อาจเปลี่ยน array ต้นฉบับ ถ้าจะเก็บแยกให้ clone ก่อนv, ok := m[k] อย่าเดาจาก zero valuemake(..., 0, n) เมื่อรู้ขนาด ลด allocCloneInts + เทสต์ว่าแก้ตัว clone แล้วต้นฉบับไม่เปลี่ยน-raceอ้างอิง: Effective Go #slices #maps #embedding, Go 1.26 (stack alloc, new(expr)), Go 1.27 (embedlit)