想像一下,你正在負責一場社區主席選舉的計票工作。傳統的紙本計票方式是這樣的:
候選人A:正正正正 正 (21票)
候選人B:正正正 (13票)
候選人C:正正正正 正正 (27票)
每當有新的選票進來,你就在候選人名字後面畫一筆。
如果今天改用電腦程式來計票會發生什麼呢?
package main
import "fmt"
func addVote(count int) {
count = count + 1
fmt.Printf("函式內部:票數變成 %d\n", count)
}
func main() {
candidateAVotes := 21
fmt.Printf("加票前:候選人A有 %d 票\n", candidateAVotes)
addVote(candidateAVotes)
fmt.Printf("加票後:候選人A有 %d 票\n", candidateAVotes)
}
執行結果:
加票前:候選人A有 21 票
函式內部:票數變成 22
加票後:候選人A有 21 票
➡️ 問題出現了!因為傳進去的是「數值的複製品」,所以函式內加票的變化沒傳回外面。就像你拿一張影印的票箱清單給助手,他在影印本上畫記號,但原始清單根本沒變。
在 Go 語言中,指標 = 地址。
請想像候選人A的票箱放在「投票所3樓會議室」,指標就是記錄「住在哪裡」的地址卡,有了它就能找到那個票箱,並讀或改裡面的數字。
在 Go 裡會用到兩個符號:
&
取地址candidateAVotes := 21
address := &candidateAVotes
fmt.Printf("票數存放的地址:%p\n", address)
*
透過地址取值或改值fmt.Println(*address) // 顯示票箱裡的票:21
*address = 22 // 直接改票箱裡的票
fmt.Println(candidateAVotes) // 22
NewVoteCounter
來做一個真正能用的計票器函式:
// 建立新的票箱
func NewVoteCounter() *int {
initialCount := 0
return &initialCount // 回傳地址
}
// 加票功能
func AddVote(counter *int) {
if counter != nil {
*counter++
}
}
// 查票功能
func GetVoteCount(counter *int) int {
if counter != nil {
return *counter
}
return 0
}
func main() {
candidateA := NewVoteCounter()
fmt.Printf("候選人A票箱地址:%p\n", candidateA)
AddVote(candidateA)
AddVote(candidateA)
AddVote(candidateA)
fmt.Printf("候選人A最後得票:%d\n", GetVoteCount(candidateA))
}
輸出:
候選人A票箱地址:0xc000014098
候選人A最後得票:3
你可能會疑惑:
NewVoteCounter
函式裡的變數 initialCount
不是「區域變數」嗎?
函式結束後應該會消失,為什麼還能回傳?
答案是:Go 會自動進行 逃逸分析 (Escape Analysis)。
編譯器發現這個變數在函式外部還要被使用,就會自動幫它放到 heap,確保這個票箱地址不會失效。這就是為什麼函式結束後,外面依然找得到那份資料。
我們甚至可以擴展,讓多個候選人都有屬於自己的票箱:
type ElectionSystem struct {
candidates map[string]*int
}
func NewElectionSystem() *ElectionSystem {
return &ElectionSystem{
candidates: make(map[string]*int),
}
}
func (es *ElectionSystem) RegisterCandidate(name string) {
es.candidates[name] = NewVoteCounter()
fmt.Printf("候選人 %s 已註冊!\n", name)
}
func (es *ElectionSystem) CastVote(name string) {
if _, ok := es.candidates[name]; ok {
AddVote(es.candidates[name])
}
}
func (es *ElectionSystem) ShowResults() {
fmt.Println("=== 選舉結果 ===")
for name, counter := range es.candidates {
fmt.Printf("%s:%d 票\n", name, GetVoteCount(counter))
}
}
func main() {
election := NewElectionSystem()
election.RegisterCandidate("張三")
election.RegisterCandidate("李四")
election.CastVote("張三")
election.CastVote("李四")
election.CastVote("張三")
election.ShowResults()
}
透過 選舉計票的例子,我們學到了:
&
= 取地址,*
= 解參考取值👉 在下一篇,我們會進一步探討 nil 指標 — 如果候選人沒有票箱,該怎麼處理?