iT邦幫忙

2025 iThome 鐵人賽

DAY 7
1
Modern Web

後端攻略筆記系列 第 7

Day 7 : Go 指標基礎 範例:選舉計票系統入門

  • 分享至 

  • xImage
  •  

從零開始學 Go 指標與結構 — 篇一:Go 指標基礎與選舉計票系統入門

為什麼需要指標?從選舉計票談起

想像一下,你正在負責一場社區主席選舉的計票工作。傳統的紙本計票方式是這樣的:

候選人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 指標的基本語法

在 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()
}

小結

透過 選舉計票的例子,我們學到了:

  1. 指標的必要性:要修改資料,需要知道「地址」
  2. 語法重點& = 取地址,* = 解參考取值
  3. 安全性:Go 自動處理逃逸,讓地址不會失效
  4. 應用:可以用地址(指標)設計出真實可用的「票箱系統」

👉 在下一篇,我們會進一步探討 nil 指標 — 如果候選人沒有票箱,該怎麼處理?



上一篇
Day 6 : Go語言 slice 與 map 應用練習 - 補充
下一篇
Day 8 : Go 指標與結構 — 篇二:處理指標的 Nil 判斷與安全操作技巧
系列文
後端攻略筆記13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言