Go語言學習十二 變量和常量

本文最初發表在我的個人博客,查看原文,獲得更好的閱讀體驗


Go 使用var關鍵字聲明變量;使用關鍵字const聲明常量。變量可以像常量一樣初始化。

一 變量

1.1 變量聲明

語法:
var 變量名 變量類型 | = 值

var x int
var y string = "hello"
var z = 2
var c, python, java = true, false, "no!"
var r, s int

預聲明的值nil不能用於初始化沒有顯式類型的變量。

var n = nil            // 非法

另外,還可以使用短變量聲明運算符:=來聲明變量,以下聲明是等價的:

a := 2
var a = 2

短變量聲明可能僅出現在函數內部。在某些上下文中,例如ifforswitch語句的初始化程序,它們可用於聲明本地臨時變量。

實現限制:如果在函數體內聲明瞭變量卻未使用,編譯器可能認爲是非法的。

初始值也可以是一個在運行時計算的一般表達式

var (
    home   = os.Getenv("HOME")
    user   = os.Getenv("USER")
    gopath = os.Getenv("GOPATH")
)

調用內置函數new或獲取複合字面量的地址,會在運行時爲變量分配存儲空間。這種匿名變量通過指針重定向來引用(可能是隱式的)。

數組,切片和結構類型的結構化變量具有可單獨尋址的元素和字段。每個這樣的元素就像一個變量。

1.2 變量的類型

聲明時給出的類型、調用new函數或在複合字面量中提供的類型,或結構化變量的元素類型,稱爲變量的靜態類型(或僅僅類型)。
接口類型的變量甚至具有不同的動態類型,它是在運行時分配給變量的值的具體類型(除非該值是預先聲明的標識符nil,它沒有類型)。動態類型在執行期間可能會有所不同,但存儲在接口變量中的值始終可分配給變量的靜態類型。

var x interface{}  // x is nil and has static type interface{}
var v *T           // v has value nil, static type *T
x = 42             // x has value 42 and dynamic type int
x = v              // x has value (*T)(nil) and dynamic type *T

如果尚未爲變量賦值,則其值爲其類型的零值。

二 常量

Go中的常量有:布爾常量,符文常量,整數常量,浮點常數,複數常量和字符串常量。符文,整數,浮點和複數常量統稱爲數字常量。常量就是不變量。它們在編譯期間創建。即使在函數中定義爲局部常量,也是如此。

由於編譯時限制,定義它們的表達式必須是可被編譯器求值的常量表達式。例如,1<<3是常量表達式,而math.Sin(math.Pi/4)不是,因爲函數調用math.Sin需要在運行時發生。

2.1 常量聲明

常量的聲明與變量類似,只不過是使用const關鍵字。常量聲明將標識符列表(常量的名稱)綁定到常量表達式列表的值。標識符的數量必須等於表達式的數量,左側的第n個標識符綁定到右側的第n個表達式的值。

如果存在類型,則所有常量都採用指定的類型,並且表達式必須可分配給該類型。如果省略該類型,則常量將採用相應表達式的各個類型。

const Pi float64 = 3.14159265358979323846
const zero = 0.0         // untyped floating-point constant
const (
	size int64 = 1024
	eof        = -1  // untyped integer constant
)
const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", untyped integer and string constants
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

在帶括號的const聲明列表中,除了第一個ConstSpec之外,可以省略表達式列表。與iota常量生成器一起,此機制允許輕量級聲明順序值:

const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Partyday
	numberOfDays  // this constant is not exported
)

常量不能用:=語法聲明。預聲明常量有:布爾常量true,false;預聲明標識符:iota

可以使用枚舉器iota創建枚舉常量。由於iota可以是表達式的一部分,並且表達式可以隱式重複,因此很容易構建複雜的值集。

type ByteSize float64

const (
    _           = iota // 通過賦值給空白標識符來忽略第一個值(0)
    KB ByteSize = 1 << (10 * iota)// 序列自動順延
    MB // 聲明自動順延
    GB
    TB
    PB
    EB
    ZB
    YB
)

將諸如String之類的方法附加到任何用戶定義類型的能力使得任意值可以自動格式化以進行打印。雖然它最常用於結構,但這種技術對標量類型也很有用,比如像ByteSize這種浮點類型。

func (b ByteSize) String() string {
    switch {
    case b >= YB:
        return fmt.Sprintf("%.2fYB", b/YB)
    case b >= ZB:
        return fmt.Sprintf("%.2fZB", b/ZB)
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%.2fB", b)

使用Sprintf來實現ByteSizeString方法是安全的(避免無限重複)不是因爲轉換而是因爲它用%f調用Sprintf,這不是一個字符串格式:Sprintf只在需要字符串時調用String方法,%f需要浮點數。

2.2 無類型常量

無類型常量具有默認類型,該類型是在需要鍵入值的上下文中隱式轉換常量的類型,例如,在一個簡短的變量聲明中,例如i := 0,其中沒有顯式類型。無類型常量的默認類型分別是bool,rune,int,float64,complex128或string,具體取決於它是布爾值,符文,整數,浮點數,複數還是字符串常量。

實現限制:雖然數字常量在語言中具有任意精度,但編譯器可能使用精度有限的內部表示來實現它們。也就是說,每個實現必須:

  • 表示至少256位的整數常量。
  • 表示浮點常數,包括複數常量的部分,尾數至少爲256位,有符號二進制指數至少爲16位。
  • 如果無法精確表示整數常量,則給出錯誤。
  • 如果由於溢出而無法表示浮點或複數常量,則給出錯誤。
  • 如果由於精度限制而無法表示浮點或複數常量,則舍入到最接近的可表示常量

這些要求既適用於字面量常量,也適用於求值常量表達式的結果。

2.3 常量表達式

常量表達式只包含常量操作數,且在編譯時求值。

無類型的布爾值,數字,字符串常量可以用作操作數,只要分別滿足布爾,數字和字符串的操作數的要求。

常量的比較總是產生無類型的布爾常量。

如果常量移位表達式的左操作數是無類型常量,則結果爲整數常量,否則它是一個與左操作數相同類型的常量,且必須是整數類型。

無類型常量的任何其他運算都會導致產生同樣的無類型常量;也就是說,布爾,整數,浮點數,複數或字符串常量。如果二元運算(非移位操作)的無類型操作數具有不同的類型,則結果是該列表中後出現的操作數類型:整數,符文,浮點數,複數。例如,無類型整數常量除以無類型複數常量會產生無類型複數常量。

const a = 2 + 3.0          // a == 5.0   (無類型浮點常量)
const b = 15 / 4           // b == 3     (無類型整數常量)
const c = 15 / 4.0         // c == 3.75  (無類型浮點常量)
const Θ float64 = 3/2      // Θ == 1.0   (float64類型, 3/2 是整數除法)
const Π float64 = 3/2.     // Π == 1.5   (float64類型, 3/2. 是浮點除法)
const d = 1 << 3.0         // d == 8     (無類型整數常量)
const e = 1.0 << 3         // e == 8     (無類型整數常量)
const f = int32(1) << 33   // 非法    (常量 8589934592 在 int32下溢出)
const g = float64(2) >> 1  // 非法    (float64(2) 是浮點類型的常量)
const h = "foo" > "bar"    // h == true  (無類型布爾常量)
const j = true             // j == true  (無類型布爾常量)
const k = 'w' + 1          // k == 'x'   (無類型符文常量)
const l = "hi"             // l == "hi"  (無類型字符串常量)
const m = string(k)        // m == "x"   (字符串類型)
const Σ = 1 - 0.707i       //            (無類型複數常量)
const Δ = Σ + 2.0e-4       //            (無類型複數常量)
const Φ = iota*1i - 1/1i   //            (無類型複數常量)

常量表達式始終可以精確計算。中間值和常量本身可能要求精度明顯大於語言中任何預聲明的類型所支持的精度。以下是合法聲明:

const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (無類型整數常量)
const Four int8 = Huge >> 98  // Four == 4                                (int8類型的整數常量)

常量除法或餘數運算的除數不得爲零:

x := 3.14
x / 0.0      // 合法
3.14 / 0.0   // 非法: 被零除

類型常量的值必須始終可以通過常量類型的值準確表示,以下常量表達式是非法的:

uint(-1)     // -1 不能表示爲 uint
int(3.14)    // 3.14 不能表示爲 int
int64(Huge)  // 1267650600228229401496703205376 不能表示爲 int64
Four * 300   // 操作數 300 不能表示爲 int8 (Four的類型)
Four * 100   // 積 400 不能表示爲 int8 (Four的類型)

一元按位補碼運算符^使用的掩碼滿足非常量的規則:對於無符號常量,掩碼全爲1,對於有符號和無類型常量,掩碼均爲-1

^1         // 無類型整數常量, 等於 -2
uint8(^1)  // 非法: 同 uint8(-2), -2 不能表示爲 uint8
^uint8(1)  // uint8 類型的常量, 同 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // 同 int8(-2)
^int8(1)   // 同 -1 ^ int8(1) = -2

實現限制:編譯器可能在計算無類型浮點數或複數常量表達式時使用舍入;請參閱常量一節中的實現限制。這種舍入可能導致浮點常量表達式在整數上下文中無效,即使在使用無限精度計算時它將是積分的,反之亦然。

2.4 求值順序

在包級別,初始化依賴決定變量聲明中各個初始化表達式的求值順序。除此之外,在計算表達式的操作數、賦值或返回語句時,所有函數調用,方法調用和通信操作都按詞法從左到右進行求值。

例如,在(函數-局部)賦值中:

y[f()], ok = g(h(), i()+x[j()], <-c), k()

函數調用和通信按f(), h(), i(), j(), <-c, g(), 和 k()的順序發生。但是,沒有指定與x的求值和索引以及y的求值相比較的那些事件的順序。

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x 可能爲 [1, 2] 或 [2, 2]: a 和 f() 之間的求值順序未指定
m := map[int]int{a: 1, a: 2}  // m 可能爲 {2: 1} 或 {2: 2}: 兩個映射賦值的求值順序未指定
n := map[int]int{a: f()}      // n 可能Wie {2: 3} 或 {3: 3}: key 和 value之間的求值順序未指定

在包級別,初始化依賴會覆蓋單個初始化表達式的從左到右的規則,但不會覆蓋每個表達式中的操作數的順序規則:

var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// 函數 u 和 v 獨立於所有其他變量和函數

函數調用按順序u(), sqr(), v(), f(), v(), 和 g()發生。

單個表達式中的浮點運算根據運算符的關聯性求值。顯式括號通過覆蓋默認關聯性來影響求值順序。在表達式x + (y + z)中,在加x前先執行加法(y + z)

2.5 iota常量生成器

在常量聲明中,預先聲明的標識符iota表示連續的無類型整數常量。它的值是該常量聲明中相應ConstSpec的索引,從零開始。它可以用於構造一組相關的常量:

const (
	c0 = iota  // c0 == 0
	c1 = iota  // c1 == 1
	c2 = iota  // c2 == 2
)

const (
	a = 1 << iota  // a == 1  (iota == 0)
	b = 1 << iota  // b == 2  (iota == 1)
	c = 3          // c == 3  (iota == 2, unused)
	d = 1 << iota  // d == 8  (iota == 3)
)

const (
	u         = iota * 42  // u == 0     (untyped integer constant)
	v float64 = iota * 42  // v == 42.0  (float64 constant)
	w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0
const y = iota  // y == 0

根據定義,同一ConstSpec中iota的多次使用都具有相同的值:

const (
	bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0  (iota == 0)
	bit1, mask1                           // bit1 == 2, mask1 == 1  (iota == 1)
	_, _                                  //                        (iota == 2, unused)
	bit3, mask3                           // bit3 == 8, mask3 == 7  (iota == 3)
)

最後一個示例利用最後一個非空表達式列表的隱式重複。

參考:
https://golang.org/doc/effective_go.html#variables
https://golang.org/ref/spec#Variables
https://golang.org/ref/spec#Constant_expressions
https://golang.org/ref/spec#Order_of_evaluation

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章