1.2 變數、零值與短變數宣告
Go 變數宣告的核心規則是:每個變數都有明確型別,未指定初始值時會得到該型別的零值。本章將說明 var、:=、型別推斷與零值如何讓 Go 程式保持可預測。
變數一定有型別
Go 的變數一定屬於某個明確型別。這個型別可以由程式直接寫出,也可以由編譯器根據右側的初始值推斷出來;但推斷完成後,變數的型別就固定了。
1var name string = "api"
2var port int = 8080
3
4enabled := true
5timeout := 30name 的型別是 string,port 的型別是 int,enabled 與 timeout 則由右側初始值推斷型別。Go 允許省略重複資訊,但不允許變數在執行期間任意改變型別。
1count := 10
2count = 20 // 可以,仍然是 int
3// count = "20" // 編譯錯誤:string 不能指派給 int這個規則讓 Go 程式在閱讀時更穩定:看到一個變數後,讀者可以相信它的型別不會在後面突然變成另一種資料。
零值讓變數可立即使用
零值是 Go 對「尚未明確指定初始值」的標準答案。每個型別都有自己的零值:數字是 0,字串是空字串,布林是 false,指標、slice、map、function、interface 與 channel 是 nil。
1var retryCount int
2var title string
3var debug bool
4var tags []string
5
6fmt.Println(retryCount) // 0
7fmt.Println(title) // ""
8fmt.Println(debug) // false
9fmt.Println(tags == nil) // true零值是型別的預設狀態。這是 Go 設計中很重要的精神:讓資料結構在最少初始化下仍然有合理行為。
1type Counter struct {
2 value int
3}
4
5func (c *Counter) Add(n int) {
6 c.value += n
7}
8
9func (c Counter) Value() int {
10 return c.value
11}
12
13var counter Counter
14counter.Add(3)
15fmt.Println(counter.Value()) // 3Counter 沒有建構函式也能使用,因為 value 的零值是 0。當型別可以靠零值進入可用狀態時,使用者需要記住的初始化規則就會少很多。
var 適合宣告意圖
var 的主要用途是清楚宣告變數的存在、型別或零值意義。當你需要讓讀者知道「這個變數稍後才會被賦值」或「零值本身有意義」時,var 比 := 更清楚。
1var userID string
2
3if fromHeader != "" {
4 userID = fromHeader
5} else {
6 userID = fromCookie
7}這裡的 userID 需要經過條件判斷才會得到值,因此先用 var 宣告變數,再在不同分支指派。讀者看到 var userID string,可以理解這是一個稍後會被填入的字串。
var 也適合用在 package 層級,因為 package 層級不能使用 :=。
1var defaultPort = 8080
2var serviceName = "worker"package 層級變數會增加全域狀態,使用前要先確認它是否真的代表整個 package 的共同狀態;如果只是函式內部暫存資料,應該放回函式裡。
:= 適合區域初始化
短變數宣告 := 的主要用途是在函式內同時宣告與初始化變數。當右側初始值已經清楚表達型別時,:= 可以讓程式更精簡。
1func greeting(name string) string {
2 message := "hello, " + name
3 return message
4}message 的型別可以從字串串接結果推斷,所以不需要寫成 var message string = ...。這種省略是移除讀者已經能從右側看懂的重複資訊。
短變數宣告只能用在函式內。以下寫法不能放在 package 層級:
1// package 層級不允許:
2// port := 8080:= 也要求左側至少有一個新變數。這個規則會影響常見的錯誤處理寫法。
1data, err := loadFile("config.json")
2if err != nil {
3 return err
4}
5
6config, err := parseConfig(data) // config 是新變數,err 是重新指派
7if err != nil {
8 return err
9}第二次使用 := 是合法的,因為 config 是新變數;同時 err 會被重新指派。這種寫法在 Go 很常見,但要留意不要在內層區塊意外宣告出新的同名變數。
型別推斷不等於放棄型別
型別推斷的核心作用是減少重複,不是讓變數變成動態型別。Go 只在編譯期根據右側表達式推斷型別,推斷後仍然遵守靜態型別規則。
1limit := 100 // int
2ratio := 0.75 // float64
3label := "active" // string當右側型別不夠明確,或你需要指定更精確的型別時,就應該直接寫出型別。
1var timeoutSeconds int64 = 30
2var threshold float32 = 0.8這裡如果只寫 timeoutSeconds := 30,通常會得到 int;如果 API、資料庫欄位或二進位格式需要 int64,明確宣告型別會比事後轉型更好讀。
指標、slice 與 map 的零值差異
零值的共同規則是「未初始化時有定義好的狀態」,但不同型別的零值可用程度不同。指標的零值是 nil,使用前需要確認是否指向有效資料;slice 的零值可以安全讀長度與 append;map 的零值可以讀取但不能寫入。
1var names []string
2names = append(names, "alice")
3fmt.Println(len(names)) // 1
4
5var scores map[string]int
6fmt.Println(scores["alice"]) // 0
7// scores["alice"] = 10 // panic: assignment to entry in nil mapslice 的零值能直接 append,所以很多函式可以回傳 nil slice 表示沒有資料;呼叫端仍然可以用 len 與 range 處理。map 要寫入前必須先用 make 初始化。
1scores := make(map[string]int)
2scores["alice"] = 10這個差異是零值判讀的核心陷阱:nil slice 通常容易處理,nil map 則需要先初始化才能寫入。
命名要服務讀者
變數名稱的核心責任是說明資料在當前範圍內的角色。範圍越小,名稱可以越短;範圍越大,名稱就應該越具體。
1for i, item := range items {
2 fmt.Println(i, item)
3}i 在很短的迴圈內代表 index,讀者可以立即理解。相反地,跨越多個段落使用的變數應該使用更完整的名稱。
1requestTimeout := 5 * time.Second
2maxRetryCount := 3Go 程式常使用短名稱,但短名稱不是目標本身。好的名稱應該讓讀者不必回頭搜尋變數來源,就能理解這個值現在代表什麼。
下一章
下一章會進入控制流程,說明 Go 如何用少量語法表達條件、迴圈與多分支判斷。