本文 AI 產出,尚未審核

Golang

單元:控制流程

主題:實用範例(FizzBuzz、判斷質數)


簡介

在程式設計的世界裡,控制流程是所有語言的基礎骨幹。無論是條件判斷、迴圈還是跳脫機制,都直接影響程式的可讀性、可維護性與效能。對於學習 Golang 的新手來說,掌握 if‑elseswitchfor 這幾種最常見的流程控制語句,是邁向「寫得出、寫得好」的第一步。

本篇文章以兩個經典且實用的練習題——FizzBuzz判斷質數——作為範例,說明如何在 Go 中運用條件式、迴圈與函式,並同時探討常見的陷阱與最佳實踐。透過完整的程式碼示例與逐行解說,讀者不只會知道「怎麼寫」,更能了解「為什麼要這樣寫」,進而在實務專案中快速套用。


核心概念

1. if‑elseswitch 的基本用法

Go 的 if 允許在條件判斷前先宣告變數,這在需要先計算再比較的情況下非常方便。switch 則提供了多分支的選擇,且不需要寫 break,預設會自動跳出。

// if 內部宣告變數
if n := x % 3; n == 0 {
    fmt.Println("可被 3 整除")
} else if n == 1 {
    fmt.Println("餘數為 1")
}

// switch 範例
switch {
case x%15 == 0:
    fmt.Println("FizzBuzz")
case x%3 == 0:
    fmt.Println("Fizz")
case x%5 == 0:
    fmt.Println("Buzz")
default:
    fmt.Println(x)
}

小技巧:使用 switch true 可以把多個布林表達式寫在同一個 case 中,讓程式結構更清晰。


2. for 迴圈的三種形態

Go 只提供 for 作為唯一的迴圈關鍵字,但它可以模擬 whiledo‑while 以及傳統的 for(init; cond; post)

形態 範例 說明
for i := 0; i < n; i++ 典型計數迴圈 需要索引時使用
while 風格 for n > 0 { … } 條件不成立即停止
do‑while 風格 for { …; if !cond { break } } 必執行一次,條件不符才跳出
// 計數迴圈:列印 1~10
for i := 1; i <= 10; i++ {
    fmt.Println(i)
}

// while 風格:倒數 5~1
n := 5
for n > 0 {
    fmt.Println(n)
    n--
}

3. 函式的設計:回傳值與錯誤處理

在實作 判斷質數 時,我們會把核心邏輯封裝成函式,回傳 bool(是否為質數)以及 error(輸入不合法時)。這樣的設計符合 Go 的錯誤處理慣例,也讓呼叫端可以更簡潔地使用。

// IsPrime 判斷 n 是否為質數,若 n 小於 2 則回傳錯誤
func IsPrime(n int) (bool, error) {
    if n < 2 {
        return false, fmt.Errorf("input must be >= 2, got %d", n)
    }
    // 只需要檢查到 sqrt(n)
    limit := int(math.Sqrt(float64(n)))
    for i := 2; i <= limit; i++ {
        if n%i == 0 {
            return false, nil
        }
    }
    return true, nil
}

程式碼範例

以下提供 3 個完整範例,涵蓋 FizzBuzz、單一數字的質數判斷,以及一次列舉多個質數的實作。每段程式碼均附有說明註解,方便讀者一步步跟進。

1️⃣ 範例一:最簡版 FizzBuzz(1~100)

package main

import "fmt"

func main() {
    // 1 到 100 的迴圈
    for i := 1; i <= 100; i++ {
        // 使用 switch true 取代多層 if‑else
        switch {
        case i%15 == 0:
            fmt.Println("FizzBuzz")
        case i%3 == 0:
            fmt.Println("Fizz")
        case i%5 == 0:
            fmt.Println("Buzz")
        default:
            fmt.Println(i)
        }
    }
}

重點i%15 == 0 必須放在最前面,因為 15 同時滿足 3 與 5 的條件,若寫在後面會被前面的 case i%3 == 0case i%5 == 0 先截斷。


2️⃣ 範例二:函式化的 FizzBuzz(可重複使用)

package main

import "fmt"

// FizzBuzz 返回對應的字串,若不符合則回傳空字串
func FizzBuzz(n int) string {
    switch {
    case n%15 == 0:
        return "FizzBuzz"
    case n%3 == 0:
        return "Fizz"
    case n%5 == 0:
        return "Buzz"
    default:
        return ""
    }
}

func main() {
    for i := 1; i <= 30; i++ {
        if out := FizzBuzz(i); out != "" {
            fmt.Println(out)
        } else {
            fmt.Println(i)
        }
    }
}

技巧:將判斷抽成函式後,主程式只負責「輸入/輸出」的工作,讓測試與維護變得更簡單。


3️⃣ 範例三:判斷單一數字是否為質數

package main

import (
    "fmt"
    "math"
)

// IsPrime 如前所示,回傳 (bool, error)
func IsPrime(n int) (bool, error) {
    if n < 2 {
        return false, fmt.Errorf("input must be >= 2, got %d", n)
    }
    limit := int(math.Sqrt(float64(n)))
    for i := 2; i <= limit; i++ {
        if n%i == 0 {
            return false, nil
        }
    }
    return true, nil
}

func main() {
    numbers := []int{1, 2, 3, 4, 17, 20, 23, 100}
    for _, v := range numbers {
        if prime, err := IsPrime(v); err != nil {
            fmt.Printf("%d: %v\n", v, err)
        } else if prime {
            fmt.Printf("%d 是質數\n", v)
        } else {
            fmt.Printf("%d 不是質數\n", v)
        }
    }
}

4️⃣ 範例四:一次列舉 2~N 之間的所有質數(埃拉托斯特尼篩法)

package main

import "fmt"

// SieveEratosthenes 使用布林切片標記合成數,返回所有質數
func SieveEratosthenes(limit int) []int {
    if limit < 2 {
        return []int{}
    }
    // true 表示「可能是質數」
    isPrime := make([]bool, limit+1)
    for i := 2; i <= limit; i++ {
        isPrime[i] = true
    }

    for p := 2; p*p <= limit; p++ {
        if isPrime[p] {
            // 從 p^2 開始標記,避免重複
            for multiple := p * p; multiple <= limit; multiple += p {
                isPrime[multiple] = false
            }
        }
    }

    // 收集結果
    primes := []int{}
    for i := 2; i <= limit; i++ {
        if isPrime[i] {
            primes = append(primes, i)
        }
    }
    return primes
}

func main() {
    fmt.Println("2~100 的質數:", SieveEratosthenes(100))
}

說明:相較於逐一檢查 IsPrime,埃拉托斯特尼篩法的時間複雜度為 O(n log log n),在需要大量質數時效能優勢顯著。


常見陷阱與最佳實踐

陷阱 可能的結果 建議的做法
忘記 break(在其他語言中常見) switch 會自動跳出,不會產生穿透問題,但若使用 fallthrough 必須明確寫出 只在真的需要「往下」時使用 fallthrough,否則保持預設行為
迴圈條件寫錯(例如 i <= n 而實際只需要 < n 可能多跑一次或少跑一次,導致錯誤的輸出或陣列索引越界 for i := 0; i < len(slice); i++ 的慣例,或直接使用 for range
質數判斷未使用 sqrt 限制 迴圈次數過多,效能下降(特別是大數) 只檢查到 int(math.Sqrt(float64(n))),可大幅減少運算
未處理錯誤IsPrime 回傳 error 卻忽略) 程式在非法輸入時仍繼續執行,結果不可靠 每次呼叫返回 error 時,都要檢查 if err != nil { … }
過度使用全域變數 造成資料競爭、測試困難 儘量把資料封裝在函式或結構體內,保持純函式的特性

最佳實踐

  1. 保持函式單一職責:FizzBuzz、質數判斷各自獨立,讓單元測試更容易撰寫。
  2. 使用 go fmt:統一程式碼風格,提升可讀性。
  3. 加入單元測試testing 套件可以快速驗證 FizzBuzzIsPrime 的正確性。
  4. 適時使用 defer 釋放資源(例如檔案、網路連線),避免資源洩漏。
  5. 考慮效能:在大量計算時,先思考演算法(如埃拉托斯特尼篩法)再寫程式。

實際應用場景

場景 為何需要控制流程 範例程式碼的應用
日誌過濾 依據等級(INFO、WARN、ERROR)決定是否輸出 使用 switch 判斷等級,類似 FizzBuzz 的多條件分支
API 請求驗證 必須檢查多個參數是否符合規範,並回傳錯誤訊息 if‑else 搭配 error 回傳,類似 IsPrime 的錯誤處理
批次資料處理 需要遍歷大量資料,對特定條件執行不同邏輯 for 迴圈結合 if/switch,可直接套用 FizzBuzz 的結構
數學運算服務 計算質數、因式分解等,需要高效演算法 使用 SieveEratosthenes 產生質數表,供後續演算使用
教學平台自動評分 依照答案正確與否給予不同分數 if‑else 判斷答案,配合 switch 處理多種題型

總結

本篇文章以 FizzBuzz判斷質數 為核心範例,說明了 Go 語言中 條件判斷、迴圈與函式 的基本用法與實務技巧。透過 if‑elseswitchfor 三大控制流程,我們可以寫出結構清晰、易於維護的程式;再配合 錯誤處理演算法優化(如 sqrt 限制、埃拉托斯特尼篩法),即可在真實專案中獲得良好的效能與可靠性。

關鍵要點

  • 使用 switch true 讓多條件判斷更直觀。
  • 只檢查到 sqrt(n),即可判斷質數,避免不必要的迴圈。
  • 把核心邏輯封裝成函式,搭配 error 回傳,使程式更具彈性與可測試性。

掌握了這些控制流程的基礎後,讀者即可在日常開發、演算法練習或面試題目中,快速寫出正確且高效的 Go 程式碼。祝大家在 Golang 的學習旅程中,玩得開心、寫得順手!