Pointer

Pointer là gì?

Pointer (con trỏ) lưu địa chỉ bộ nhớ của một biến thay vì lưu giá trị. Hãy tưởng tượng bộ nhớ là một dãy nhà — mỗi nhà có địa chỉ. Pointer là tờ giấy ghi địa chỉ đó.

x := 42
p := &x    // & lấy địa chỉ của x → p là pointer

fmt.Println(x)   // 42         — giá trị
fmt.Println(p)   // 0xc000...  — địa chỉ bộ nhớ
fmt.Println(*p)  // 42         — giá trị TẠI địa chỉ đó (dereference)

*p = 100         // thay đổi giá trị thông qua pointer
fmt.Println(x)   // 100 — x thay đổi theo!

Tại sao cần Pointer?

1. Thay đổi biến trong hàm khác:

// Không dùng pointer: hàm nhận bản sao, gốc không đổi
func tangX1(n int) { n++ }

// Dùng pointer: hàm nhận địa chỉ, thay đổi gốc
func tangX2(n *int) { *n++ }

x := 5
tangX1(x)           // x vẫn = 5
tangX2(&x)          // x = 6

2. Tránh copy dữ liệu lớn:

type BigStruct struct { data [1000]int }

// KHÔNG hiệu quả: copy 8000 bytes mỗi lần gọi
func processValue(s BigStruct) { ... }

// Hiệu quả: chỉ truyền 8 bytes (địa chỉ)
func processPointer(s *BigStruct) { ... }

3. Cho phép giá trị "không có" (nil):

// int không thể nil
// *int có thể nil — biểu diễn "không có giá trị"
type Config struct {
    Port    int
    Timeout *int  // nil = dùng timeout mặc định
}

new() — Cấp phát và trả về pointer

p := new(int)    // cấp phát int mới, trị = 0, trả về *int
*p = 42
fmt.Println(*p)  // 42

// Tương đương với:
val := 0
p := &val

Nil Pointer — Cạm bẫy phổ biến

var p *int   // p == nil

// PANIC nếu dereference nil pointer:
fmt.Println(*p)  // runtime error: invalid memory address

// Luôn kiểm tra nil trước khi dereference
if p != nil {
    fmt.Println(*p)
}

Go KHÔNG có pointer arithmetic

// Trong C: *(ptr + 1) hợp lệ — rất nguy hiểm
// Trong Go: KHÔNG được làm vậy
p := &someArray[0]
p++  // lỗi compile: cannot take address of non-addressable value

// Dùng index để truy cập phần tử mảng
for i := range someArray {
    process(&someArray[i])
}

Lưu ý: Trả về pointer đến biến local trong Go là an toàn — Go sẽ tự động cấp phát biến đó trên heap thay vì stack (khác với C!)

example.go
package main

import "fmt"

// Stack dùng pointer để thay đổi slice gốc
type Stack struct {
	items []int
}

func (s *Stack) Push(item int) {
	s.items = append(s.items, item)
}

func (s *Stack) Pop() (int, bool) {
	if len(s.items) == 0 {
		return 0, false
	}
	n := len(s.items) - 1
	item := s.items[n]
	s.items = s.items[:n]
	return item, true
}

func (s *Stack) Peek() (int, bool) {
	if len(s.items) == 0 {
		return 0, false
	}
	return s.items[len(s.items)-1], true
}

func (s Stack) Size() int { return len(s.items) }

// Linked list — ví dụ pointer điển hình
type Node struct {
	Value int
	Next  *Node
}

func newList(vals ...int) *Node {
	if len(vals) == 0 {
		return nil
	}
	head := &Node{Value: vals[0]}
	cur := head
	for _, v := range vals[1:] {
		cur.Next = &Node{Value: v}
		cur = cur.Next
	}
	return head
}

func printList(head *Node) {
	for cur := head; cur != nil; cur = cur.Next {
		if cur.Next != nil {
			fmt.Printf("%d -> ", cur.Value)
		} else {
			fmt.Println(cur.Value)
		}
	}
}

func main() {
	var s Stack
	for i := 1; i <= 5; i++ {
		s.Push(i * 10)
	}
	fmt.Println("Stack size:", s.Size())
	for {
		if v, ok := s.Pop(); ok {
			fmt.Println("Pop:", v)
		} else {
			break
		}
	}

	fmt.Println()
	list := newList(1, 2, 3, 4, 5)
	printList(list)
}