正如上一節《Go語言變量聲明》中提到的 Go語言在聲明變量時,自動對變量對應的內存區域進行初始化操作。每個變量會初始化其類型的默認值,例如:
- 整型和浮點型變量的默認值為 0。
- 字符串變量的默認值為空字符串。
- 布爾型變量默認為 bool。
- 切片、函數、指針變量的默認為 nil。
當然,依然可以在變量聲明時賦予變量一個初始值。
回顧C語言
在C語言中,變量在聲明時,並不會對變量對應內存區域進行清理操作。此時,變量值可能是完全不可預期的結果。開發者需要習慣在使用C語言進行聲明時要初始化操作,稍有不慎,就會造成不可預知的后果。在網絡上只有程序員才能看懂的“燙燙燙”和“屯屯屯”的梗,就來源於C/C++中變量默認不初始化。
微軟的 VC 編譯器會將未初始化的棧空間以 16 進制的 0xCC 填充,而未初始化的堆空間使用 0xCD 填充,而 0xCCCC 和 0xCDCD 在中文的 GB2312 編碼中剛好對應“燙”和“屯”字。
因此,如果一個字符串沒有結束符
\0
,直接輸出的內存數據轉換為字符串就剛好對應“燙燙燙”和“屯屯屯”。
變量初始化的標准格式
var 變量名 類型 = 表達式
例如,游戲中,玩家的血量初始值為100。可以這樣寫:- var hp int = 100
上面代碼中,100 和 int 同為 int 類型,int 可以認為是冗余信息,因此可以進一步簡化初始化的寫法。
編譯器推導類型的格式
在標准格式的基礎上,將 int 省略后,編譯器會嘗試根據等號右邊的表達式推導 hp 變量的類型。- var hp = 100
下面是編譯器根據右值推導變量類型完成初始化的例子。
- var attack = 40
- var defence = 20
- var damageRate float32 = 0.17
- var damage = float32(attack-defence) * damageRate
- fmt.Println(damage)
- 第 1 和 2 行,右值為整型,attack 和 defence 變量的類型為 int。
- 第 3 行,表達式的右值中使用了 0.17。Go 語言和C語言一樣,編譯器會盡量提高精確度,以避免計算中的精度損失。
默認情況下,如果不指定 damageRate 變量的類型,Go 語言編譯器會將 damageRate 類型推導為 float64。由於這個例子中不需要 float64 的精度,所以強制指定類型為 float32。
- 第 4 行,將 attack 和 defence 相減后的數值結果依然為整型,使用 float32() 將結果轉換為 float32 類型,再與 float32 類型的 damageRate 相乘后,damage 類型也是 float32 類型。
提示:damage 變量的右值是一個復雜的表達式,整個過程既有 attack 和 defence 的運算還有強制類型轉換。強制類型轉換會在后面的章節中介紹。
- 第 5 行,輸出 damage 的值。
以上代碼輸出結果為:
3.4
短變量聲明並初始化
var 的變量聲明還有一種更為精簡的寫法,例如:- hp := 100
注意:由於使用了
:=
,而不是賦值的=
,因此推導聲明寫法的左值變量必須是沒有定義過的變量。若定義過,將會發生編譯錯誤。如果 hp 已經被聲明過,但依然使用
:=
時編譯器會報錯,代碼如下:
- // 聲明 hp 變量
- var hp int
- // 再次聲明並賦值
- hp := 10
no new variables on left side of :=
意思是,在“:=”的左邊沒有新變量出現,意思就是“:=”的左邊變量已經被聲明了。短變量聲明的形式在開發中的例子較多,比如:
- conn, err := net.Dial("tcp","127.0.0.1:8080")
- var conn net.Conn
- var err error
- conn, err = net.Dial("tcp", "127.0.0.1:8080")
注意:在多個短變量聲明和賦值中,至少有一個新聲明的變量出現在左值中,即便其他變量名可能是重復聲明的,編譯器也不會報錯,代碼如下:
- conn, err := net.Dial("tcp", "127.0.0.1:8080")
- conn2, err := net.Dial("tcp", "127.0.0.1:8080")