W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
原文鏈接:https://gopl-zh.github.io/ch3/ch3-06.html
常量表達式的值在編譯期計算,而不是在運行期。每種常量的潛在類型都是基礎(chǔ)類型:boolean、string或數(shù)字。
一個常量的聲明語句定義了常量的名字,和變量的聲明語法類似,常量的值不可修改,這樣可以防止在運行期被意外或惡意的修改。例如,常量比變量更適合用于表達像π之類的數(shù)學(xué)常數(shù),因為它們的值不會發(fā)生變化:
const pi = 3.14159 // approximately; math.Pi is a better approximation
和變量聲明一樣,可以批量聲明多個常量;這比較適合聲明一組相關(guān)的常量:
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
所有常量的運算都可以在編譯期完成,這樣可以減少運行時的工作,也方便其他編譯優(yōu)化。當(dāng)操作數(shù)是常量時,一些運行時的錯誤也可以在編譯時被發(fā)現(xiàn),例如整數(shù)除零、字符串索引越界、任何導(dǎo)致無效浮點數(shù)的操作等。
常量間的所有算術(shù)運算、邏輯運算和比較運算的結(jié)果也是常量,對常量的類型轉(zhuǎn)換操作或以下函數(shù)調(diào)用都是返回常量結(jié)果:len、cap、real、imag、complex和unsafe.Sizeof(§13.1)。
因為它們的值是在編譯期就確定的,因此常量可以是構(gòu)成類型的一部分,例如用于指定數(shù)組類型的長度:
const IPv4Len = 4
// parseIPv4 parses an IPv4 address (d.d.d.d).
func parseIPv4(s string) IP {
var p [IPv4Len]byte
// ...
}
一個常量的聲明也可以包含一個類型和一個值,但是如果沒有顯式指明類型,那么將從右邊的表達式推斷類型。在下面的代碼中,time.Duration是一個命名類型,底層類型是int64,time.Minute是對應(yīng)類型的常量。下面聲明的兩個常量都是time.Duration類型,可以通過%T參數(shù)打印類型信息:
const noDelay time.Duration = 0
const timeout = 5 * time.Minute
fmt.Printf("%T %[1]v\n", noDelay) // "time.Duration 0"
fmt.Printf("%T %[1]v\n", timeout) // "time.Duration 5m0s"
fmt.Printf("%T %[1]v\n", time.Minute) // "time.Duration 1m0s"
如果是批量聲明的常量,除了第一個外其它的常量右邊的初始化表達式都可以省略,如果省略初始化表達式則表示使用前面常量的初始化表達式寫法,對應(yīng)的常量類型也一樣的。例如:
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
如果只是簡單地復(fù)制右邊的常量表達式,其實并沒有太實用的價值。但是它可以帶來其它的特性,那就是iota常量生成器語法。
常量聲明可以使用iota常量生成器初始化,它用于生成一組以相似規(guī)則初始化的常量,但是不用每行都寫一遍初始化表達式。在一個const聲明語句中,在第一個聲明的常量所在的行,iota將會被置為0,然后在每一個有常量聲明的行加一。
下面是來自time包的例子,它首先定義了一個Weekday命名類型,然后為一周的每天定義了一個常量,從周日0開始。在其它編程語言中,這種類型一般被稱為枚舉類型。
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
周日將對應(yīng)0,周一為1,如此等等。
我們也可以在復(fù)雜的常量表達式中使用iota,下面是來自net包的例子,用于給一個無符號整數(shù)的最低5bit的每個bit指定一個名字:
type Flags uint
const (
FlagUp Flags = 1 << iota // is up
FlagBroadcast // supports broadcast access capability
FlagLoopback // is a loopback interface
FlagPointToPoint // belongs to a point-to-point link
FlagMulticast // supports multicast access capability
)
隨著iota的遞增,每個常量對應(yīng)表達式1 << iota,是連續(xù)的2的冪,分別對應(yīng)一個bit位置。使用這些常量可以用于測試、設(shè)置或清除對應(yīng)的bit位的值:
gopl.io/ch3/netflag
func IsUp(v Flags) bool { return v&FlagUp == FlagUp }
func TurnDown(v *Flags) { *v &^= FlagUp }
func SetBroadcast(v *Flags) { *v |= FlagBroadcast }
func IsCast(v Flags) bool { return v&(FlagBroadcast|FlagMulticast) != 0 }
func main() {
var v Flags = FlagMulticast | FlagUp
fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true"
TurnDown(&v)
fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false"
SetBroadcast(&v)
fmt.Printf("%b %t\n", v, IsUp(v)) // "10010 false"
fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true"
}
下面是一個更復(fù)雜的例子,每個常量都是1024的冪:
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776 (exceeds 1 << 32)
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424 (exceeds 1 << 64)
YiB // 1208925819614629174706176
)
不過iota常量生成規(guī)則也有其局限性。例如,它并不能用于產(chǎn)生1000的冪(KB、MB等),因為Go語言并沒有計算冪的運算符。
練習(xí) 3.13: 編寫KB、MB的常量聲明,然后擴展到Y(jié)B。
Go語言的常量有個不同尋常之處。雖然一個常量可以有任意一個確定的基礎(chǔ)類型,例如int或float64,或者是類似time.Duration這樣命名的基礎(chǔ)類型,但是許多常量并沒有一個明確的基礎(chǔ)類型。編譯器為這些沒有明確基礎(chǔ)類型的數(shù)字常量提供比基礎(chǔ)類型更高精度的算術(shù)運算;你可以認為至少有256bit的運算精度。這里有六種未明確類型的常量類型,分別是無類型的布爾型、無類型的整數(shù)、無類型的字符、無類型的浮點數(shù)、無類型的復(fù)數(shù)、無類型的字符串。
通過延遲明確常量的具體類型,無類型的常量不僅可以提供更高的運算精度,而且可以直接用于更多的表達式而不需要顯式的類型轉(zhuǎn)換。例如,例子中的ZiB和YiB的值已經(jīng)超出任何Go語言中整數(shù)類型能表達的范圍,但是它們依然是合法的常量,而且像下面的常量表達式依然有效(譯注:YiB/ZiB是在編譯期計算出來的,并且結(jié)果常量是1024,是Go語言int變量能有效表示的):
fmt.Println(YiB/ZiB) // "1024"
另一個例子,math.Pi無類型的浮點數(shù)常量,可以直接用于任意需要浮點數(shù)或復(fù)數(shù)的地方:
var x float32 = math.Pi
var y float64 = math.Pi
var z complex128 = math.Pi
如果math.Pi被確定為特定類型,比如float64,那么結(jié)果精度可能會不一樣,同時對于需要float32或complex128類型值的地方則會強制需要一個明確的類型轉(zhuǎn)換:
const Pi64 float64 = math.Pi
var x float32 = float32(Pi64)
var y float64 = Pi64
var z complex128 = complex128(Pi64)
對于常量面值,不同的寫法可能會對應(yīng)不同的類型。例如0、0.0、0i和\u0000
雖然有著相同的常量值,但是它們分別對應(yīng)無類型的整數(shù)、無類型的浮點數(shù)、無類型的復(fù)數(shù)和無類型的字符等不同的常量類型。同樣,true和false也是無類型的布爾類型,字符串面值常量是無類型的字符串類型。
前面說過除法運算符/會根據(jù)操作數(shù)的類型生成對應(yīng)類型的結(jié)果。因此,不同寫法的常量除法表達式可能對應(yīng)不同的結(jié)果:
var f float64 = 212
fmt.Println((f - 32) * 5 / 9) // "100"; (f - 32) * 5 is a float64
fmt.Println(5 / 9 * (f - 32)) // "0"; 5/9 is an untyped integer, 0
fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0 is an untyped float
只有常量可以是無類型的。當(dāng)一個無類型的常量被賦值給一個變量的時候,就像下面的第一行語句,或者出現(xiàn)在有明確類型的變量聲明的右邊,如下面的其余三行語句,無類型的常量將會被隱式轉(zhuǎn)換為對應(yīng)的類型,如果轉(zhuǎn)換合法的話。
var f float64 = 3 + 0i // untyped complex -> float64
f = 2 // untyped integer -> float64
f = 1e123 // untyped floating-point -> float64
f = 'a' // untyped rune -> float64
上面的語句相當(dāng)于:
var f float64 = float64(3 + 0i)
f = float64(2)
f = float64(1e123)
f = float64('a')
無論是隱式或顯式轉(zhuǎn)換,將一種類型轉(zhuǎn)換為另一種類型都要求目標可以表示原始值。對于浮點數(shù)和復(fù)數(shù),可能會有舍入處理:
const (
deadbeef = 0xdeadbeef // untyped int with value 3735928559
a = uint32(deadbeef) // uint32 with value 3735928559
b = float32(deadbeef) // float32 with value 3735928576 (rounded up)
c = float64(deadbeef) // float64 with value 3735928559 (exact)
d = int32(deadbeef) // compile error: constant overflows int32
e = float64(1e309) // compile error: constant overflows float64
f = uint(-1) // compile error: constant underflows uint
)
對于一個沒有顯式類型的變量聲明(包括簡短變量聲明),常量的形式將隱式?jīng)Q定變量的默認類型,就像下面的例子:
i := 0 // untyped integer; implicit int(0)
r := '\000' // untyped rune; implicit rune('\000')
f := 0.0 // untyped floating-point; implicit float64(0.0)
c := 0i // untyped complex; implicit complex128(0i)
注意有一點不同:無類型整數(shù)常量轉(zhuǎn)換為int,它的內(nèi)存大小是不確定的,但是無類型浮點數(shù)和復(fù)數(shù)常量則轉(zhuǎn)換為內(nèi)存大小明確的float64和complex128。 如果不知道浮點數(shù)類型的內(nèi)存大小是很難寫出正確的數(shù)值算法的,因此Go語言不存在整型類似的不確定內(nèi)存大小的浮點數(shù)和復(fù)數(shù)類型。
如果要給變量一個不同的類型,我們必須顯式地將無類型的常量轉(zhuǎn)化為所需的類型,或給聲明的變量指定明確的類型,像下面例子這樣:
var i = int8(0)
var i int8 = 0
當(dāng)嘗試將這些無類型的常量轉(zhuǎn)為一個接口值時(見第7章),這些默認類型將顯得尤為重要,因為要靠它們明確接口對應(yīng)的動態(tài)類型。
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)
現(xiàn)在我們已經(jīng)講述了Go語言中全部的基礎(chǔ)數(shù)據(jù)類型。下一步將演示如何用基礎(chǔ)數(shù)據(jù)類型組合成數(shù)組或結(jié)構(gòu)體等復(fù)雜數(shù)據(jù)類型,然后構(gòu)建用于解決實際編程問題的數(shù)據(jù)結(jié)構(gòu),這將是第四章的討論主題。
![]() | ![]() |
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: