原文:CalliCoder — Working with Constants in Golang
常數
在 Golang 中,我們使用 constant 語法來表示固定(不改變)的值,如 5、1.34、true 和 "Hello" 等。
定數是常數
Golang 中的所有定數,如整數 5、1000、浮點數 4.76、1.89、布林值 true、false 或字串 "Hello"、"Jhon" 都是常數。
| 常數 | 範例 | 
|---|---|
| 整數常數 | 1000、67413 | 
| 浮點數常數 | 4.56、128.372 | 
| 布林常數 | true、false | 
| 表示字元的常數 | 'C'、'ä' | 
| 複數常數 | 2.7i、3 + 5i | 
| 字串常數 | "Hello"、"Rajeev" | 
宣告常數
定數是沒有名稱的常數。你可以使用 const 關鍵字來要宣告一個常數並幫它命名:
const myFavLanguage = "Python"
const sunRisesInTheEast = true你也可以像這樣在宣告時指定型別:
const a int = 1234
const b string = "Hi"在單一陳述式中也可以有多個宣告:
const country, code = "India", 91
const (
	employeeId string  = "E101"
	salary     float64 = 50000.0
)正如你所期望的,常數無法更改。也就是說,你無法在初始化常數後重新分派給它:
const a = 123
a = 321 // Compiler Error (Cannot assign to constant)已型別化和未型別化常數
Golang 中的常數很特別。它們的運作方式與其他語言的運作方式不同。要了解它們為何如此特別以及它們如何正常運作,我們需要關於 Go 型別系統的背景知識。讓我們直接進入正題吧!
背景知識
Go 是一種靜態型別的程式語言。也就是說編譯器在編譯時期就知道或推斷了每個變數的型別。
但是它的型別系統更進一步的不允許你執行混合數字型別的操作。舉例來說,你不能將 float64 變數加到 int,也不能將 int64 變數加到 int:
var myFloat float64 = 21.54
var myInt int = 562
var myInt64 int64 = 120
var res1 = myFloat + myInt // Not Allowed (Compiler Error)
var res2 = myInt + myInt64 // Not Allowed (Compiler Error)要使上述操作正常運作,你需要明確地轉換變數,讓它們都屬於相同型別:
var res1 = myFloat + float64(myInt) // Works
var res2 = myInt + int(myInt64)     // Works如果你使用過其他靜態型別的語言,如 C、C++ 或 Java,那麼你必須知道,只要在任何操作中將它們混合使用,它們會自動將較小的型別轉換為較大的型別。舉例來說,int 可以自動轉換為 long、float 或 double。
所以顯而易見的問題是:為什麼 Go 不會做同樣的事?它為什麼不像 C、C++ 或 Java 一樣執行隱式型別轉換?
以下是 Go 設計師所說的(節錄自 Golang 的官方文件):
C 語言籠罩在數字型別間便利的自動轉換所引起的混亂中。表達式什麼時候是無號數?可容納多大數值範圍?它會溢位嗎?結果是否可移植、不受執行它的機器影響?這也使得編譯器複雜化;「一般算術轉換」不容易實作且跨架構不一致。出於可移植性的原因,我們決定以程式碼中明確地型別轉換作為代價,讓事情變得清晰明瞭。
好吧!因此 Go 不提供隱式型別轉換,它要求我們在混合操作多個型別的變數時,必須使用明確地型別轉換。
但是 Go 的型別系統如何與常數一起使用?以下所有陳述式在 Golang 中皆有效:
var myInt32 int32 = 10
var myInt int = 10
var myFloat64 float64 = 10
var myComplex complex64 = 10在以上範例中,常數值 10 的型別是什麼?此外,如果 Golang 中沒有隱式型別轉換,那麼我們就不需要像這樣寫上面的陳述式:
var myInt32 int32 = int32(10)
var myFloat64 float64 = float64(10)
// etc..好吧!所有這些問題的答案都在於 Golang 處理常數的方式。因此,讓我們找出它們的處理方式。
未型別化常數
除非明確地給定型別,否則 Golang 中任何已命名或未命名的常數都是未型別化的。舉例來說,以下所有常數都是未型別化的:
1       // untyped integer constant
4.5     // untyped floating-point constant
true    // untyped boolean constant
"Hello" // untyped string constant即使你給它們名字,它們還是未型別化:
const a = 1
const f = 4.5
const b = true
const s = "Hello"現在,你可能會想說我使用的是如 integer 常數、string 常數之類的語法,而我還說它們是未型別化。
是的,1 是整數、4.5 是浮點數以及 "Hello" 是字串。但它們只是數值,還沒有給它們如 int32、float64 或 string 的固定型別,這會迫使它們遵守 Go 的嚴格型別規則。
事實上,1 是未型別化的,這讓我們可以將其分派給型別與整數相容的任何變數:
var myInt int = 1
var myFloat float64 = 1
var myComplex complex64 = 1要注意的是,雖然 1 未型別化,但它是未型別化的整數。因此,它只能在允許整數的地方使用。舉例來說,你不能將它分派給 string 或 boolean 變數。
同樣地,可以在任何允許使用浮點數的地方使用如 4.5 之類的未型別化浮點數常數:
var myFloat32 float32 = 4.5
var myComplex64 complex64 = 4.5現在來看一個未型別化字串常數的範例:
在 Golang 中,你可以使用 type 關鍵字來建立型別別名:
type RichString string // Type alias of `string`鑑於 Golang 的強型別特性,你不能將 string 變數分派給 RichString 變數:
var myString string = "Hello"
var myRichString RichString = myString // Won't work.但是,你可以將一個未型別化的字串常數分派給 RichString 變數,因為它與字串相容:
const myUntypedString = "Hello"
var myRichString RichString = myUntypedString // Works常數和型別推斷:預設型別
Go 支援型別推斷。也就是說,它可以從初始化變數的值推斷出變數的型別。因此,你可以宣告一個具有初始值但沒有任何型別資訊的變數,而 Go 會自動決定型別:
var a = 5 // Go compiler automatically infers the type of the variable `a`但是它是如何運作的呢?鑑於 Golang 中的常數是未型別化的,在上面的範例中,變數 a 的型別是什麼?是 int8、int16、int32、int64 還是 int?
好吧,事實證明,Golang 中每個未型別化的常數都有一個預設型別。當我們將常數分派給沒有任何明確型別的變數時,將使用預設型別。
以下是 Golang 中各種常數的預設型別:
| 常數 | 預設型別 | 
|---|---|
| 整數(10、76) | int | 
| 浮點數(3.14、7.92) | float64 | 
| 複數(3 + 5i) | complex128 | 
| 字元( 'a'、'♠') | rune | 
| 布林值(true, false) | bool | 
| 字串(”Hello”) | string | 
因此,在陳述式 var a = 5 中,由於沒有明確地型別資訊,整數常數的預設型別將決定 a 的型別,即 int。
已型別化常數
在 Golang 中,當你像這樣在宣告常數時明確地指定型別,常數是已型別化的:
const typedInt int = 1 // Typed constant就像變數一樣,Go 型別系統的所有規則都適用於已型別化的常數。舉例來說,你不能分派已型別化的整數常數到浮點數變數:
var myFloat64 float64 = typedInt // Compiler Error使用已型別化常數,你將失去未型別化常數帶來的所有靈活性,例如將它們分派給相容型別的任何變數,或將它們混合在算術運算中。因此,僅在絕對必要時,才應該為常數宣告型別。否則,只宣告沒有型別的常數。
常數表達式
常數是未型別化的(除非明確地給定型別),這讓你可以在任何表達式中自由混合它們。
因此,你可以擁有一個混合各種未型別化常數的表達式,只要這些未型別化的常數彼此相容:
const a = 5 + 7.5 // Valid
const b = 12 / 5  // Valid
const c = 'z' + 1 // Valid
const d = "Hey" + true // Invalid (untyped string constant and untyped boolean constant are not compatible with each other)對常數表達式及其結果的評估遵循某些規則。讓我們看看這些規則:
常數表達式的規則
- 比較兩個未型別化的常數,會輸出未型別化的布林值常數。 - const a = 7.5 > 5 // true (untyped boolean constant) const b = "xyz" < "uvw" // false (untyped boolean constant)
- 對於其他運算(除了位移): - 如果兩個運算元型別相同(例如,皆為未型別化的整數常數),則結果也是相同型別。舉例來說,表達式 - 25 / 2的結果是- 12,不是- 12.5。由於兩個運算元均為未型別化整數,因此結果將被截斷為整數。
- 如果兩個運算元型別不同,則結果是該運算元的型別且根據規則取較大的: - integer < rune < floating-point < complex。
 - const a = 25 / 2 // 12 (untyped integer constant) const b = (6 + 8i) / 2 // (3 + 4i) (untyped complex constant)
- 位移運算規則有點複雜。首先,有一些必要條件: - 位移表達式右側的運算元必須是未型別化的整數型別,或是可以表示 - uint型別的未型別化常數。
- 左側的運算元必須是整數型別,或是可以表示 - int型別的未型別化常數。
 - 規則:如果位移表達式左側的運算元是未型別化常數,則結果是未型別化的整數常數;否則結果與左側運算元的型別相同。 - const a = 1 << 5 // 32 (untyped integer constant) const b = int32(1) << 4 // 16 (int32) const c = 16.0 >> 2 // 4 (untyped integer constant) - 16.0 can represent a value of type `int` const d = 32 >> 3.0 // 4 (untyped integer constant) - 3.0 can represent a value of type `uint` const e = 10.50 << 2 // ILLEGAL (10.50 can't represent a value of type `int`) const f = 64 >> -2 // ILLEGAL (The right operand must be an unsigned int or an untyped constant compatible with `uint`)
常數表達式範例
讓我們來看一些常數表達式的範例:
package main
import "fmt"
func main() {
	var result = 25 / 2
	fmt.Printf("result is %v which is of type %T\n", result, result)
}# Output
result is 12 which is of type int由於 25 和 2 都是未型別化的整數常數,因此結果會被截斷為未型別化的整數 12。
若要得到正確的結果,你可以執行以下任一操作:
// Use a float value in numerator or denominator
var result = 25.0 / 2// Explicitly cast the numerator or the denominator
var result = float64(25) / 2讓我們來看另一個範例:
package main
import "fmt"
func main() {
	var result = 4.5 + (10-5)*(3+2)/2
	fmt.Println(result)
}以上程式會產生什麼結果?
嗯,不是 17。以上程式的實際結果是 16.5。我們來看一下表達式的求值順序,以了解為什麼結果是 16.5。
4.5 + (10 - 5) * (3 + 2)/2
          ↓
  4.5 + (5) * (3 + 2)/2
          ↓
   4.5 + (5) * (5)/2
          ↓
     4.5 + (25)/2
          ↓
      4.5 + 12
          ↓
        16.5了解了嗎?結果是錯誤的,因為表達式 25/2 的計算結果是 12。
要得到正確的結果,可以執行以下任一操作:
// Use a float value in the numerator or denominator
var result = 4.5 + (10-5)*(3+2)/2.0// Explicitly cast numerator or the denominator
var result = 4.5 + float64((10-5)*(3+2))/2結論
未型別化常數是 Go 創造者做出的驚人設計決策。儘管 Go 有強大的型別系統,但你可以使用未型別化的常數來擺脫 Go 的型別系統,並在處理任何表達式中的混合資料型別時具有更強大的靈活性。