目標
概念
- 常量與變量的主要區別在于:
- 不可變性:常量在聲明后其值就固定下來,不能再被修改。這保證了程序運行時不會因意外修改而導致錯誤。
- 使用不可變數據(例如數學常數 π)可以避免意外修改帶來的問題
- 編譯期計算:常量表達式在編譯期間就會被求值,這不僅減少了運行時的計算負擔,還意味著許多在運行時可能出錯的操作(比如除零、越界)都可以在編譯階段捕獲。
- 不可變性:常量在聲明后其值就固定下來,不能再被修改。這保證了程序運行時不會因意外修改而導致錯誤。
- Go 中常量的潛在類型為布爾型、字符串或數字
- 無類型常量:在定義時沒有顯式指定類型的常量。比如寫
3.14
、0
或"hello"
,這些數值或字符串常量在初始狀態下沒有固定的類型。 - 六種無類型常量:分別對應布爾、整數、字符(rune)、浮點數、復數和字符串。
- 無類型常量更高精度的運算:無類型常量在算術運算中擁有比具體類型(如 int 或 float64)更高的精度(可以看作有 256 位或更多位的精度);像 ZiB 或 YiB 這樣超出任何內置整數類型范圍的常量,也仍然可以參與運算(例如
YiB / ZiB
),因為在編譯期編譯器會將它們按照數學上準確的值處理。
要點
常量聲明
-
聲明常量時,可以直接賦予初值:
const pi = 3.14159 // 近似值;實際上 math.Pi 提供了更精確的值
這和變量聲明語法類似,但常量的值一旦設定就不能修改。
-
批量聲明常量
多個相關常量可以使用小括號一起聲明:
const (e = 2.71828182845904523536028747135266249775724709369995957496696763pi = 3.14159265358979323846264338327950288419716939937510582097494459 )
這樣的批量聲明不僅代碼更簡潔,也可以利用類型推斷:如果沒有顯式指定類型,常量的類型會根據右側的表達式自動推斷。
類型推斷說明
-
如果你在常量聲明時沒有顯式標明類型,則 Go 會根據右邊的表達式推斷類型。例如:
const timeout = 5 * time.Minute
此時
timeout
會被推斷為time.Duration
類型,因為time.Minute
本身是time.Duration
類型的常量。
-
編譯期常量表達式與優化
在數組定義中,可以用常量來指定數組的長度:
const IPv4Len = 4func parseIPv4(s string) IP {var p [IPv4Len]byte// 解析 IPv4 地址的邏輯
}
這樣,數組長度在編譯期間就確定下來了。程序運行時減少了不必要的計算,提高了效率。
iota常量生成器
-
iota的基本原理
- 自動遞增:在一個
const
聲明塊中,iota 是一個預定義標識符,它在第一行被置為 0,后續每出現一行常量聲明,它的值自動加 1。 - 省略初始化表達式:如果在常量聲明塊中,后面的常量省略了右側的表達式,那么它們將默認使用前一行的表達式。這樣在簡單的復制中,雖然沒有太大實用價值,但在配合 iota 時可以生成有規律的值。
- 自動遞增:在一個
-
例子
type Weekday intconst (Sunday Weekday = iota // 0Monday // 1Tuesday // 2Wednesday // 3Thursday // 4Friday // 5Saturday // 6 )
這種方式使得常量的賦值變得簡單且易于維護。
-
iota 還常用于生成一系列位掩碼(bit mask)
type Flags uintconst (FlagUp Flags = 1 << iota // 1 << 0 = 1 (第 0 位)FlagBroadcast // 1 << 1 = 2 (第 1 位)FlagLoopback // 1 << 2 = 4 (第 2 位)FlagPointToPoint // 1 << 3 = 8 (第 3 位)FlagMulticast // 1 << 4 = 16 (第 4 位) )
每個常量都代表一個單獨的 bit 位,這樣在設置或測試標志時,可以使用位運算:
- 測試標志:
v & FlagUp == FlagUp
- 清除標志:
v &^= FlagUp
—— 把 v 中與 FlagUp 對應的那一位變成 0(不管原來是1還是0)。這樣就清除了這個標志。 - 設置標志:
v |= FlagBroadcast
—— 把 v 中與 FlagBroadcast 對應的那一位變成1(即使之前是0)
- 測試標志:
-
利用 iota 生成一系列以 1024 為底的冪(例如 KiB、MiB 等)
const (_ = 1 << (10 * iota)KiB // 1 << 10 = 1024MiB // 1 << 20 = 1048576GiB // 1 << 30 = 1073741824TiB // 1 << 40 = 1099511627776PiB // 1 << 50 = 1125899906842624EiB // 1 << 60 = 1152921504606846976// ZiB, YiB 等可能會超過某些平臺的位數限制 )
通過這種方式,編譯器自動計算出每一項的值,而無需手動寫出復雜的計算。
-
局限性
不能產生任意的冪:例如,要生成 1000、1000000 等(通常用于 KB、MB 等),因為 Go 語言沒有內置的冪運算符(如
**
或pow
),所以不能直接利用 iota 來生成 10 的冪。
無類型常量的靈活性:隱式轉換
當你將無類型常量賦值給一個變量時,Go 編譯器會根據上下文自動推斷出一個“默認類型”。例如:
-
var x float64 = math.Pi
這里,math.Pi
是一個無類型浮點常量,賦值時自動轉換成 float64。 -
又如:
i := 0 // 隱式為 int 類型 f := 0.0 // 隱式為 float64 類型 c := 0i // 隱式為 complex128 類型 r := '\000'// 隱式為 rune(int32)類型
對于一個沒有顯式類型的變量聲明(包括簡短變量聲明),常量的形式將隱式決定變量的默認類型,
這種機制讓我們在書寫表達式時更靈活,無需反復寫類型轉換代碼。
隱式轉換與默認類型
當無類型常量出現在需要具體類型的上下文時(比如當一個無類型的常量被賦值給一個變量的時候),編譯器會自動進行轉換。例如:
var f float64 = 3 + 0i // 這里 3+0i 是無類型復數,但可以隱式轉換為 float64
這種轉換類似于在后臺寫了:
var f float64 = float64(3 + 0i)
注意: 轉換要求目標類型必須能表示原始常量的值。如果值太大或不適合,則會導致編譯錯誤。例如:
- 將一個超出 int32 范圍的無類型整數轉換為 int32,會報錯;
- 將一個超出 float64 表示范圍的浮點數轉換為 float64,同樣會報錯。
無類型常量隱式轉換的更多例子
var f float64 = 212
fmt.Println((f - 32) * 5 / 9) // 輸出 "100"
fmt.Println(5 / 9 * (f - 32)) // 輸出 "0"
fmt.Println(5.0 / 9.0 * (f - 32)) // 輸出 "100"
- 第一行:
(f - 32)
是 float64,乘以 5 后依然是 float64,然后除以 9,所有運算都是浮點運算,所以結果正確(100)。 - 第二行:
5 / 9
兩個都是無類型整數(隱式為 int),整數除法結果為 0(因為 5/9 小于 1,用整數運算取整),導致整個表達式結果為 0。 - 第三行:
5.0
和9.0
是無類型浮點常量,所以 5.0/9.0 得到正確的浮點值,再乘以 (f - 32) 得到 100。
關鍵點:常量的寫法決定了它們在運算中的默認類型,從而影響最終結果。這也說明了在使用無類型常量時,要注意數值字面量的形式(整數形式或浮點形式)對運算結果的影響。
fmt.Println(YiB / ZiB) // 輸出 "1024"
- 即便 ZiB 和 YiB 的值超出了 Go 內置整數類型能表達的范圍,它們仍然可以參與運算.
類型與接口轉換
當無類型常量賦值給接口變量時,接口的動態類型取決于常量的默認類型
fmt.Printf("%T\n", 0) // 輸出 "int"
fmt.Printf("%T\n", 0.0) // 輸出 "float64"
fmt.Printf("%T\n", 0i) // 輸出 "complex128"
fmt.Printf("%T\n", '\000') // 輸出 "int32"(也稱為 rune)
這對接口編程非常關鍵,因為接口在運行時需要知道底層數據的具體類型。
語言特性
練習
-
編寫KB、MB的常量聲明,然后擴展到YB。
const (KB = 1000MB = 1000 * KBGB = 1000 * MBTB = 1000 * GBPB = 1000 * TBEB = 1000 * PBZB = 1000 * EBYB = 1000 * ZB )
總結
- iota 常量生成器:iota 是一個強大的工具,可以方便地生成有規律的數值序列,適用于枚舉、位標志、以及其他有序數值集合的定義。
- 有一類常量稱為“無類型常量”,這類常量在聲明時不被賦予一個具體的基礎類型,而是保持一種更“通用”的狀態。這樣做有兩個好處:
- 更高精度的運算:無類型常量在算術運算中擁有比具體類型(如 int 或 float64)更高的精度(可以看作有 256 位或更多位的精度)。
- 靈活的隱式轉換:當無類型常量賦值給變量時,編譯器會自動將它們轉換成變量所需要的類型(如果轉換合法),這樣可以直接用于多種場合,減少顯式轉換的麻煩。