Golang 基本語法與資料型態 — 常數(const)與列舉(iota)
簡介
在任何程式語言中,常數都是不可變的值。它們讓程式碼更具可讀性、可維護性,同時避免硬編碼(magic number)帶來的錯誤。Go 語言提供了 const 關鍵字來宣告常數,並且配合 iota 這個特殊的編譯期產生器,讓我們可以輕鬆建立**列舉(enumeration)**型別。
對於剛踏入 Go 世界的開發者,掌握 const 與 iota 不僅能寫出更乾淨的程式碼,也能在設計 API、錯誤代碼、狀態機等實務情境中,減少錯誤、提升效能。本文將從概念說明、實作範例、常見陷阱到最佳實踐,一步步帶你深入了解這兩個關鍵特性。
核心概念
1. const 基本用法
const 用來宣告在編譯期就已確定、且在執行期間不可變的值。語法如下:
const identifier [type] = value
identifier:常數名稱,遵循一般變數的命名規則。type(可選):若省略,編譯器會根據右側的值自動推斷。value:必須是編譯期可計算的常量表達式(如字面量、算術運算、已宣告的常數等)。
範例 1:基本常數
package main
import "fmt"
const Pi = 3.14159 // 省略型別,預設為 untyped float
const MaxUser = 1000 // 整數常數
const Greeting = "Hello!" // 字串常數
func main() {
fmt.Println(Pi, MaxUser, Greeting)
}
註解:
Pi雖未指定型別,但在需要時會被自動轉型(如傳入float64參數時)。
2. 多行常數與常數群組
在同一個 const 區塊內,可一次宣告多個常數,省去重複寫 const 的麻煩。
const (
Red = "#FF0000"
Green = "#00FF00"
Blue = "#0000FF"
)
- 若某行省略右側的表達式,會自動使用上一行的值(不是上一行的名稱)。
- 常數群組常與
iota搭配,形成列舉。
3. iota 產生列舉
iota 是 Go 編譯器在 每個 const 區塊 內自動產生的整數序列,從 0 開始,每新增一行自動遞增 1。它的特點:
| 特性 | 說明 |
|---|---|
| 自動遞增 | 每行 iota 的值比上一行多 1 |
| 可重設 | 每個 const 區塊重新從 0 開始 |
| 可運算 | 可與算術、位元運算結合,產生自訂序列 |
| 僅限整數 | iota 本身是未具名的整數常數,不能直接用於浮點或字串(除非透過運算轉型) |
範例 2:最簡單的列舉
package main
import "fmt"
const (
Sun = iota // 0
Mon // 1
Tue // 2
Wed // 3
Thu // 4
Fri // 5
Sat // 6
)
func main() {
fmt.Println(Sun, Mon, Tue, Wed, Thu, Fri, Sat)
}
重點:
Mon、Tue… 等等,雖未寫iota,但會自動取得前一行的iota值。
4. iota 與位元旗標(Bit Flags)
在需要 二進位旗標(如權限、狀態)時,iota 搭配左移運算 (<<) 非常方便。
const (
FlagRead = 1 << iota // 1 (0001)
FlagWrite // 2 (0010)
FlagExec // 4 (0100)
FlagAdmin // 8 (1000)
)
範例 3:使用位元旗標檢查權限
package main
import "fmt"
func hasPermission(userPerm, required int) bool {
return userPerm&required == required
}
func main() {
// 假設使用者同時擁有 Read 與 Write 權限
user := FlagRead | FlagWrite
fmt.Println("Can Exec ?", hasPermission(user, FlagExec)) // false
fmt.Println("Can Write?", hasPermission(user, FlagWrite)) // true
}
5. iota 與自訂遞增規則
iota 不一定要線性遞增,我們可以透過算術運算改變序列。例如產生 星期的中文縮寫:
const (
SunCN = iota + 1 // 1
MonCN // 2
TueCN // 3
WedCN // 4
ThuCN // 5
FriCN // 6
SatCN // 7
)
或是產生 十六進位的位元掩碼:
const (
Mask0 = 0x1 << (iota * 4) // 0x1
Mask1 // 0x10
Mask2 // 0x100
Mask3 // 0x1000
)
6. 常見的 iota 用法:字串列舉(透過陣列映射)
iota 本身只能產生整數,但我們常會搭配切片或陣列,將整數映射成字串,實現「字串列舉」的效果。
type Day int
const (
Sunday Day = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
var dayNames = []string{
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday",
}
func (d Day) String() string {
if int(d) < len(dayNames) {
return dayNames[d]
}
return "Unknown"
}
範例 4:列舉與 String() 方法
package main
import "fmt"
type Color int
const (
Red Color = iota
Green
Blue
)
var colorNames = []string{"Red", "Green", "Blue"}
func (c Color) String() string {
if int(c) < len(colorNames) {
return colorNames[c]
}
return "Undefined"
}
func main() {
fmt.Println(Red, Red.String()) // 0 Red
fmt.Println(Green, Green.String()) // 1 Green
}
常見陷阱與最佳實踐
| 陷阱 | 說明 | 建議的最佳實踐 |
|---|---|---|
忘記 iota 重新計數 |
每個 const 區塊會重新從 0 開始,若在不同區塊間期待相同的值,會產生錯誤。 |
若需要跨區塊保持相同序列,使用 單一區塊 或手動指定值。 |
| 混用未指定型別的常數 | const 若未指定型別,會是 untyped 常數,傳遞給不同型別參數時會自動轉型,可能導致隱式精度損失。 |
在公開 API 中,明確宣告型別(如 const MaxSize int = 1024)以避免意外。 |
iota 與字串直接結合 |
直接寫 const s = "value" + iota 會編譯錯誤,因為 iota 只能用於整數運算。 |
使用切片/陣列映射或 fmt.Sprintf 在函式層面轉換。 |
位元旗標的值超出 int 範圍 |
在 32 位元系統上,1 << 31 會溢位。 |
使用 uint64 或 1 << uint(iota) 來確保足夠寬度。 |
| 常數群組中遺漏逗號 | 多行常數最後一行若忘記加逗號,會導致編譯錯誤。 | 養成每行結尾加逗號 的習慣,尤其在使用 iota 時。 |
最佳實踐小結
- 盡量使用單一
const區塊 產生相關列舉,避免iota重新計數。 - 明確指定型別(特別是公開套件的常數),提升 API 可預測性。
- 利用切片/陣列映射 產生字串列舉,保持
iota的效能與可讀性。 - 位元旗標 建議使用
uint或uint64,並寫好HasFlag、AddFlag、RemoveFlag輔助函式。 - 註解說明 常數的意圖與使用情境,讓維護者快速了解其語意。
實際應用場景
1. HTTP 狀態碼
const (
StatusOK = 200
StatusCreated = 201
StatusBadRequest = 400
StatusUnauthorized = 401
StatusForbidden = 403
StatusNotFound = 404
StatusInternalServerError = 500
)
說明:將常見的 HTTP 狀態碼抽成常數,讓程式碼不必硬寫數字,提升可讀性。
2. 錯誤類別(自訂錯誤碼)
type ErrCode int
const (
ErrInvalidInput ErrCode = iota + 1 // 1
ErrNotFound // 2
ErrPermissionDenied // 3
ErrTimeout // 4
)
在錯誤處理函式中返回 ErrCode,呼叫端只要檢查 code == ErrNotFound 即可。
3. 狀態機(Finite State Machine)
type State int
const (
StateInit State = iota
StateRunning
StatePaused
StateStopped
)
配合 switch 或 map[State]func(),可輕鬆實作工作流或協議解析器。
4. 權限系統(RBAC)
const (
RoleGuest = 1 << iota // 1
RoleUser // 2
RoleModerator // 4
RoleAdmin // 8
)
使用位元運算檢查多重角色:
func hasRole(userRoles, required int) bool {
return userRoles&required == required
}
5. 日誌等級(Log Level)
type LogLevel int
const (
Debug LogLevel = iota
Info
Warn
Error
Fatal
)
透過 iota 建立有序的等級,方便比較 if currentLevel <= Warn { ... }。
總結
const為 編譯期不可變 的值,能提升程式碼可讀性與安全性。iota是 自動遞增的整數產生器,專為常數群組設計,讓列舉與位元旗標的撰寫變得簡潔且不易出錯。- 透過 單一區塊、明確型別、切片映射 等技巧,我們可以避免常見的陷阱,寫出 易於維護、可擴充 的程式碼。
- 在實務開發中,
const+iota常見於 HTTP 狀態碼、錯誤代碼、狀態機、權限系統、日誌等級 等多種情境,幾乎是每個 Go 專案的基礎建構塊。
掌握了這兩個概念,你就能在 Go 程式中以 最少的程式碼 表達 最多的意圖,寫出既簡潔又健壯的程式。祝你在 Golang 的學習旅程中,玩得開心、寫得順手!